diff --git a/CHANGELOG.md b/CHANGELOG.md index 568900e6a..453583921 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,229 @@ follow semantic versioning; release dates are ISO 8601. ### Public API +- **A proposal block carries the paragraph that opens it.** `ProposalScope`, + `ProposalGoals` and `ProposalGlance` each set a heading and a list, and every one-page + sales proposal measured for the header spine also sets a paragraph between the two — + five of the seven set two of them, once over the solution and once over the reasons to + choose the issuer. The header survey missed it, so it arrives here rather than inside + the first preset that needs it. All three records gain `intro`, a plain string blank + when absent, and each keeps its previous constructor explicitly, so existing calls + compile and link unchanged and every block built through them opens straight into its + list as before. + +- **A proposal carries the header a one-page sales proposal has.** The proposal model was + shaped for a two-page consulting document: a running header naming three things by role, + and a title of exactly three lines. A one-page sales proposal opens differently — an + addressed organisation with its own address block, a named person at it with a role and + two channels, a row of marked tiles whose captions are the document's own, a headline + broken across however many lines the design breaks it across, and a foot carrying the + legal entity rather than only a name. None of that had a home, and each of the seven + designs measured for this needs the same five things, which is why they arrive together + rather than one per preset: five separate additions to a published record would leave + the model a patchwork of near-duplicates. +

+ `ProposalRecipient`, `ProposalAttention` and `ProposalFooter` are new; + `StructuredProposalData` carries them as `recipient`, `attention` and `footer`, and its + twelve-argument constructor is kept explicitly so existing calls compile and link + unchanged. `ProposalTitleLines` gains `eyebrow`, `lines` and `standfirst`, with `lines` + the canonical reading and `lead`/`second`/`third` its first three — one statement kept + consistent by construction rather than by the caller, so a preset written against the + three reads the same title a four-line document states. The list form is a factory, + `ProposalTitleLines.of`, because a second three-argument constructor taking + `(String, List, List)` is ambiguous for a caller passing nulls. + `ProposalMetaLine` gains `entries`, and those do not derive from the trio in + either direction: turning three roles into tiles would mean inventing their captions, + and reading roles back out of arbitrary tiles would mean guessing which is which. A + preset draws the one its design has. + +- **An invoice line carries where it was delivered.** A design that bills the same + service in more than one place — the same instance type in two datacentres, the same + plan in two jurisdictions — prints where beside what, as its own column next to the + service. `InvoiceServiceLines.Line` had nowhere to put it: `servicePeriod` is the + neighbouring column on such a sheet, so folding the two together collapses two columns + the design draws apart, and `vatRate` is a tax rate that happens to be free. `Line` now + carries `region` and `Columns` its caption, both plain strings blank when absent, and a + design with one location per invoice leaves them alone. All three constructors that + predate it — the ones before the per-line tax rate, the mark, and the region — are kept + explicitly, so existing calls compile and link unchanged and every line built through + them still prints no region. + +- **A supplier carries the legal line it prints under its name.** A footer band that + identifies the issuer often has to carry more than a name and an address: a + parent-company or regulator disclosure — "X is a subsidiary of Y" — is a statement the + jurisdiction requires on the sheet, and deriving it from the fields beside it is not + possible because it is prose, not a number. Composed from `legalName` alone, that line + simply went missing. `InvoiceContactBlock` now carries `legalFootnote`, a plain string + blank when absent, like the fields beside it; a preset with no footer band ignores it. + Both constructors that predate it — the one before the second registration and the one + before the footnote — are kept explicitly, so existing calls compile and link unchanged + and every block built through them still prints no disclosure. + +- **A billed party carries a printed registration.** A supplier could already state a + tax registration through `InvoiceContactBlock.taxRegistrationLabel` / + `taxRegistrationNumber`, and the party being billed could not — but most B2B invoices + print the customer's VAT or tax number too, under its address. `subline` was the only + spare field and it is contractually the attention line that sits *above* the address, + so a registration put there renders in the wrong place. `InvoiceRecipient` now carries + `registrationLabel` and `registrationNumber`, mirroring the pair the supplier block + already has, with `hasRegistration()` for the presets that draw the row only when + there is a number to draw. Both are plain strings, blank when absent, and the + six-argument constructor is kept explicitly, so existing calls compile and link + unchanged and every recipient built through them still prints no registration. + +- **An invoice line carries a mark.** A design that opens each service line with a + glyph — a card for a billing line, a shield for fraud screening, a globe for a hosted + service — had nowhere to say which one, and deriving it from the description would + have been guesswork dressed as a feature. `InvoiceServiceLines.Line` now carries + `icon`, a plain string blank when absent, exactly as `CvEntry.icon` already works on + the CV side: the token means something only to the preset that packages it, and a + preset that draws no marks ignores it. It costs the layout nothing where nothing is + set. Both constructors that predate it — the one before the per-line tax rate and the + one before the mark — are kept explicitly, so existing calls compile and link + unchanged and every line built through them still carries no mark. + +- **`CvSkill` carries the level as the document words it.** A rated skill could say how + much — a number in `[0, 1]` a preset draws as dots or a meter — or it could say it in + words by not being a skill at all and living in a `RowsSection` instead. It could not + say both, which is why `CharcoalGold` sets its languages as rows: that design writes + "Native" and "B2 – Upper Intermediate" out, and a number could not carry them back. A + design that shows a rating *and* names it had nowhere to go. `CvSkill` now carries + `note` — a plain string, blank when absent, like the fields beside it — reachable + through `CvSkill.of(name, level, note)`. The two channels stay separate because a + number cannot carry a wording and a wording cannot be measured into a meter; a preset + draws whichever it has room for, or both. The two-argument constructor and both + existing factories are kept explicitly, so existing calls compile and link unchanged + and every skill built through them still carries no note. + +- **The structured invoice model carries what a second sheet needs.** It landed with + one consumer, `ConsultingInvoice`, and a model shaped around one document is a model + nobody has tested. Fitting a second published invoice to it found six things it could + not say, each of them general rather than one design's whim: a brand lockup drawn as + a two-line monogram instead of a logo (`InvoiceBrand.monogramTop` / `monogramBottom`); + a second labelled registration, because a UK sender prints both a company number and + a VAT number (`InvoiceContactBlock.taxRegistrationLabel` / `taxRegistrationNumber`); + a delivery address beside the billing one (`StructuredInvoiceData.shipTo`); a tax + rate printed per line and its column (`InvoiceServiceLines.Line.vatRate`, + `Columns.vat`), written as the design shows it because the wording differs by + jurisdiction; and who the money is paid to, with the closing line beside the due + notice (`InvoicePaymentBlock.accountHolder` / `signOff`). Every addition is blank + when absent, and every constructor that predates one is kept explicitly, so existing + calls compile and link unchanged — `ConsultingInvoice` passes its snapshot and pixel + gates untouched, which is the proof. + +- **`CvEntry` carries a link.** An entry that points somewhere — a repository, a case + study, a company — had no way to say so, and a preset had no way to make its title + reachable. `CvEntry` now carries `link`, a plain string blank when absent like the + fields beside it, set through `CvEntry.Builder.link(...)`. It costs the layout + nothing: a link is an annotation rather than ink, so a linked title and a plain one + are the same sheet, which is also why the parity gates cannot see it and a test + asserts the targets directly. The six-argument constructor is kept explicitly, so + existing calls compile and link unchanged. + +- **`CvEntry` carries a location and a mark, and gains a builder.** The record held a + title, a subtitle, a date and a body, which is enough for a dated block and not + enough for the designs that set the city beside the employer in its own colour, or + that open a project with an icon. Folding a location into the subtitle would have + merged two things a design styles apart; deriving an icon from the text would have + been guesswork. `CvEntry` now carries `place` and `icon` — both plain strings, blank + when absent, matching how `subtitle` and `date` already behave — and + `CvEntry.builder(title)` reaches them without counting six positions. The icon + vocabulary is preset-scoped: a token means something only to the preset that packages + it, and the presets that draw no marks ignore it. The four-argument constructor is + kept explicitly, so existing calls compile and link unchanged. + +- **`CvIdentity` carries an optional portrait.** A CV design with a photograph in it + had nowhere to put one: the identity record held the name, the title, the contact + triple and the links, and a preset that wanted a face had to ship a silhouette of its + own. `CvIdentity` now carries `Optional portrait` — the image + itself, because a photograph is caller-supplied content rather than template chrome — + reachable through `CvIdentity.Builder.portrait(...)`. The four- and three-argument + constructors are kept explicitly, so existing calls compile and link unchanged; only a + record deconstruction pattern over `CvIdentity` sees the extra component. A document + carrying a portrait still renders through every preset in the family — the ones with + nowhere to draw it ignore it. + +- **A structured invoice document model.** The invoice family's data layer knew one + shape — an invoice as pre-formatted display strings, with one address block per + party, line items whose quantity and money are already rendered, and a flat list of + summary rows. That cannot carry the structured business invoice: a brand lockup with + the sender's own logo, labelled masthead metadata, a contact block with a business + registration, priced service lines carrying `BigDecimal` figures and the unit they + are counted in, a totals stack with its own total band, bank payment fields, and a + footer line. `templates.data.invoice` now carries that second model — + `StructuredInvoiceData` (+ its section records) wrapped by + `StructuredInvoiceDocumentSpec` — alongside the display one; a preset consumes the + model whose shape it renders. The brand logo arrives as `DocumentImageData`, because + the logo is caller-supplied content rather than template chrome, and it stays + optional so a wordmark-only lockup composes. Every component normalizes `null` to + its empty form, money and quantities default to zero, and collections are frozen. + +- **A structured proposal document model.** The proposal family's data layer knew one + shape — a titled run of prose sections with a flat timeline and pricing list — which + cannot carry the structured business proposal: brand marks, an authored multi-line + title, an at-a-glance fact card, goal cells, a numbered scope list, authored + deliverable columns, a phase grid with its own headers, priced rows with a + `Role` (`NONE` / `SUBTOTAL` / `OPTIONAL`) and a total band, and a signing card. + `templates.data.proposal` now carries that second model — + `StructuredProposalData` (+ its section records) wrapped by + `StructuredProposalDocumentSpec` — alongside the narrative one; a preset consumes + the model whose shape it renders. Every component normalizes `null` to its empty + form and freezes its collections, matching the family's existing records. + +- **A rota document model, replacing `data.schedule`.** The library already shipped a + weekly-roster model — `templates.data.schedule`, public since the templates module was + extracted — and nothing ever rendered it, because it is not shaped like a rota is + drawn: its people are ordered by a token and grouped nowhere, so there are no staff + bands; an assignment cannot say that one half of a split day is drawn more quietly + than the other; a day heading is one string, which a design that raises the ordinal's + suffix cannot split; and a category carries three `java.awt.Color`s, which puts the + rendering decision in the document and stops two presets drawing the same rota in two + palettes. `templates.data.rota` is the replacement — `StructuredRotaData` (venue, + week, day columns, legend, staff bands, footer) wrapped by + `StructuredRotaDocumentSpec`, with `RotaVenue` / `RotaWeek` / `RotaDay` / + `RotaCovers` / `RotaLegend` / `RotaGroup` / `RotaStaff` / `RotaShift` / `RotaFooter` + and the `ShiftStatus` / `ShiftEmphasis` enumerations. +

+ **The grid is the document's, not the preset's.** `days()` is the columns and every + `RotaStaff.days()` runs in that same order, so a cell is found by position and a rota + of five days is as ordinary as one of seven — the span is a label the document + carries, not a shape the model imposes. A person's day is a *list* of entries rather + than one, which makes the two shapes a rota actually has — a blank cell and a split + shift — ordinary rather than special cases a preset has to invent a representation + for. `RotaStaff.day(int)` answers with nothing for a day the rota does not reach, so a + short row is a short row and not a broken document. +

+ **What a cell means is separate from what it prints.** `ShiftStatus` is the meaning a + preset colours by; the text is the word a particular site uses for it, so a rota + printing `A/L` and one printing `HOL` colour alike, and a legend entry pairs the two. + `ShiftEmphasis` says how loudly an entry is drawn — the day someone is off is what a + reader scans for, the hours they work is what they read once they have found the + person. There is deliberately no `marked(text, status)` factory that picks the + emphasis: a design that halves a day draws the halves differently, sometimes quiet + second and sometimes not, so `RotaShift.strong(...)` and `RotaShift.soft(...)` make + the caller say which. Every component normalizes `null` to its empty form and freezes + its collections, the inner day lists included, matching the family's existing records. + +- **One place turns a printed contact into a followable one: + `core.identity.ContactUri`.** The rule that a printed telephone number and the number + a device dials are different strings had three named homes — one in the CV presets, one + in the invoice presets, one in the proposal presets — byte-identical where they + overlapped. They are now one helper in the family-neutral identity layer, beside the + contact-block and link records it serves, with `tel` / `telLink` / `mailLink` / + `web` / `webLink` / `channelLink`. Thirteen further copies of the same rule, written + inline inside the presets rather than named, are swept onto it under *Fixed* below. +

+ It is public because the three families are three packages and a helper shared between + them cannot be package-private; it is not marked experimental because the shape is not + a guess — sixteen independent implementations had already agreed on it. The link forms + answer `null` rather than throwing, including where the string cannot be a URI at all: + a notice, a name or a line of prose can reach a channel field, and losing the + affordance is not worth failing to compose the page over. `tel` and `web` are the + target forms behind `telLink` and `webLink`, for the call sites that take a plain + string; a target is not yet a link, so it is the link forms that carry that guarantee. + Nothing renders differently — a link annotation is not ink, and every pixel baseline in + the suite passes untouched. + - **Text style carries typographic tracking.** `DocumentTextStyle.builder().letterSpacing(...)` takes a `DocumentLetterSpacing` — either `ofFontSize(0.12)`, a share of the font size, or `points(1.2)`, an absolute amount. Negative values tighten. The unit lives in the value @@ -119,6 +342,84 @@ follow semantic versioning; release dates are ISO 8601. it was measured and rejected: a reserved-column approximation renders a gap that is not the one you asked for, and misaligns outright for a marker wider than the column. +- **A list item can be styled in pieces.** `ListBuilder.addItem(Consumer)` takes + the same inline runs a paragraph is made of, so `"Status: pending"` with the label bold + is one list item; `addItem(Consumer, Consumer)` lets a styled label + head a sub-tree. `addItem("plain string")` is unchanged and remains the short form. + + An item was a string in one style, so a design that emphasises the opening words of each + bullet had to be built as a two-column row per item — which stops being a list. The + marker becomes a table cell, the wrapped lines are the column's business rather than the + item's, and nothing about it paginates as one thing. + + Runs, not a second rich-text model: the same `RichText` builder `ParagraphBuilder.rich` + takes, so a chip, an icon, a link or a coloured span inside an item is whatever it already + is inside a paragraph. An item is content whatever its runs draw, which is why a row of an + icon and no text is still a row — it reads as the empty string, exactly as an item that + draws nothing does, and the runs are what separate the two. + `InlineRun.plainText(runs)` is that reading and `InlineRun.textRuns(runs)` its styled + form; a paragraph's own `text()` and `inlineTextRuns()` now come from those two, so which + kinds of run read as text is stated once instead of once per surface. + + The geometry is the marker column's, unchanged: the marker is measured, `markerGap` + applies, and the first line, the lines it wraps onto and the lines that continue on the + next page all start at one x, with the marker drawn once. Which means `hangingIndent(true)` + is required — the older layout makes the marker part of the item's text and so carries one + style for the whole item, and rendering the plain reading instead would drop silently + every style, icon and chip the author asked for. A rich item in a list that has not opted + in says so, and names the call. + + A rich item does not change what its list's marker is. Runs do not fit a list of labels, + so the list carries an item tree instead — and that is a change of representation, not of + markers: a dashed flat list still dashes at its top level when one of its items needs + styling. A list whose author declared depth is unaffected and still resolves every level + from the per-depth cascade and `markerFor(...)`. + + **The semantic DOCX export carries this one**, unlike the geometry above it: Word holds a + style per run inside a paragraph, so a rich item writes one Word run per authored run — + the marker leading in the list's own style — and image, shape and SVG runs drop with the + same one-per-kind warning a rich paragraph's do. Because the semantic export lays nothing + out, it is indifferent to `hangingIndent` and needs no opt-in to carry the runs. + +- **A list marker can be drawn, and can carry a colour of its own.** + `ListBuilder.marker(Consumer)` takes the marker as inline runs, so + `m -> m.dot(4, ACCENT)` is a coloured disc, `m -> m.color("•", ACCENT)` is an accent + bullet beside near-black copy, and `m -> m.svgIcon(icon, 8)` is an icon. + `marker(String)` is unchanged and remains the simple form. + + The marker's one span was built in the list's own text style, so a design with an accent + mark and dark text could not be written as a list at all. What the designs that wanted it + did instead was a paragraph per item — the mark, then a run of spaces standing in for the + gap, then the text — which buys the mark and loses the column. Measured on that + construction at a width that wraps: the first line's text starts at 25.246 and every line + after it at 12.000, because a paragraph has no marker column to hang under. The spaces are + a second cost. Rounded to a whole count and then measured at each item's own type size, + one declared 9.175pt gap came out as 9.308pt in one column of a real CV and 8.356pt in + another — the same constant, two gaps. + + A drawn marker is measured as what it draws: a disc's column is its diameter, an icon's is + its box. Nothing is counted in characters and nothing is approximated, so `markerGap` is + points of real space after it and `contentX` is `markerX + width + gap` exactly. It is + measured once per distinct marker for the whole list, through the same pipeline that + measures the items' own content — which is also why a marker can be anything an inline run + can be, and why no renderer needed a new branch to draw one. + + Everything the marker column already promised holds: every visual line of an item starts at + one x, the marker is drawn once when an item continues onto later pages, a child's marker + starts where its parent's text does, and the marker rides the item's first baseline without + making the row taller. A marker drawn larger than that line overflows it, which is the + answer a marker wider than its column already gets. + + Because the marker column is what makes this possible, `hangingIndent(true)` is required + and the layout says so if it is missing. A drawn marker's plain reading is empty, so the + older layout — where the marker is characters at the front of the item's text — would render + the list with no marker at all and no signal. + + **In the semantic DOCX export** a marker written as text keeps the colour and face it was + given, because a run's style is something Word holds. A marker that draws a disc or an icon + has no Word analogue, so it drops with the export's usual one-per-kind warning and its item + is written unmarked, rather than substituting a glyph nobody asked for. + - **A timeline's rail is one line, drawn from where its markers landed.** It was a left border repeated on every entry section, which is why it sat at the entry's edge whatever the markers did, could not stop short of them, and had no way to be @@ -126,19 +427,30 @@ follow semantic versioning; release dates are ISO 8601. markers' and entries' resolved positions, and contributed as one fragment per page — one logical rail, however many pages it crosses, bounded on each by that page alone. - Two independent choices, and they stay independent. `TimelineRailExtent` says how far the - rail runs: `ENTRY_BOUNDS`, the default and what every existing timeline already draws, or - `MARKER_TO_MARKER`, which starts at the first marker and stops at the last. - `markerOnRail()` says where it runs: it aligns every marker's declared anchor with the - timeline axis. With the current centre anchor, markers of different sizes are centred - within the axis column and share one continuous rail — a 6pt dot, a 14pt numbered disc - and a 24pt square all sit on the same line rather than on the same left edge. An entry's - body moves with them into the content column beside the marker, so the line is left with - only markers to cross — see *Fixed* below. A timeline that does not call it keeps the - left-edge anchor and the placement it has always had. - A timeline with one entry and `MARKER_TO_MARKER` emits no rail at all rather than a line - of no length. `TIMELINE_BOUNDS` is named and rejected — on one page it is the same line - as `ENTRY_BOUNDS`, and across pages there is nothing to measure it against. + Two independent choices, and they stay independent. `rail(r -> r.from(...).to(...))` says + how far the rail runs, one end at a time: each is a `TimelineRailEnd`, either + `ENTRY_BOUND` — the entries' own bound, the default, and what every existing timeline + already draws — or `MARKER`, the marker's anchor point. `markerOnRail()` says where it + runs: it aligns every marker's declared anchor with the timeline axis. With the current + centre anchor, markers of different sizes are centred within the axis column and share one + continuous rail — a 6pt dot, a 14pt numbered disc and a 24pt square all sit on the same + line rather than on the same left edge. An entry's body moves with them into the content + column beside the marker, so the line is left with only markers to cross — see *Fixed* + below. A timeline that does not call it keeps the left-edge anchor and the placement it has + always had. + + **The two ends are chosen separately because a design that wants them the same is only one + of four.** A sidebar whose line begins at the first dot and carries on past the last one to + close the block is a real design, and with a single value naming both ends it could not be + asked for: `ENTRY_BOUND` at both ends drew a stub above the first dot, and `MARKER` at both + cut the tail the block draws on purpose. The preset that wanted it painted over the stub + with the page colour and redrew the line below — and measured, that did not even work, + because an accent draws above the mask. It now says `from(MARKER).to(ENTRY_BOUND)`, which + is 4.650pt shorter at the top than the first and 26.690pt longer at the foot than the + second. + + A timeline with one entry and both ends on the marker emits no rail at all rather than a + line of no length. **A leading column sits to the left of the timeline axis; it does not move the rail to the entry boundary.** The layout is `LEADING | AXIS | CONTENT`, and the rail belongs to @@ -199,6 +511,26 @@ follow semantic versioning; release dates are ISO 8601. row and both align exactly. Leading content without a declared column throws too, naming the call to add, rather than inventing a width per entry. +- **The gap before a timeline's marker is its own number.** + `TimelineBuilder.leadingGap(double)` sets the space between the leading column and the + marker, leaving `markerGap` to mean what it already says it means — the gap between the + marker and the content beside it. + + With one number for both, a three-column timeline could not be asked for what it looks + like. A row spaces every pair of its columns equally, so writing `x0` for where the + columns start, `L` for the leading width and `A` for the axis, one gap `s` puts the rail + at `x0 + L + s + A/2` and the content at `x0 + L + 2s + A`. Subtract them and + `L = (rail − x0) − (s + A/2)`: the leading width is decided by where the rail and the + content sit, whatever `A` and `s` are given. A dated timeline states all three — where + the dates are, where the rail is, where the copy starts — and could have any two. + Measured on a real one, the date column came out 55.860pt against a longest date of + 57.005 and broke `2022 - Present` across two lines; padding the column only narrows what + goes in it, and widening the axis re-pins the same number. + + With the gaps separate the leading width is the caller's and the gap absorbs the + difference. Unset, `leadingGap` is `markerGap`, which is the single-gap layout exactly — + so a timeline that does not ask for it is laid out as before, snapshots included. + - **A timeline entry can fill its own content column.** `TimelineBuilder.entry(Consumer)` is a longer form of the existing `entry(marker, ...)` that takes its marker from `TimelineEntryBuilder.marker(...)` inside @@ -446,6 +778,59 @@ follow semantic versioning; release dates are ISO 8601. ### Fixed +- **The weekly-schedule board's foot is a footer.** The rule, its seal and the build + line were the last three nodes in the flow, so they were drawn wherever the board + happened to end — on the sample week, forty points above the bottom margin. They are + now a page zone, which is what they had always been describing. A zone is measured + from the foot of the sheet rather than from the margin, so the band reserves the + margin too: without that the build line printed within three millimetres of the paper + edge, inside the dead zone of most office printers, and this board is meant to be + printed. The line itself stays against the left margin, where the board's own first + column starts — it is a credit, not part of the device above it. +

+ What made the old foot look off centre was the artwork, not the seal — the seal had + always been within a five-thousandth of a point of the centre line, because equal + weights centre its slot whatever is drawn beside it. The rules were the problem: fixed + at 292pt inside slots of 382pt, and a shape narrower than its slot is drawn at the + slot's left edge, so the seal carried 98 points of air on one side and 8 on the other + while the right rule stopped 90 points short of the margin. The rule width now comes + off the page — printable width less the seal and its two gaps, halved — so both rules + reach the seal and the right one ends on the margin. The rules also sat below the + seal's middle by 2.45pt of its 13.7; they now share its centre line exactly. +

+ The seal's letter rode high in its pill, because a line box is tall enough for a + descender and a capital has none, so centring the box centres the unused space with + it. The pill's padding now carries that half descent, computed from the Times-Bold + metrics, and the letter's ink sits within 0.07pt of the pill's centre. The letter + itself is no longer the hardcoded `S`: it is the board's own initial, so a schedule + printed for one venue is not sealed with another's, and a blank brand is now rejected + rather than sealed with nothing. +

+ Being a zone rather than flow content, the foot is drawn on every page instead of once + at the end, and reserves its height on every page. The sample week is a page shorter + than that reservation, so its table is untouched; a rota long enough to run over now + carries its foot on both pages. + +- **Five designs dialled a number they had not printed.** A printed telephone number and + the number a device dials are different strings, and the conversion between them lived + in thirteen private copies — one per design. Eight of them dropped the parenthesised + trunk prefix and five did not, so a sheet printing `+44 (0)20 7946 0832` linked + `tel:+4402079460832`, a number that reaches nobody from abroad. `ConsultingInvoice` was + further out still: its copy removed spaces and nothing else, so the printed brackets + went into the target and the link was not a dialable number at all. The five are + `CharcoalGold`, `NavySidebar`, `ProfessionalSidebar` and `SerifHeadline` on the CV + side, and `ConsultingInvoice` on the invoice side. +

+ All thirteen copies are gone and every design routes through `ContactUri` — which is + what eight of them were already doing, in eight separate places: four spelling the + regex out inline and four naming a constant for it. That duplication is as much the + cost being removed as the five that got it wrong. The same sweep removes three copies + of the scheme-prefixing rule for a printed website, which `ContactUri` gains as `web`. +

+ Nothing on any page moves: a link annotation is not ink, and no fixture prints a trunk + prefix, so no pixel baseline and no layout snapshot changes — what changes is where the + link goes when a caller's own number carries one. + - **`markerOnRail()` no longer draws the rail through the entry's text.** Putting the markers on the rail moves the line into the middle of the axis column, and an entry's body spanned the whole entry — so the line was drawn straight through ordinary @@ -511,8 +896,989 @@ follow semantic versioning; release dates are ISO 8601. alike — a wrapper handled in one and missed in the other loses a subtree just as completely. PDF and PPTX were never affected; they draw what the layout produced. +### Templates + +- **A stacked row's body now hangs under its name, not a couple of points to the left of + it.** The shared bullet path every themed CV preset draws its rows through — + `RowRenderer`, `SkillsRenderer` — put the glyph inside the paragraph's text as a + `bulletOffset` and indented what followed with a run of spaces measured to clear it. + `Decoration.stackedIndent`, the two spaces under a stacked row's bold name, carries its own + contract: it "must visually occupy the same width as bulletGlyph". Measured on the + canonical document it does not, by **0.720pt to 3.078pt** depending on the theme's type + size — so the body of every Projects-style row started slightly left of the name it hangs + under. + + Each row is a list now. A bulleted row is one item, so its marker sits in a measured + column and every visual line of it starts at one content origin. A stacked row is one item + with a **markerless child**: a child's marker column opens at its parent's content origin, + and a child with no marker takes no marker width and no gap, so the body begins exactly + where the name's text begins — by construction rather than by a count of spaces. The + content is inline runs, so the bold label of a `label: body` row stays part of one item. + The gap after a bullet is a quarter of the type size, which is what the trailing space + inside `bulletGlyph` was approximating. + + Measured per preset, at the pixel gate's own canonical document. Page counts, wrapping and + vertical rhythm are unchanged everywhere — the correction is horizontal: + + | preset | pages | row text origin | stacked body → name | pixels (of 500 395) | + |---|---|---|---|---| + | `blue_banner` | 2 → 2 | 38.437 → 38.391 | own path, unchanged | 7 422, maxΔ 12 | + | `boxed_sections` | 2 → 2 | 38.003 → 38.063 | 36.180 → 38.338 (**+2.158**) | 26 085, maxΔ 201 | + | `centered_headline` | 2 → 2 | 35.273 → 35.221 | own path, unchanged | 8 709, maxΔ 12 | + | `executive` | 2 → 2 | 35.819 → 35.885 | 32.864 → 35.885 (**+3.021**) | 29 553, maxΔ 202 | + | `minimal_underlined` | 2 → 2 | 42.003 → 42.063 | 40.180 → 42.338 (**+2.158**) | 26 300, maxΔ 202 | + | `modern_professional` | 2 → 2 | 36.280 → 36.000 | 35.560 → 36.000 (**+0.440**) | 32 165, maxΔ 98 | + | `panel` | 1 → 1 | 33.858 → 33.802 | 313.451 → 316.440 (**+2.989**) | 29 990, maxΔ 201 | + + The row text origin moves by at most 0.280pt, which is the difference between a quarter of + the type size and the space it replaces. Every multi-line count is identical before and + after, and so is every page count; the only structural change is one extra fragment per row, + which is the marker's own. The seven pixel baselines and seven committed previews are + re-recorded, with all 126 baselines and all 118 previews hashed either side: exactly seven + of each moved. `engineering_resume`'s baseline differs from its render by 306 pixels for a + reason that predates this work — verified by measuring it against the unmodified code — so + it is left alone, as are the four preview drifts under separate investigation. + +- **A long degree title no longer draws over its own institution line on Professional + Sidebar.** The title sat in a container of exactly `ENTRY_HEAD_HEIGHT`, centred, which put + its centre on the rail dot's — and works for a title of one line and no other. Measured + with a real degree, "MSc Advanced Computer Science and Software Engineering", the title + needed three lines in a sidebar that narrow and the box stayed 9.3pt tall: under + `OVERFLOW_VISIBLE` the extra lines drew rather than vanished, putting the title's foot + **16.840pt below its own institution line**, across that line and the dates under it. + + A declared box that stays its declared size is the engine doing as it was told, so the fix + is where the box was declared. The title is now the entry's own paragraph with the band's + surplus over one line as air above and below it — `EDUCATION_DEGREE_AIR`, declared as air + rather than left implicit in a height, so a title needing two lines gets two lines with the + same air around them. **Nothing about the canonical sheet moves:** the paragraph occupies + the same 9.3pt, its centre stays on the dot's, and its wrap width is the same number, + because the container's declared width and the timeline's content column resolve to the + same expression. Zero named nodes move in the layout snapshot; the two + `EducationHead_*` wrapper nodes are gone with the containers, and the pixel baseline and + the committed preview are untouched. + +- **Orange Ops' skills are a real list.** Each name was a paragraph opened by an accent + dot, because a list marker used to take the list's own text colour and these dots are + accent against charcoal. The gap between dot and name was four literal spaces, which is a + measurement of the face rather than a distance the design states; it is now + `SKILL_BULLET_GAP`, the nearest round value in the sheet's own unit, and the dot is the + list's marker. Measured: **one named node moves, by `placementWidth` +0.067pt** — the + difference between those four spaces and the declared gap — with no x, no y, no page + ownership, still one page. 4 784 pixels of 500 395 change at zero tolerance, maxDelta 15, + so the pixel gate never noticed; the baseline is re-recorded anyway, with all 126 hashed + either side to prove only this one moved. Eleven `Skill*` paragraph nodes become one + `SkillNames` list node. + +- **Teal Pulse's dotted lines are a real list, so its one declared gap is the gap it + draws.** The sheet's competencies, experience highlights and certifications were a + paragraph per label: a teal dot, then a run of spaces standing in for the gap, then the + text. `TealPulseStyles.DOT_GAP` declares that gap as `px(17)` = 9.175pt, and the spaces + could not deliver it — the count is rounded to a whole number of them and then measured at + each label's own type size, so one constant rendered as **9.308pt** beside the + competencies, **8.356pt** beside the highlights and **8.806pt** in the closing band. + Wrapped labels were the larger cost: with no marker column to hang under, every line after + the first returned to the label's own left edge. + + Each block is now one list with the dot as its marker and `DOT_GAP` as `markerGap`, so the + gap is that number everywhere and every line of a wrapped label starts where its first + line's text does. Measured: **7 named nodes moved and every one is a `placementWidth`** — + the sidebar 0.133pt narrower, the three highlight blocks 0.819pt wider, the certifications + column 0.369pt wider, each exactly its own gap correction. No x, no y, no page ownership, + still one page; the blocks are content-sized boxes, so the correction is all that moves + them. 22 883 pixels of 457 555 change at zero tolerance (maxDelta 192), inside the 50 000 + budget the gate already allowed; the baseline and the committed preview are re-recorded for + it, and all 126 baselines were hashed either side to prove only Teal Pulse's moved. + + The rows are one list node per block rather than one paragraph node per label, so the + layout snapshot has 96 named nodes where it had 121, and the per-label names + (`Competency_*`, `Highlight_*`, `Certification_*`) are gone with the paragraphs that + carried them. + +- **New `receipt` family — payment confirmations.** The fifth family on the layered + architecture, for the document a bank sends after money moves: a transfer + confirmation, a direct debit advice, a card receipt. `ReceiptDocumentSpec` carries + the amount, the two parties, titled groups of label/value rows, the steps the + payment went through, and the footer small print; `ModernReceipt` sequences them + over `BrandTheme.receiptModern()`. + + It is laid out as a receipt rather than as an invoice without line items. The + amount is set larger than the document title, because on a receipt the amount is + the headline; the status carries a chip coloured by tone — settled green, failed + red — in every theme, so a reader checking whether money arrived does not have to + learn a colour scheme per issuer; the two parties share one panel with a direction + arrow instead of sitting in two tables the reader has to relate; detail rows are + joined by a dotted leader, the same construction the table-of-contents builder + uses, because across a full page width a bare label and a bare value read as two + unrelated columns. The verification QR code and the small print are seated on the + bottom margin: the engine has no vertical flex, so the preset measures what the + body left on the last page and spends it — and only when the receipt owns the + session, since the measurement costs a second composition pass. + + The theme carries no brand colour at all. An issuer's mark and accent arrive per + document through `ModernReceipt.Options`, so one preset and one theme render every + institution rather than one theme per institution. + +- **`SvgGlyph.fromFile(Path)`.** The classpath variant covers glyphs a template ships + with; this covers the glyph a template is given — a receipt's issuer mark, a + report's client logo — which arrives as a file beside the running application + rather than repackaged into its jar. + +- **The invoice presets set their page number in their own face.** A header or footer + zone draws in the standard-14 face unless a document says otherwise, and until the + zone gained `fontName` there was no way to say otherwise — so `PaymentsInvoice` and + `WorkspaceInvoice` left the page number as the one line on an otherwise Lato sheet set + in Helvetica. Both now name the sheet's own face. The change is confined to that line: + measured at 600 dpi it moves 3 885 pixels of 34 794 400 on one sheet and 3 898 on the + other, all of them inside the number's own box, and nothing around it shifts. +- **Seven CV presets make their telephone number dialable.** `EngineeringResume`, + `Executive`, `MintEditorial`, `MonogramSidebar`, `Panel`, `SidebarPortrait` and + `TimelineMinimal` already made an email and a profile reachable and printed the phone + as plain text, so a reader on a phone or a tablet could tap every contact on the sheet + except the one they would actually ring. Each now carries a `tel:` target built the + way the rest of the library builds it — a parenthesised trunk prefix is dropped, + because it is the digit a caller omits from abroad, while a parenthesised area code is + kept. `ContactUri` is the one place that conversion now lives. +

+ Two of the seven printed the address and the number as one joined string, which cannot + carry a target on half of itself; they now set the two as separate runs of the same + paragraph, with the same glyphs in the same order. Nothing moves: every pixel baseline + in the suite passes untouched, because a link annotation is not ink. +

+ A number inside a referee's free-form prose stays plain. An address in running text is + recognisable; a number is not — dates, reference numbers and amounts all look like + one — so only the structured contact field is linked. + +- **A commerce invoice preset: `MerchantInvoice`.** A lockup against the title over a + short accent rule, the supplier's details beside the invoice's metadata, two addressed + parties on filled discs, a line-item table whose rows are bordered boxes, a bank panel + beside the totals and the due-by card, a closing note, and an identity band closing the + sheet on a marked tile. Ships as `invoice.presets.MerchantInvoice` on the existing + `StructuredInvoiceData` model, porting the rendered layout of a published standalone + template: rendered against the frozen original with the same data the text layers are + **identical, all 225 words in the same place**, and the sheet above the identity band + carries **no differing pixel**. What differs is the two marks — the masthead lockup and + the band's tile, both the caller's — the enumeration's face, and the social row below. +

+ **The social row is deliberately not ported.** The design closes with three discs + carrying social-platform marks. Those marks belong to their platforms, and the templates + artifact does not redistribute other companies' trademarks, so neither they nor the rule + that divided them off are drawn; the band keeps the tile and the issuer's identity, which + is what identifies the sheet. This is stated on the preset rather than left to be + noticed. +

+ Each table row is one cell. A cell's stroke draws all four of its edges, which is + exactly what this design's rows are — a soft box round each, no interior verticals — so + a row is a single cell spanning every column with the five columns composed inside it. +

+ The supplier's contact rows are the one block whose line box is not its type: an inline + mark taller than the text sets the box, so a pitch measured from the text and solved + against the text lands short by the difference. Those pitches are solved against the + mark. A quantity of nothing prints the design's own dash rather than a zero, because a + zero in a quantity column reads as none delivered rather than as not counted. Guarded by + a smoke test (including the dash, the unknown-mark data error, a line with no mark, an + invoice that ships nowhere, where the currency is stated, and pagination), an exact + layout snapshot, and a pixel baseline — all three fed by one fixture. + +- **A dark invoice preset: `ObsidianInvoice`.** The first dark sheet in the library: a + wordmark over the document's name against a ruled metadata stack, the issuer and the + billed party on filled discs in cards of their own, a line-item table in a third, the + totals in a fourth, the notes and payment details side by side in a fifth and sixth, + and a closing band under a hairline. Ships as `invoice.presets.ObsidianInvoice` on the + existing `StructuredInvoiceData` model, porting the rendered layout of a published + standalone template: rendered against the frozen original with the same data, every + card, the table, the totals and both information cards are **pixel-identical**, and the + only regions that differ are the two discs — which carry the caller's mark where the + original carried its own — and the enumeration, which sets the sheet's own face. +

+ The fill is set on the page rather than on the flow. A container's fill is bounded by + its content height, so a continuation page carrying three line items would show two + thirds of a white sheet under them; the page background covers the paper whatever the + content does. +

+ Sizes are solved from ink rather than from cap height. The design's own face is not + bundled and matching a cap height alone would still set every string to the wrong + width, so each size is solved from the measured ink width of the string it sets, with + the substitute's width ratio applied once — and every size is named, in the styles, by + the string it came from. Vertical positions are cap tops rather than box edges, because + ink is what a design can be measured on. +

+ The tax column is worked out rather than asked for. This design gives the tax its own + money column where the model carries a rate, and the figure is not a second thing to + state: a line already gives the quantity, the unit price and the total it comes to, so + the tax is the difference between what was charged and what the goods cost. Reading it + off the line is also what keeps the column and the total consistent when either moves. +

+ The issuer's disc carries the caller's logo when there is one, the brand's monogram when + it states one, and initials taken from the name otherwise; the billed party's always + shows initials taken from its name. Guarded by a smoke test (including the derived tax + and its zero-rated case, both disc fallbacks, the due sentence keeping its accented date + inside one sentence, and pagination), an exact layout snapshot, and a pixel baseline — + all three fed by one fixture. + +- **A per-seat subscription invoice preset: `SubscriptionInvoice`.** A lockup against the + title, the supplier's details beside a metadata panel whose rows each open with a + coloured bar, two addressed parties under coloured underlines, a six-column table + carrying a tax rate on every line, the notes beside the totals, a marked payment band, + a four-segment strip, and a closing band that bleeds to three paper edges. Ships as + `invoice.presets.SubscriptionInvoice` on the existing `StructuredInvoiceData` model, + porting the rendered layout of a published standalone template — and this one is exact: + rendered against the frozen original with the same data the text layers are + **identical, all 218 words in the same place**, and the body carries **no differing + pixel at all**. Only two things differ, both by design: the masthead mark, which the + preset leaves to the caller, and the enumeration, which sets the sheet's own face where + the original took the zone default. +

+ It is the first preset in the family to draw `InvoiceServiceLines.Line.vatRate()` and + its caption — it bills in a jurisdiction that prints the rate per line rather than only + as a total — and the first to number its lines, from `lineNumber()` when a line states + one and from its position when it does not. +

+ Colour is by position and never by meaning: the metadata bars, the party underlines and + the closing strip all draw from one four-colour cycle indexed by where a thing sits, so + the fourth metadata row is amber because it is fourth and nothing in the document ever + names a colour. The page carries no side margins, because the strip and the closing band + have to reach the paper edges and do it by being the only blocks without horizontal + padding; the band reaches the bottom edge by bleeding rather than by being last, which + is what lets the enumeration sit over its fill for nothing. +

+ Money is written with the currency's mark against the digits, so every figure names its + own currency and no column states one — which is also the preset's one boundary, and it + is documented and tested rather than left to be discovered: the columns are measured for + a one-character mark, so a currency the runtime knows only by its three-letter code does + not fit the unit-price column and the render is refused there rather than letting the + figure run under its neighbour. Guarded by a smoke test (including the per-line rate, + line numbering from both sources, the currency boundary, a payment band with a different + cell count, an invoice that ships nowhere, and pagination), an exact layout snapshot, + and a pixel baseline — all three fed by one fixture. + +- **A region-billed platform invoice preset: `PlatformInvoice`.** A lockup against the + title, the supplier split from the invoice's own metadata by a full-height hairline, + two addressed parties on differently-treated marks, a six-column usage table, an + outlined bank panel beside the totals and the due-date card, and an identity band + closing the sheet. Ships as `invoice.presets.PlatformInvoice` on the existing + `StructuredInvoiceData` model, porting the rendered layout of a published standalone + template: rendered against the frozen original with the same data, the document body + carries **no ink displaced at all** — every one of 225 paired words within 0.15 pt, + and zero pixels differing by more than a rounding step at 150 dpi between the masthead + and the closing note. The regions that differ are the two lockups, which the preset + leaves to the caller, the enumeration, which sets the sheet's own face where the + original took the zone default, and one phrase in the note. +

+ This is the design that needed `Line.region`: it bills the same service in more than + one place and prints where beside what, as its own column between the service and the + usage. +

+ Two scales, on purpose. The design is 1.50 aspect where A4 is 1.414, so one conversion + constant cannot serve both axes: widths, x-offsets and type sizes go through the + horizontal one because that is where advance widths have to fit, heights and pitches + through the vertical one, which is 6.3% tighter, and marks and discs through their + mean — about 3% out in each axis rather than 6% out in one. Writing a vertical + measurement on the horizontal scale would stretch the sheet by 6.3% with nothing + failing, which is what the split exists to make visible. The design's line pitches are + set cap-top to cap-top, so the preset solves each gap from Barlow's own cap height and + ascender rather than from the type size. +

+ The table's separators are rows. A table's rules come from each cell's own style and + cover all four of that cell's edges, so any stroke that buys a horizontal separator + also buys five verticals — which this design has nowhere inside its table. Every cell + is stroked at zero width and each separator is a row of its own: one cell spanning + every column, zero padding, and a full-width line as its content. +

+ A unit price is written at the precision it is quoted at, between two places and four. + Usage that names a unit carries two places even on a whole number, because the meter + reads to that precision; usage that names none is a count of whole things and is + written bare. The currency is stated once per money column with the figures under it + bare, and once more on the grand total, which stands under no caption. Guarded by a + smoke test (including the region column, the unknown-mark data error, a line with no + mark, an invoice that ships nowhere, both figure rules, and pagination bringing the + header back), an exact layout snapshot, and a pixel baseline — all three fed by one + fixture. + +- **A metered-usage invoice preset: `MeteredInvoice`.** An orange-accented masthead over + its own rule, the supplier split from the invoice's metadata by a hairline, two + addressed parties, a service-line table whose marks sit on bordered tiles, a closing + row pairing the bank details against the totals and the due date, and a full-bleed dark + band along the foot of every page. Ships as `invoice.presets.MeteredInvoice` on the + existing `StructuredInvoiceData` model, porting the rendered layout of a published + standalone template: rendered against the frozen original with the same data, the + document body is pixel-identical — 0 of 2,173,720 at 150 dpi between the title and the + footer band — and the two regions that differ are the masthead lockup, which the preset + leaves to the caller, and the band, which sets the sheet's own face where the original + took the zone default. It flows: the table's header repeats on every page it reaches, + the bottom margin reserves the band on every page so no row runs into the chrome, and + every page carries its number. +

+ A unit price is written at the precision it is quoted at, between two places and four, + where every other figure on the sheet is written at two. That is not a formatting + preference: a metered rate is a fraction of a currency unit, and an hour of compute at + 0.0710 rounded to an amount's two places is 0.07 — a different price that multiplies + out to a different bill. Figures state their locale rather than inheriting it. +

+ The design's side margins are unequal, 35.5 px against 43.5 px of a 1055 px sheet, and + are kept as measured: regularising them at the mean costs four pixels of horizontal + error on every glyph to save eight on one edge. Its hairlines are a pixel of ink rather + than a pixel of geometry, so they are drawn at the weight that reproduces them instead + of the 0.6 pt they measure, which rasterises to less than a pixel and drops half the + row rules to partial opacity. The table is one column wide with a composed row inside + each cell, because a cell strokes all four of its own edges and five real columns would + draw four interior verticals the design does not have. +

+ Guarded by a smoke test (including the unknown-mark data error, a line that names no + mark, the rate-versus-amount precision rule, pagination bringing the header back with + it, and the disclosure line falling back to the supplier's name), an exact layout + snapshot, and a pixel baseline — both fed by one fixture. + +- **A violet SaaS invoice preset: `WorkspaceInvoice`.** A brand masthead over a short + accent bar, a half-split issuer and metadata header, two addressed parties on filled + discs, a service-line table whose marks sit on coloured tiles, a settlement row + pairing the bank details against the totals, and a closing band carrying the wordmark. + Ships as `invoice.presets.WorkspaceInvoice` on the existing `StructuredInvoiceData` + model, porting the rendered layout of a published standalone template. Like + `PaymentsInvoice` it flows — the table's header repeats on every page it reaches, a + continuation page reserves a deeper bottom margin, and every page carries its number, + which is a departure from the one-page design and the only way a lost page is + detectable. The sheet's own closing band cannot be page chrome, because a header + footer zone takes only strings and the band holds a wordmark and a link, so it is body + content and the number is the only chrome. Two of the design's own type corrections + are carried as measured constants rather than smoothed away: a cap read off a + screenshot includes half a pixel of antialiasing on each edge, and the body face sets + uppercase about 6% wider than the design's at the same cap height — measured across + three all-caps runs while mixed-case runs at the same sizes matched within 2%. This + design states its currency once per money column and writes the figures under it bare, + carrying the code only on the total, which is the opposite of what `PaymentsInvoice` + does and is why the two presets format money differently. A quantity is written with + what it counts. Both parties print a registration under their address through the pair + `InvoiceRecipient` gained for it, and a party with no number prints no label. Guarded + by a smoke test (including the unknown-mark data error, a line with no mark, the + wordmark fallback, the currency named once and not on every figure, the quantity with + its unit, both registrations and the absent one, the closing address as an annotation, + a missing ship-to, a payment card with no note, an empty document, and a thirty-line + invoice that runs on, repeats its column names and numbers its pages), an exact layout + snapshot and a pixel-parity gate; the examples showcase gains `invoice-workspace-v2`. + +- **A paginating invoice preset: `PaymentsInvoice`.** A lavender-and-navy sheet with a + diagonal band crossing the masthead, a half-split issuer and metadata header, two + addressed parties on discs, a marked service-line table, a settlement row pairing bank + details against the totals, a note block and a two-cell document footer. Ships as + `invoice.presets.PaymentsInvoice` on the existing `StructuredInvoiceData` model, + porting the rendered layout of a published standalone template. It is the first preset + in the family built to **flow**: the design shows six service lines and a real billing + month brings dozens, so the table's header repeats on every page it reaches, a + continuation page reserves a deeper bottom margin than page one, and every page + carries its number — a financial record that runs over has to make a missing page + detectable. The table is one column, not five: the design shows an outer box and a + rule between rows with no interior verticals, which is exactly what a single-column + table draws, and the five columns are a row inside each cell. Building it the other + way round would leave every continuation page ending in an empty bordered strip, + because a section's box fills its page fragment rather than hugging its rows. The + lockup beside the title is the caller's: the design's mark occupies a measured + 136 × 55.3 px box, a document that brings a logo has it drawn to that height, and one + that brings only a name has the name set as a wordmark — the templates artifact + carries no mark of its own. Figures are written with the locale stated rather than + inherited, because both the grouping of a number and the symbol for a currency code + change with it and a preset that let the JVM decide would render a different sheet on + a different machine. Guarded by a smoke test (including the unknown-mark data error, a + line with no mark, the wordmark fallback, the currency named once and carried, the + uppercased due line, a trunk prefix printed but not dialled, the support contacts as + annotations, a missing ship-to and a missing note block, an empty document, and a + thirty-line invoice that runs on, repeats its column names and numbers its pages), an + exact layout snapshot and a pixel-parity gate; the examples showcase gains + `invoice-payments-v2`. + +- **A navy-plate CV preset: `MidnightNavy`.** A one-page sheet on a full-height navy + plate: an outlined monogram over a two-weight name and a tracked role line, then + contact, education, metered skills and dotted languages down the plate, beside a + paper column carrying the summary, the roles held on a rail, three achievement discs + and the certifications in divided columns. Ships as `cv.presets.MidnightNavy` on the + existing `CvDocument` model, porting the rendered layout of a published standalone + template; it needed no model change. The plate is a page background rather than a + section fill — it reaches three paper edges and a fill stops at its own box — sized + by the same ratio the body row splits on, so the two cannot drift apart. Every + horizontal pair goes through one wrapper: a row nested directly in a row cell is + refused and both columns are cells, so a skill and its meter, a language and its + dots, a title and its dates, a disc and its line and the certification columns are + each a row wrapped in a single layer of a stack. Three marks are relationships rather + than lengths: the experience rail is the entry section's left accent, so its height + derives from the entry and the inter-entry gap is padding inside the border, which is + what makes consecutive rails meet; a certification divider is the column's own + accent; and a skill meter is three layers sharing the track's axis rather than three + pieces placed apart. The monogram and the role line are built from the identity — two + initials and the name's own words — so a document fills neither in twice. Seven + berths reach their sections by title, and the contact heading is the preset's own + because a document has no section to carry it. Unlike its ported siblings this sheet + is one page **strictly**: the body is a single row and a row is atomic, so a longer CV + is refused with an `AtomicNodeTooLargeException` naming the node rather than being cut + — splitting the row would leave the plate on one page and half the aside on the next. + Two things depart from the ported sheet deliberately, and both are measured. A link is + drawn as its own label with the address behind it, as on the presets before it. And + every rail marker is centred on its rail: an accent is drawn centred on the edge it + belongs to, so the section's left edge already is the rail's axis, and the ported + sheet's extra half-thickness correction put each marker a rail width to the right of + the line it sits on — 0.72 pt, visible at reading size. Together the two come to + 2 498 of 2 173 720 pixels, of which the centring is 200. Guarded by a smoke test (including the unknown-mark data error, the + monogram taken from the name, the uppercased name and tracked role, a language rating + rounded to the nearest of five dots, a skill with no level, a trunk prefix left + undialled, the link targets on every kind of title, a dropped berth, a document with + nothing but an identity, and the refusal past one page), an exact layout snapshot and + a pixel-parity gate; the examples showcase gains `cv-midnight-navy-v2`. + +- **A two-column operations CV preset: `OrangeOps`.** A one-page sheet in three bands + over a split body: a two-tone name above a dark role bar whose right edge and three + accent slashes all lean by one ratio, a contact strip whose items sit on one axis + between hairlines, then a narrow column of dotted skills, achievement discs, a degree + and certifications beside a wide column carrying the profile, the roles held with + their dates on the right margin, a four-metric strip on full-height rules, and the + closing lines. Ships as `cv.presets.OrangeOps` on the existing `CvDocument` model, + porting the rendered layout of a published standalone template. It is the first + preset whose display family the engine does not carry: Oswald is not one of the + families `graph-compose-fonts` ships, so the preset names it through + `OrangeOps.DISPLAY_FONT` and the caller registers it — the gate and the example each + register the two faces from their own module's resources, and the class documentation + shows the call. Eight berths reach their sections by title, four to a column, and a + berth nobody fills takes its heading, its accent rule and its join hairline with it + rather than leaving a rule over a gap. Two shapes are what the design shows rather + than what a row could hold: the role bar is a layer stack because the plate and the + slashes overlap horizontally, and an achievement is a layer stack because a table cell + anchors its disc to the foot of the pair instead of the top. A badge is one leaf, not + two — the disc is re-emitted around the glyph inside a widened viewBox, because a + shape container inside a table cell reserves its box and composes none of its + children. A heading's parenthetical is set smaller on the same line, split off at the + title's first bracket, which is how `KEY KPI SNAPSHOT (Recent 12 Months)` reaches the + page as the design sets it. As on the presets before it a link is drawn as its own + label with the address behind it, the one deliberate departure from the ported sheet, + measured at 7 241 of 2 173 720 pixels. Guarded by a smoke test (including the unknown + mark data error, an entry with no mark, the uppercased name and role bar, the + parenthetical heading, a trunk prefix left undialled, the link targets on every kind + of title, a dropped berth taking its join rule with it, a document with nothing but an + identity, and the run onto a second page), an exact layout snapshot and a pixel-parity + gate; the examples showcase gains `cv-orange-ops-v2`. + +- **A banded single-column CV preset: `VioletGrid`.** A one-page sheet with no page-level + grid at all: a two-tone name beside the contact list, three opening lines, a six-up + grid of marked skills divided by dotted rules, a strip of tools on inline discs, the + roles held on a dated timeline whose dates sit outside the rail, the projects behind + tinted tiles, education and languages side by side, and a tinted band closing on a + quotation. Ships as `cv.presets.VioletGrid` on the existing `CvDocument` model, + porting the rendered layout of a published standalone template; like `SlateOrange` it + reads a rated language's wording from `CvSkill.note`. Every split is local to one + band — the masthead splits where the contact marks begin, the timeline into dates, + rail and content, a project into tile and copy, the credentials into two halves — and + none of the four knows about the others, which is why every horizontal arrangement is + either a top-level row or a table and none of them nests. Three marks are + relationships rather than lengths: a section rule starts where its heading ends + because it is a weighted column taking what an auto column leaves; the timeline rail + is the left border of every entry but the last, so consecutive entries butt into one + line; and a project's hairline is its copy's left border, so it is exactly as tall as + the copy. Seven berths reach their sections by title, including a quotation berth + whose body is drawn and whose title is not. Unlike its ported siblings this sheet + flows: each experience entry is held together, so a longer CV runs onto a second page + rather than cutting a role in half. As on the presets before it a link is drawn as its + own label with the address behind it, the one deliberate departure from the ported + sheet, measured at 1 882 of 2 173 720 pixels. Guarded by a smoke test (including the + unknown-mark data error, a skill with no mark, the two-tone name, the quotation's + title staying off the sheet, the link targets on every kind of title, an identity with + no links, a document with nothing but an identity, and the run onto a second page), an + exact layout snapshot and a pixel-parity gate; the examples showcase gains + `cv-violet-grid-v2`. + +- **A masthead-and-rail CV preset: `SlateOrange`.** A one-page sheet built as a + full-bleed slate band over a two-column body: an orange monogram tile beside the name, + the role line and a tracked specialism strip, with the contact lines across an orange + hairline; then a narrow column of marked competencies, trophied achievements, rated + languages and closing facts, beside a wide column carrying the profile, the roles held + on a rail, and a credentials footer of education and certifications. Ships as + `cv.presets.SlateOrange` on the existing `CvDocument` model, porting the rendered + layout of a published standalone template; it is the first preset to use + `CvSkill.note`, which its language rows need to show a rating and name it on the same + line. The four fills that reach a paper edge — the slate band, the orange tile and the + two column dividers — are page backgrounds rather than section fills, because a fill + on a section is bounded by its content. Every horizontal pair inside a body column is + a table with fixed widths: a row cannot nest in a row cell and both columns are cells, + so mark and label, language and rating, role and dates, and the two credential columns + are all tables, and where a cell needs several stacked lines it holds a single-column + table of its own. Seven berths reach their sections by title, including a specialism + berth whose body is drawn and whose title is not. As on the presets before it, a link + is drawn as its own label with the address behind it — the one deliberate departure + from the ported sheet, measured at 2 100 of 2 173 720 pixels — and a role or a degree + becomes a link when its entry carries one. Guarded by a smoke test (including the + unknown-mark data error, an achievement with no mark falling back to the trophy, a + language with and without its wording, the monogram and the strip, the link targets, + an identity with no links, a document with nothing but an identity, and the refusal of + a CV taller than the sheet), an exact layout snapshot and a pixel-parity gate; the + examples showcase gains `cv-slate-orange-v2`. + +- **A clinical CV preset in five bands: `TealPulse`.** A one-page sheet whose mark is a + heart crossed by a flat pulse, beside a letter-spaced name over a contact strip + divided by short rules; then a two-column body carrying the competencies as dotted + lines beside the summary and the roles under badged headings; then a three-column + closing band for the degree, the certifications and the facts; and a tracked line + under a rule that ends in a small heart. Ships as `cv.presets.TealPulse` on the + existing `CvDocument` model with **no model change at all**, porting the rendered + layout of a published standalone template. **It sets its own page**, unlike every CV + preset before it: the design was drawn on a raster whose proportion is not A4's and + every length is a share of that grid, so a caller's page size and margin are + overwritten rather than followed. Six berths reach their sections by title, including + a tagline berth whose body is drawn and whose title is not — the title is how a + document names the berth. Both vertical rules are the left border of the column to + their right rather than lines placed beside them, so they are the grid: change a + weight and the rules follow. The main headings are laid over their own rule with the + paper knocked out behind every letter, because a row cannot nest in a row cell and the + rule's visible length still has to follow the words. **What happens past one page is + not what the other ported CV presets do:** the bands are stacked in the page flow, so + a CV with more roles than the design holds carries the closing band onto a second page + rather than losing anything, while the body row itself is atomic and a body taller + than a page raises `AtomicNodeTooLargeException`. As on `TerracottaRail`, a link is + drawn as its own label with the address behind it, which is the one deliberate + departure from the ported sheet — writing the URL out makes the strip's gaps depend on + how long a profile is called — measured at six of 121 nodes shifting sideways and + 7 724 of 1 987 720 pixels, with nothing moving vertically. Guarded by a smoke test + (including the empty document, the page override, the tagline's title staying off the + sheet, the link targets, a linked role and degree, the run onto a second page and the + refusal of a body taller than one), an exact layout snapshot and a pixel-parity gate; + the examples showcase gains `cv-teal-pulse-v2`. + +- **An architect's two-column CV preset: `TerracottaRail`.** A one-page A4 sheet whose + narrow column carries a serif monogram over a terracotta rule, the contact channels + behind their marks, two bulleted lists and a block of closing facts, beside a wide + column carrying a letter-spaced masthead, the summary, the roles held on a ringed + rail, a projects grid and the degrees. Ships as `cv.presets.TerracottaRail` on the + existing `CvDocument` model with **no model change at all**, porting the rendered + layout of a published standalone template. Like `CharcoalGold` it leaves the page to + the caller. Eight berths reach their sections by title; the two bulleted lists are + skills without levels, because this design writes them as plain lines — one takes a + terracotta square and no dash under its heading, the other a disc and a dash, which + is how the sheet tells two lists of one-liners apart. Each fact and each project + takes the mark its entry names in `CvEntry.icon()` from this preset's own vocabulary, + every title it draws — a role, a project, a degree, a credential — is a link when its + entry carries one, and the monogram is drawn from + the name's own initials rather than a field of its own — a document states its name + once, and a monogram that could disagree with it would be a second place to keep + true. **A link in the contact block is drawn as its own label with the address behind + it**, which is where the preset departs from the sheet it ports: writing the URL out + makes that row as wide as whatever the reader's profile happens to be called — long + enough that the published design sets it smaller and nudged in, on an axis of its + own. Stating `Link("LinkedIn", "https://…")` puts the four rows on one axis at one + size for every document. The departure is exactly measured — two of 154 nodes narrow, + 1 743 of 2 173 720 pixels change, and nothing moves vertically — and both baselines + were recorded with it. A link takes the mark of the network it points at, or a globe. + Like its siblings it holds one page: the body is a single atomic row, so a longer CV raises + `AtomicNodeTooLargeException` rather than flowing or dropping entries. Guarded by a + smoke test (including the unknown-mark data error, an entry with no mark, an identity + with no links, a document with nothing but an identity, the monogram, the link + targets on every kind of title, the four contact rows sharing one axis and the + one-page limit), an exact layout snapshot and a pixel-parity gate; + the examples showcase gains `cv-terracotta-rail-v2`. + +- **The first invoice preset that paginates what it ports: `LumaStudioInvoice`.** A + studio invoice built around a cream sidebar — the two-line monogram and the wordmark + on a terracotta block at its head, a tinted quarter-disc, an arch and a sprig running + down the rest of it — beside a billing sheet that reads sender, title and metadata, + the billed-to and shipped-to pair across a rule, the priced service lines with a VAT + column, the totals stack closing on a filled total-due band, and the notes and bank + details above a dark sign-off band. Ships as `invoice.presets.LumaStudioInvoice` on + the structured invoice model, porting the rendered layout of the published standalone + `luma-co-studio-invoice` template. Unlike the CV presets promoted before it this one + flows: the line-items table repeats its dark header on the next page, the totals stack + and each closing block stay whole, and the paper tint, the sidebar column and the dark + foot band are page backgrounds, so every page carries the same frame and the folio + always has a dark ground. **The sign-off carries its own strip, which is where the + preset deliberately departs from the sheet it ports.** On the published template the + words rely on that background band, which is pinned to the paper's edge — right on a + one-page invoice, and white-on-cream in the middle of the last page of a longer one, + where the flow ends well above the paper's foot. Drawing the strip with the words + fixes it wherever they land; the cost is 7 440 of 2 173 720 pixels on the reference + sheet, where the strip starts three points above the background band it sits on, and + one extra layout node. It is flow content only because footer chrome carries text + today — one size, one colour, no glyph, where this band needs two faces and a disc — + so once a footer zone can hold a node the sign-off belongs in one and the strip goes + away. Its geometry is not a set of round numbers: the design was drawn + on a pixel grid, so the preset carries that grid as page ratios and states every + vertical gap as the white the drawing shows, subtracting the blank a line box already + carries — per family, because the three faces it sets fill different line boxes at the + same size. Letter-spaced runs are written letter by letter with an invisible inline + rectangle between the pairs, since a text style carries no tracking; the six spacings + are frozen constants rather than a measurement, because measuring at compose time + would tie the preset to the font artifact's resource layout. Amounts take their mark + from `StructuredInvoiceData.currencyCode()` — the code is the authority, so the sheet + cannot contradict itself — and the contact channels carry `tel:`, `mailto:` and + `https:` targets derived from their values, with a parenthesised trunk prefix dropped + from the dial target the way a caller dialling from abroad drops it. Guarded by a + smoke test (including the empty document, an unknown and a blank currency code, a + supplier with one registration and with none, the link targets, the repeated header + and folio on a continuation page, and the sign-off landing on its own ground), an + exact layout snapshot over both a one-page and a three-page invoice, and a + pixel-parity gate; the examples showcase gains `invoice-luma-studio-v2`. + +- **`SerifHeadline` links its titles and lets its bands breathe.** Every title the + preset draws — a role, a project, a degree, an achievement — is now a link when its + entry carries one. And a band column keeps a gutter at its right edge, so its text + stops short of the hairline between columns instead of running into it: **the first + place a promoted preset deliberately departs from the sheet it ports**, where a line + that happens to fill its column touches the rule. The departure is exactly measured — + six of 235 nodes narrow, 8 191 of 2 173 720 pixels change, and nothing else moves — + and both baselines were re-recorded with it. The class documentation names the two + colours the packaged marks come in, so a document chooses one deliberately rather + than mixing navy and gold in a row by accident. + +- **A photographic CV preset: `CharcoalGold`.** A one-page sheet in two columns: a + charcoal sidebar carrying a ringed photograph, the contact channels behind their + marks, rated skills, languages and degrees, beside a paper column carrying a two-tone + name — the given name in ink, the family name larger and in gold — the summary, the + roles held on a dated rail, a pair of credential columns divided by a hairline, and a + closing strip of tools. Ships as `cv.presets.CharcoalGold` on the existing + `CvDocument` model with **no model change at all**, porting the rendered layout of the + published standalone `charcoal-gold-cv` template. Unlike its ported siblings it leaves + the page size to the caller: the design is drawn on A4 and its geometry follows the + page's own width, so a different page rescales rather than breaks. Seven berths reach + their sections by title; credentials take the mark each entry names in + `CvEntry.icon()` from this preset's own set, every title it draws is a link when the + entry carries one, and a skill the document leaves unlevelled draws no rating rather + than five empty dots. Guarded by a smoke test (including the unknown-mark data error, + the missing portrait, the unlevelled skill, the link targets and the one-page limit), + an exact layout snapshot and a pixel-parity gate; the examples showcase gains + `cv-charcoal-gold-v2`. + +- **A two-column editorial CV preset: `SerifHeadline`.** A one-page A4 sheet under a + Volkhov masthead — the name in the display serif over its role and a short gold rule, + the contact channels stacked opposite — then a two-column body: the roles held on a + timeline rail and the projects as marked cards on the left, the degrees and grouped + skill meters across a hairline divider on the right, closing with full-width bands of + certifications and achievements. Ships as `cv.presets.SerifHeadline` on the existing + `CvDocument` model, porting the rendered layout of the published standalone + `serif-headline-cv` template. Its geometry is not a set of round numbers: the design + was drawn on a 1024-pixel grid, so the preset carries that grid and scales it onto A4 + — heights by an extra factor — and states every vertical gap as the white the drawing + shows, subtracting the blank a line box already carries above and below its own type. + Eight berths reach their sections by title, the projects and achievements take the + mark each entry names in `CvEntry.icon()` from this preset's own vocabulary, and the + employer's city and the campus come from `CvEntry.place()`. Like its siblings it owns + its page and holds one: the body is a single atomic row, so a longer CV raises + `AtomicNodeTooLargeException` rather than flowing or dropping entries. Guarded by a + smoke test (including the unknown-mark data error, an entry with no mark, an employer + with no place, the overlapping soft-skills berth, the link targets and the one-page + limit), an exact layout snapshot and a pixel-parity gate; the examples showcase gains + `cv-serif-headline-v2`. + +- **A portrait CV preset: `NavySidebar`.** A one-page A4 CV in two columns on Lato — + a navy plate carrying a ringed portrait, the contact channels behind their marks, the + degrees, the skills and the languages, beside a white column of the name, the summary, + the roles held on a timeline rail with a filled marker at each one, and the + achievements and certifications behind badged headings. Ships as + `cv.presets.NavySidebar` on the existing `CvDocument` model, porting the rendered + layout of the published standalone `navy-sidebar-cv` template. Like its sibling it + owns its page and holds one: the two columns are a single atomic row, so a CV longer + than the sheet raises `AtomicNodeTooLargeException` rather than flowing or dropping + entries. Sections reach their berth by title rather than by `Slot`; languages are a + `RowsSection` because this design writes the proficiency out — "Native", "Advanced" — + which a levelled skill could not carry back. The photograph comes from the new + `CvIdentity.portrait()`; an identity without one draws the ring around an empty navy + disc; its education entries read the `place` field for the campus line. The phone, + the email and each link are reachable from the PDF, with the + `tel:` and `mailto:` targets built from the values — the published sheet drew its + channels as plain text, and this is the one place the port deliberately improves on + it, at no cost to the render: annotations move no pixel and no layout node, which + both gates confirm without re-blessing. Guarded by a smoke test (including the link + targets, the missing portrait, the capitals this design imposes, the one-page limit + and the fields it has no place for), an exact layout snapshot and a pixel-parity + gate; the examples showcase gains `cv-navy-sidebar-v2`. + +- **The first CV preset that owns its page: `ProfessionalSidebar`.** A one-page CV + in two columns on the Barlow Condensed / Lato pair — a navy monogram plate over a + pale sidebar carrying the contact channels, meter-bar skills, an education rail with + dot markers and five-dot language ratings, beside a white column of the tracked name, + the profile, the roles held with bulleted highlights, the projects and the references + note. Ships as `cv.presets.ProfessionalSidebar` on the existing `CvDocument` model — + no model change was needed — porting the rendered layout of the published standalone + `professional-sidebar-cv` template. The preset owns its page: a 491.6 x 737.28pt + sheet with no margin, the page fill and the pale sidebar painted as page backgrounds. The + sheet holds one page — its two columns are a single atomic row, so a CV longer than the + sheet raises `AtomicNodeTooLargeException` rather than flowing onto a second page or + silently dropping entries the way the capped sidebar presets do. The class documentation + says so, points at `TimelineMinimal` for a preset that splits its own columns, and + `docs/templates/v2-layered/using-templates.md` carries it beside the capped presets. + Sections reach their berth by title rather than by `Slot`, + because the columns are fixed; skills and languages are both `SkillsSection`s drawn + differently, and a level the document omits draws the name alone. The contact channels + come off `CvIdentity`, with `tel:` and `mailto:` targets built from the values and the + packaged marks chosen per channel. Guarded by a smoke test (including the identity-only + document, the unlevelled skill, the PDF link targets, the one-page limit and the fields + this design has no place for), an exact layout snapshot and a pixel-parity gate; the + examples showcase gains `cv-professional-sidebar-v2`. + +- **A second structured proposal preset: `EditorialProposal`.** The same document the + `NorthlineProposal` preset renders, in a different hand: an orange accent, section + headings set in the display serif over short accent rules instead of in the body sans + inside icon badges, a brand mark drawn from vector paths instead of a monogram letter, + an untitled fact card, a scope ordinal set in plain accent text, and a hairline page + foot. Moving a document between the two presets is a one-line change plus two data + checks — the badge and goal icon tokens are preset-scoped (the four `fact-*` tokens + are not), and headings are drawn as authored, so the sibling's tracked capitals stay + capitals here. Ships as + `proposal.presets.EditorialProposal` on the structured proposal model, porting the + rendered layout of the published standalone `northline-proposal-orange` template, + with its SVG icon set packaged in the artifact. Guarded by a smoke test (including + the empty document, both data contracts, and the proof that one document renders + through both proposal presets), an exact two-page layout snapshot, and a + pixel-parity gate; the examples showcase gains `proposal-editorial-v2`. + +- **A professional-services invoice preset: `ConsultingInvoice`.** A corporate masthead + — brand lockup and contact channels beside the document title and its metadata — + over priced service lines that carry a service period and a unit per line, closing + with an emphasized total band and the bank details beside the notes and the due-by + chip. Ships as `invoice.presets.ConsultingInvoice` consuming the new structured + invoice model, with its contact marks, bank badge and calendar packaged in the + templates artifact, porting the rendered layout of the published standalone + `northpoint-consulting-invoice` template. Long invoices flow: the line-items table + repeats its header and the totals stack stays whole. Guarded by a smoke test + (including the empty document, the wordmark fallback, the rendered figures and the + repeated header), exact layout snapshots for the single page and the overflow, and a + pixel-parity gate; the examples showcase gains `invoice-consulting-v2`. + +- **A second invoice preset: `ClassicInvoice`.** The letterhead-style invoice — a header + band with the company name and a 28pt INVOICE title, a TOTAL DUE hero strip, + BILL TO / FROM party columns, and a dedicated Summary table composed after the + line items (subtotal / tax / TOTAL, the last row emphasized) — now ships as + `invoice.presets.ClassicInvoice` on the layered stack, with the same + `create()` / `create(BrandTheme)` contract as `ModernInvoice`, porting the rendered + layout of the published standalone `invoice-classic` template. Guarded by a smoke + test, exact layout snapshots (the canonical single page plus a forty-line-item + overflow that freezes the two-page table continuation), and the invoice pixel-parity + gate; the examples showcase gains `invoice-classic-v2`. + +- **The first structured proposal preset: `NorthlineProposal`.** A two-page + teal-and-navy business proposal on the Spectral/Lato pair — brand header with logo + mark and wordmark, three stacked title lines on the reference's own pitch, the + executive summary beside an at-a-glance fact card, icon goal cells, a numbered + scope list, deliverable columns, the phase grid, an investment table with subtotal / + optional / total row styling, and a signing card — with the navy footer band, the + teal page-number block and the `01`/`02` page numbers as page chrome rather than + flow content. Ships as `proposal.presets.NorthlineProposal` consuming the new + structured proposal model, with its icon set packaged in the templates artifact, + porting the rendered layout of the published standalone `northline-proposal` + template. Guarded by a smoke test (including the empty document and the + phase-grid header-count contract), an exact two-page layout snapshot, and a + pixel-parity gate; the examples showcase gains `proposal-northline-v2`. + +- **A one-page sales proposal preset: `IndigoProposal`.** A wordmark against the + document's label over a full-width rule, the addressed organisation and the person to + reply to beside a four-line headline and a row of marked discs, a tinted band that + bleeds to both paper edges carrying a paragraph about the issuer over four marked + tiles, a numbered plan beside the priced rows and their total card, and a foot with the + issuer's identity and its channels. Ships as `proposal.presets.IndigoProposal` on the + structured proposal model, with its icon set packaged in the templates artifact, + porting the rendered layout of a published standalone template — and this one is exact: + rendered against the frozen original with the same data the text layers are + **identical, all 262 words in the same place to 0.000 pt**, and the head, the lower + half, the closing rule and the foot's own lines carry **no differing pixel at all**. +

+ **Two of the design's own brand assets are deliberately not ported.** The band's right + is a product photograph carrying a payment network's mark, and the foot closes on the + issuer's logotype. Those belong to their owners, and the templates artifact does not + redistribute other companies' marks. Both places are kept rather than closed up — the + band keeps the height and the column that give the copy beside it the measure it was + solved against, and the foot keeps the column that sets where the name begins — and + what fills them is the document's: the foot sets whatever monogram the document names, + and the band's column stays flat tint. This is stated on the preset rather than left to + be noticed. +

+ The sheet is one page by design and not by accident. Every block's position is a cap + top in one vertical map and a single cursor walks the page once, turning each cap top + into the margin that puts it there — so a block moves by changing one number rather + than by re-deriving the gaps around it. Four blocks state how many lines they wrap to, + because the engine cannot be asked at compose time; content heavier than the design's + own pushes what follows down and can carry the foot onto a second page, which the + preset says rather than capping what it is given. +

+ **No block is ever drawn over another.** A stated position and heavier-than-designed + content produce a negative margin, which the engine honours by *raising* the block — + so a plan of six or seven steps still fits the page and prints the closing rule + through its last step. Every margin the cursor returns is therefore floored at + nothing, and the counts either side of that window are gates: five steps is the + design's own, six and seven are where an unclamped margin overlaps silently, and + fourteen is where the foot has to take a second page. +

+ Sizes are solved from ink rather than from cap height: the design's own face is not + bundled, the substitute sets 10–20% wide at bold, and matching a cap height alone put + every bold block over its measure. A foot's confidentiality line, when a document + carries one, closes the channels line rather than opening one of its own — the + design's last line already ends against the bottom margin, so a fourth would carry the + whole foot onto a second page — and it is set as prose, not as a channel: a notice is + a sentence, and a sentence is not somewhere a reader can be sent. +

+ The two packaged mark sets are not interchangeable, and the preset says so by + refusing: the header's marks are drawn in the accent for a pale disc and the band's in + white for a near-black tile, so a band token named on a header disc would render as + nothing at all. It is a data error reported by name instead. Guarded by a smoke test + (including the empty document, a tile that names no mark, both unknown-token cases, + the notice, the overlap window and every contact being followable), an exact layout + snapshot, and a pixel baseline — all three fed by one fixture. + +- **The first rota preset: `CobaltRota`.** A staff rota on one landscape sheet — a + navy-ruled grid of who works when, read across a row rather than down a page. The + label column carries the venue's mark over the staff names; the day columns carry a + heading, whatever else is happening that day, a strip of swatches saying what the + colours mean, the covers each service is expecting, and then the people in bands, each + band opening with a navy strip and its own mark. Ships as `rota.presets.CobaltRota` on + the new `StructuredRotaData` model, with its icon set packaged in the templates + artifact, porting the rendered layout of a published standalone template: rendered at + 150 dpi against the frozen original driven by the original's own data file, **every + pixel of the sheet is identical** — the grid, all eighty-four cells, the legend, the + covers, the bands and the foot's own line. The one region that differs is the foot's + left slot, which the original leaves empty and the port fills with the note the + document states. +

+ **The whole sheet is one table.** Everything from the masthead rule to the last person + is rows of it — that is what keeps the columns aligned, where a masthead above or a + legend beside would be a second structure to keep in step. The four header rows are + declared repeating with their count, because `repeatHeader()` with no argument repeats + one row and a rota longer than a page would carry a stray rule onto the next one + instead of its day headings. +

+ **The grid follows the document, not the design.** The sheet has as many day columns + as the rota has days and every person's entries are read by position against that + list, so a five-day rota makes five wider columns; nothing in the preset says a week. + A legend documenting more statuses than there are days runs onto a second strip rather + than losing its tail — a status a reader cannot look up is worse than an extra row — + and one documenting fewer is closed off with empty cells, so the table stays square + either way. The label column keeps its own padding whatever the day count, so the + wordmark does not resize with the number of days. A rota that states no legend and no + covers carries neither row: an empty navy bar over empty boxes is two rows of a sheet + that is short of rows already. +

+ A day cell is a block of a stated height rather than whatever its chips come to, + because a table cell places a node child at its *top* — `textAnchor` seats text, not a + node — so a short chip in a row made tall by a neighbour would otherwise hang with all + the slack beneath it. An unmarked entry is drawn as a chip with no fill and no outline + rather than as bare text, so it occupies exactly the height a marked one does. The + row's tint is threaded by hand into every cell and on into those chips rather than + left to the table's own striping, which would stripe the header, the legend, the + covers and the bands too. Guarded by a smoke test (including the empty rota, a + five-day rota, a split day, a band naming no mark, the unknown-token data error, a + legend longer and shorter than the week, a rota stating neither legend nor covers, and + one long enough to need a second page), an exact layout snapshot, and a pixel baseline + — all three fed by one fixture. +

+ **The gates say what they can see.** The sheet is composed table cells almost end to + end, and a composed cell emits fragments rather than a placed node, so the snapshot + holds two nodes for eighty-four shift cells: it pins the page and the table that + carries them, and nothing inside. The pixel baseline is the only gate that sees the + sheet, and one chip is under two thousand of the page's half-million pixels — a budget + wide enough for a renderer's antialiasing is wide enough to hide several chips — so + its budget is a fraction of the sibling presets', and a third test counts the ink of + each status colour directly. Removing a chip's fill turns that one red by name. + +- **The rails seven CV presets drew by hand are timelines.** Midnight Navy, Charcoal Gold, + Navy Sidebar, Serif Headline, Slate Orange, Terracotta Rail and Professional Sidebar each + stood their entry rail up as a left accent border repeated per entry and withheld from the + last one, so the line would appear to stop at the final marker. `addTimeline` with a + `rail(...)` extent says that directly, so it says it: `MARKER_TO_MARKER` where the line + runs between the marks, `ENTRY_BOUNDS` where it spans the entries. +

+ What a reader sees change is that a rail now meets its markers instead of stopping at the + edge of the line box a marker sits in — a few points, at the two ends only, and each + preset's own figure is recorded with its migration. Markers, date columns and content + columns hold their x, no preset gains or loses a page, and node accounting is exact either + side of every one. +

+ Professional Sidebar's education rail loses 56 lines with it. It filled the first entry's + head band with the sidebar colour, painted a shape over the rail above the first dot, and + redrew the line below — to make the rail begin at that dot. Scanned in the rail column of + its own baseline, it never did: an accent draws above both the fill and the mask, so the + masked stretch stayed visible and the construction's only effect was drawing 2.5pt of rail + twice, a shade darker than the rest of the line. +

+ **Violet Grid keeps its hand-built rail**, deliberately. It is the one of these presets + whose timeline crosses a page, and two things the rail API cannot express turn up there and + only there. A rail's extent on a page that is not the last comes from that page's content + band rather than from the entries on it, which would run this rail 55.25pt past its final + role to the bottom margin. And a timeline's leading column width is fixed by where the rail + and the content sit, which pins this design's date column at 55.860pt against a longest + date of 57.005 and wraps a line that has never wrapped. Both are measured against an + overflow fixture written for the purpose, and both are recorded as gaps rather than + approximated. + +- **Serif Headline's bullet gap is a measurement rather than a space.** Its highlight lists + asked for the air after the dot by writing the marker `"• "`. A list marker's authored + trailing whitespace is stripped and a single space re-appended, so the second one never + reached the page and the gap has always been one space: 2.067pt. It is `markerGap` now, at + 0.256 em — the body face's real space advance — with `hangingIndent(true)`. Every dot and + every first line holds its position to 0.001pt. What moves is the wrapped lines, which used + to resume 1.518pt *past* the text they belong to, because the legacy indent pads a + continuation in whole spaces and cannot land on anything finer; they start on it now. + +- **A Timeline Minimal marker that never drew is gone.** Its module body lines set + `bulletOffset("-")` and rendered no dash: a bullet prefix reaches the page only under an + indent strategy that asks for it, and the site set none, so the prefix was built, measured + for the continuation indent, and applied to no line. The call is removed rather than made + to work — plain lines are what the design has always shown — and the render is unchanged to + the pixel. + +- **The receipt and both structured proposals stop padding their spaced caps.** Eight labels + across `ModernReceipt`, `EditorialProposal` and `NorthlineProposal` built the look by + rewriting the string with a space between every letter, so the canonical receipt's text + layer read `N O R T H W I N D P A Y`, `A M O U N T C O L L E C T E D`, `T R A N S F E R D + E T A I L S`. The tracking moves onto the style — `TextOrnaments.SPACED_CAPS`, on + `ReceiptStyles.eyebrow` and `.groupTitle` and on each proposal's `DOC_LABEL`, which is what + those styles' own javadoc already said they were — and the text is the text. This is what + makes the note under **Public API** true of every built-in family rather than of the CV and + cover-letter ones alone. +

+ The receipt's labels narrow by 5–9% and nothing re-flows: one page, 130 nodes, and the nine + that change are all width and none position. Each proposal's label is right-aligned inside + its own section, so its node does not move at all and the change shows only in pixels and + on the text layer. Both families carry a new assertion on that text layer, because it is + the reason for the change and no pixel or geometry gate can see it. + ### Tests +- **The schedule fixtures no longer carry a real venue's staff.** The weekly-schedule + test fixture and the example data factory were written from a real bar's rota and kept + its people's names, its venue name, and two third parties named in the day notes. + Fixture content should be invented, and this is now: eleven invented names, an + invented venue, and generic notes. The shape — eleven people, seven days, the same + categories and the same assignments — is unchanged, so every assertion that read it + reads the same thing. `assets/readme/examples/weekly-schedule.pdf` is re-rendered for + the two note strings it prints; nothing else in it moves. + +- **The CodeQL scope guard can no longer be emptied by rewriting a deploy command.** + `CodeQlScopeGuardTest` asks whether every module a release publishes is inside the + security scan, and it read the answer's left-hand side out of the publish workflows by + matching `-f /pom.xml` on a line mentioning `deploy`. A deploy written any + other way — `-pl :graph-compose-fonts`, or the same command wrapped across two lines — + simply left that module out of the inventory, and a shorter inventory is an easier + comparison rather than a failing one. Verified by mutation: with `publish-fonts.yml` + switched to the `-pl` form and `graph-compose-fonts` removed from the scan's module + list, the suite stayed green while a module published to Maven Central went unscanned. + Two checks now key on the absence of a positive signal instead — a publish workflow + that contributes no module to the inventory fails, and `publish.yml`'s deploy steps + are held against the `order` line it validates its own resume against, which is a + second statement of the train that nothing derives from the first. Both go red under + the mutation that used to pass. + +- **The sidebar CV samples are held to the width of the column they are drawn in.** + A contact channel in `ProfessionalSidebar` and `NavySidebar` is one paragraph — the + mark and the value share a line — so a value wider than the sidebar's text column + wraps and leaves the mark alone on the first line. The promotion gates could not see + it: they measure the published template's own fixture, whose addresses fit, while the + example sample data is written for the repository and can outgrow the column with + every test still green. It shipped that way once, spotted in the rendered preview + rather than by a build. `SidebarContactRowsFitTest` now measures the shape of the row + instead of the length of the string — a single-line channel is as tall as its mark, + a wrapped one close to twice that — and the two sample addresses were shortened to + fit. + +- **Every promoted CV design is asked the same question about its telephone link.** + `CvPresetDialTargetTest` renders all ten with a contact block whose number carries a + trunk prefix and reads the `tel:` targets back out of the file. This is the check the + thirteen private copies never had: two designs had a test naming a trunk prefix and + both handled one, while the other eight asserted their dial target with a number + carrying none — so every copy agreed with its own test, and four of them still built a + target no caller could reach. Proved fails-closed by removing the trunk-prefix rule + from `ContactUri` and watching all ten go red. `ConsultingInvoiceSmokeTest` asks the + same of the invoice design that was furthest out, giving the masthead and the closing + prose different numbers so that neither site can answer for the other. + - **The timeline's finished visual model is pinned scene by scene.** Ten scenarios, each given the instrument that can decide it: a coordinate where the claim is a coordinate, a picture where the claim is a shape or a paint order, and neither where the other @@ -545,6 +1911,14 @@ follow semantic versioning; release dates are ISO 8601. ### Deprecations +- **`templates.data.schedule` — every type.** `WeeklyScheduleData`, + `WeeklyScheduleDocumentSpec`, `ScheduleDay`, `ScheduleCategory`, `SchedulePerson`, + `ScheduleAssignment`, `ScheduleSlot` and `ScheduleMetricRow` are deprecated since + 2.4.0 in favour of `templates.data.rota`, each naming its replacement. They still + ship and still compile; nothing in the library ever rendered them, so no output moves. + `docs/templates/which-template-system.md` now points a caller at the rota model + instead. + - **`TextOrnaments.spacedUpper(String)`** is `@Deprecated(since = "2.4.0", forRemoval = true)`. It is not removed, and its behaviour has not changed by a single character — code written against 2.3.0 keeps compiling and keeps getting the same strings back. What changed is that diff --git a/assets/readme/examples/cv-blue-banner-v2.pdf b/assets/readme/examples/cv-blue-banner-v2.pdf index eaf9a6052..acdc018f0 100644 Binary files a/assets/readme/examples/cv-blue-banner-v2.pdf and b/assets/readme/examples/cv-blue-banner-v2.pdf differ diff --git a/assets/readme/examples/cv-boxed-sections-v2.pdf b/assets/readme/examples/cv-boxed-sections-v2.pdf index a2f8fb368..6904d64d9 100644 Binary files a/assets/readme/examples/cv-boxed-sections-v2.pdf and b/assets/readme/examples/cv-boxed-sections-v2.pdf differ diff --git a/assets/readme/examples/cv-centered-headline-v2.pdf b/assets/readme/examples/cv-centered-headline-v2.pdf index ae1f63cdb..0632c0ef2 100644 Binary files a/assets/readme/examples/cv-centered-headline-v2.pdf and b/assets/readme/examples/cv-centered-headline-v2.pdf differ diff --git a/assets/readme/examples/cv-charcoal-gold-v2.pdf b/assets/readme/examples/cv-charcoal-gold-v2.pdf new file mode 100644 index 000000000..f3190db15 Binary files /dev/null and b/assets/readme/examples/cv-charcoal-gold-v2.pdf differ diff --git a/assets/readme/examples/cv-engineering-resume-v2.pdf b/assets/readme/examples/cv-engineering-resume-v2.pdf index dec3ff8a2..8e2bccd5d 100644 Binary files a/assets/readme/examples/cv-engineering-resume-v2.pdf and b/assets/readme/examples/cv-engineering-resume-v2.pdf differ diff --git a/assets/readme/examples/cv-executive-v2.pdf b/assets/readme/examples/cv-executive-v2.pdf index 5fa628266..3ff095451 100644 Binary files a/assets/readme/examples/cv-executive-v2.pdf and b/assets/readme/examples/cv-executive-v2.pdf differ diff --git a/assets/readme/examples/cv-midnight-navy-v2.pdf b/assets/readme/examples/cv-midnight-navy-v2.pdf new file mode 100644 index 000000000..91f11bbfe Binary files /dev/null and b/assets/readme/examples/cv-midnight-navy-v2.pdf differ diff --git a/assets/readme/examples/cv-minimal-underlined-v2.pdf b/assets/readme/examples/cv-minimal-underlined-v2.pdf index c4d1082cf..505478f17 100644 Binary files a/assets/readme/examples/cv-minimal-underlined-v2.pdf and b/assets/readme/examples/cv-minimal-underlined-v2.pdf differ diff --git a/assets/readme/examples/cv-mint-editorial-v2-custom.pdf b/assets/readme/examples/cv-mint-editorial-v2-custom.pdf index 993262500..3d553946a 100644 Binary files a/assets/readme/examples/cv-mint-editorial-v2-custom.pdf and b/assets/readme/examples/cv-mint-editorial-v2-custom.pdf differ diff --git a/assets/readme/examples/cv-mint-editorial-v2.pdf b/assets/readme/examples/cv-mint-editorial-v2.pdf index e4d327e40..c74cc0965 100644 Binary files a/assets/readme/examples/cv-mint-editorial-v2.pdf and b/assets/readme/examples/cv-mint-editorial-v2.pdf differ diff --git a/assets/readme/examples/cv-modern-professional-v2.pdf b/assets/readme/examples/cv-modern-professional-v2.pdf index 114f66c5f..0677aaa2e 100644 Binary files a/assets/readme/examples/cv-modern-professional-v2.pdf and b/assets/readme/examples/cv-modern-professional-v2.pdf differ diff --git a/assets/readme/examples/cv-monogram-sidebar-v2.pdf b/assets/readme/examples/cv-monogram-sidebar-v2.pdf index bbd3951d5..bec4f121e 100644 Binary files a/assets/readme/examples/cv-monogram-sidebar-v2.pdf and b/assets/readme/examples/cv-monogram-sidebar-v2.pdf differ diff --git a/assets/readme/examples/cv-navy-sidebar-v2.pdf b/assets/readme/examples/cv-navy-sidebar-v2.pdf new file mode 100644 index 000000000..d9356c094 Binary files /dev/null and b/assets/readme/examples/cv-navy-sidebar-v2.pdf differ diff --git a/assets/readme/examples/cv-orange-ops-v2.pdf b/assets/readme/examples/cv-orange-ops-v2.pdf new file mode 100644 index 000000000..e31a502c1 Binary files /dev/null and b/assets/readme/examples/cv-orange-ops-v2.pdf differ diff --git a/assets/readme/examples/cv-panel-v2.pdf b/assets/readme/examples/cv-panel-v2.pdf index 1375fc645..18ee0bf21 100644 Binary files a/assets/readme/examples/cv-panel-v2.pdf and b/assets/readme/examples/cv-panel-v2.pdf differ diff --git a/assets/readme/examples/cv-professional-sidebar-v2.pdf b/assets/readme/examples/cv-professional-sidebar-v2.pdf new file mode 100644 index 000000000..59801874b Binary files /dev/null and b/assets/readme/examples/cv-professional-sidebar-v2.pdf differ diff --git a/assets/readme/examples/cv-serif-headline-v2.pdf b/assets/readme/examples/cv-serif-headline-v2.pdf new file mode 100644 index 000000000..b86f5b925 Binary files /dev/null and b/assets/readme/examples/cv-serif-headline-v2.pdf differ diff --git a/assets/readme/examples/cv-sidebar-portrait-v2.pdf b/assets/readme/examples/cv-sidebar-portrait-v2.pdf index 5f79c49f8..a60cb9968 100644 Binary files a/assets/readme/examples/cv-sidebar-portrait-v2.pdf and b/assets/readme/examples/cv-sidebar-portrait-v2.pdf differ diff --git a/assets/readme/examples/cv-slate-orange-v2.pdf b/assets/readme/examples/cv-slate-orange-v2.pdf new file mode 100644 index 000000000..0f954dbaa Binary files /dev/null and b/assets/readme/examples/cv-slate-orange-v2.pdf differ diff --git a/assets/readme/examples/cv-teal-pulse-v2.pdf b/assets/readme/examples/cv-teal-pulse-v2.pdf new file mode 100644 index 000000000..72277c2da Binary files /dev/null and b/assets/readme/examples/cv-teal-pulse-v2.pdf differ diff --git a/assets/readme/examples/cv-terracotta-rail-v2.pdf b/assets/readme/examples/cv-terracotta-rail-v2.pdf new file mode 100644 index 000000000..f122afc92 Binary files /dev/null and b/assets/readme/examples/cv-terracotta-rail-v2.pdf differ diff --git a/assets/readme/examples/cv-timeline-minimal-v2.pdf b/assets/readme/examples/cv-timeline-minimal-v2.pdf index c9124479f..e26d082e9 100644 Binary files a/assets/readme/examples/cv-timeline-minimal-v2.pdf and b/assets/readme/examples/cv-timeline-minimal-v2.pdf differ diff --git a/assets/readme/examples/cv-violet-grid-v2.pdf b/assets/readme/examples/cv-violet-grid-v2.pdf new file mode 100644 index 000000000..9d165c9c7 Binary files /dev/null and b/assets/readme/examples/cv-violet-grid-v2.pdf differ diff --git a/assets/readme/examples/invoice-classic-v2.pdf b/assets/readme/examples/invoice-classic-v2.pdf new file mode 100644 index 000000000..1a4c2dc20 Binary files /dev/null and b/assets/readme/examples/invoice-classic-v2.pdf differ diff --git a/assets/readme/examples/invoice-consulting-v2.pdf b/assets/readme/examples/invoice-consulting-v2.pdf new file mode 100644 index 000000000..da323a244 Binary files /dev/null and b/assets/readme/examples/invoice-consulting-v2.pdf differ diff --git a/assets/readme/examples/invoice-luma-studio-v2.pdf b/assets/readme/examples/invoice-luma-studio-v2.pdf new file mode 100644 index 000000000..35493c777 Binary files /dev/null and b/assets/readme/examples/invoice-luma-studio-v2.pdf differ diff --git a/assets/readme/examples/invoice-payments-v2.pdf b/assets/readme/examples/invoice-payments-v2.pdf new file mode 100644 index 000000000..6d65b071d Binary files /dev/null and b/assets/readme/examples/invoice-payments-v2.pdf differ diff --git a/assets/readme/examples/invoice-workspace-v2.pdf b/assets/readme/examples/invoice-workspace-v2.pdf new file mode 100644 index 000000000..365b8ebc5 Binary files /dev/null and b/assets/readme/examples/invoice-workspace-v2.pdf differ diff --git a/assets/readme/examples/proposal-editorial-v2.pdf b/assets/readme/examples/proposal-editorial-v2.pdf new file mode 100644 index 000000000..45f0ccd6b Binary files /dev/null and b/assets/readme/examples/proposal-editorial-v2.pdf differ diff --git a/assets/readme/examples/proposal-northline-v2.pdf b/assets/readme/examples/proposal-northline-v2.pdf new file mode 100644 index 000000000..4216dc4e5 Binary files /dev/null and b/assets/readme/examples/proposal-northline-v2.pdf differ diff --git a/assets/readme/examples/receipt-modern.pdf b/assets/readme/examples/receipt-modern.pdf new file mode 100644 index 000000000..b77dedc96 Binary files /dev/null and b/assets/readme/examples/receipt-modern.pdf differ diff --git a/assets/readme/examples/weekly-schedule.pdf b/assets/readme/examples/weekly-schedule.pdf index 8c43e6531..e80469d6f 100644 Binary files a/assets/readme/examples/weekly-schedule.pdf and b/assets/readme/examples/weekly-schedule.pdf differ diff --git a/core/src/main/java/com/demcha/compose/document/dsl/ListBuilder.java b/core/src/main/java/com/demcha/compose/document/dsl/ListBuilder.java index 685096e18..5c6c76c73 100644 --- a/core/src/main/java/com/demcha/compose/document/dsl/ListBuilder.java +++ b/core/src/main/java/com/demcha/compose/document/dsl/ListBuilder.java @@ -1,5 +1,6 @@ package com.demcha.compose.document.dsl; +import com.demcha.compose.document.node.InlineRun; import com.demcha.compose.document.node.ListItem; import com.demcha.compose.document.node.ListMarker; import com.demcha.compose.document.node.ListNode; @@ -19,7 +20,21 @@ public final class ListBuilder { private final List items = new ArrayList<>(); private final Map markerOverrides = new LinkedHashMap<>(); private String name = ""; - private boolean usedNestedAuthoring = false; + /** + * Whether the items still fit the flat {@code List} shape a list had + * before it could nest. Nesting is one reason they do not; a rich item, whose + * content is runs rather than a label, is the other — it has to reach the + * layout as a {@link ListItem}, and the flat path carries only labels. + */ + private boolean needsItemTree = false; + /** + * Whether the author declared depth. Kept apart from {@link #needsItemTree} + * because the two answer different questions, and only this one decides + * whose marker depth 0 takes: a nested list resolves every level from the + * per-depth cascade and {@link #markerFor(int, ListMarker)}, while a flat + * list's marker is its own — and a list of rich items is still flat. + */ + private boolean declaredDepth = false; private ListMarker marker = ListMarker.bullet(); private DocumentTextStyle textStyle = DocumentTextStyle.DEFAULT; private TextAlign align = TextAlign.LEFT; @@ -50,7 +65,7 @@ private static List applyMarkerOverrides(List items, ? item.marker() : overrides.get(depth); List resolvedChildren = applyMarkerOverrides(item.children(), depth + 1, overrides); - out.add(new ListItem(item.label(), effective, resolvedChildren)); + out.add(new ListItem(item.label(), item.runs(), effective, resolvedChildren)); } return List.copyOf(out); } @@ -129,13 +144,77 @@ public ListBuilder addItem(String item) { */ public ListBuilder addItem(String label, Consumer body) { Objects.requireNonNull(body, "body"); - this.usedNestedAuthoring = true; + this.needsItemTree = true; + this.declaredDepth = true; ListBuilder childScope = new ListBuilder(); body.accept(childScope); this.items.add(new ListItem(label, null, childScope.snapshotItems())); return this; } + /** + * Appends one list item whose content is styled in pieces. + * + *

{@code "Bold label: normal description"} becomes one item rather than a + * row of two columns pretending to be one. The content is a {@link RichText} + * — the same inline runs a paragraph is made of, taken by the same builder + * {@link ParagraphBuilder#rich(Consumer)} takes — so the library has one + * rich-text model and not a second one for lists.

+ * + *

The item lays out on the measured marker geometry: + * {@link #hangingIndent(boolean)} is required, and the layout says so if it + * is missing. The marker is measured, {@link #markerGap(double)} applies, and + * every visual line of the content — the first, the ones it wraps onto, the + * ones that continue on the next page — starts at one x. A rich item is + * content whatever its runs draw, so runs of an icon and no text are still a + * row.

+ * + *

Seed the supplied builder with {@link RichText#plain(String)} — not + * {@code t.text(...)}: {@link RichText#text(String)} is a static factory, so + * that call compiles but builds a separate, discarded {@code RichText} and + * leaves this item empty.

+ * + * @param content callback that appends this item's inline runs + * @return this builder + * @throws NullPointerException if {@code content} is null + * @since 2.4.0 + */ + public ListBuilder addItem(Consumer content) { + Objects.requireNonNull(content, "content"); + RichText rich = RichText.empty(); + content.accept(rich); + this.needsItemTree = true; + this.items.add(ListItem.ofRuns(rich.runs())); + return this; + } + + /** + * Appends one nested list item whose own content is styled in pieces. + * + *

{@link #addItem(Consumer)} with children, so a styled label can head a + * sub-tree.

+ * + * @param content callback that appends this item's inline runs + * @param body callback that adds children of this item + * @return this builder + * @throws NullPointerException if either callback is null + * @since 2.4.0 + */ + public ListBuilder addItem(Consumer content, Consumer body) { + Objects.requireNonNull(content, "content"); + Objects.requireNonNull(body, "body"); + this.needsItemTree = true; + this.declaredDepth = true; + RichText rich = RichText.empty(); + content.accept(rich); + ListBuilder childScope = new ListBuilder(); + body.accept(childScope); + List runs = rich.runs(); + this.items.add(new ListItem(InlineRun.plainText(runs), runs, null, + childScope.snapshotItems())); + return this; + } + /** * Overrides the marker used for items at the given depth when no * per-item marker is set. Depth 0 is the top-level marker; depth 1 @@ -185,6 +264,49 @@ public ListBuilder marker(String marker) { return marker(ListMarker.custom(marker)); } + /** + * Sets a marker that is drawn rather than typed — a coloured disc, an icon, + * a glyph in a face of its own. + * + *

The marker is a {@link RichText}, the same inline runs a paragraph and + * a list item are made of, so {@code m -> m.dot(4, ACCENT)} is a teal disc, + * {@code m -> m.color("•", ACCENT)} is an accent bullet beside near-black + * text, and {@code m -> m.svgIcon(icon, 8)} is an icon. A marker's colour is + * its own here; the text form takes the list's, which is what a design with + * a coloured mark and dark copy could not say.

+ * + *

Drawn or typed, the marker is measured and occupies the marker column: + * {@link #markerGap(double)} is points of real space after it and every + * visual line of the item starts at one x. So this needs + * {@link #hangingIndent(boolean)}, and the layout says so if it is missing — + * the older layout puts the marker inside the item's text, where a drawing + * cannot go.

+ * + *

Seed the supplied builder with {@link RichText#plain(String)} — not + * {@code m.text(...)}: {@link RichText#text(String)} is a static factory, so + * that call compiles but builds a separate, discarded {@code RichText} and + * leaves the list markerless.

+ * + *

This sets the list's marker, which is its depth-0 marker. A drawn marker + * at a deeper level goes through {@link #markerFor(int, ListMarker)} as + * {@code markerFor(1, ListMarker.ofRuns(RichText.empty().dot(4, ACCENT).runs()))} + * — deliberately not a second lambda overload, because + * {@code markerFor(depth, null)} clears an override and a lambda overload + * would make that call ambiguous for code that already compiles.

+ * + * @param marker callback that appends the marker's inline runs + * @return this builder + * @throws NullPointerException if {@code marker} is null + * @since 2.4.0 + */ + public ListBuilder marker(Consumer marker) { + Objects.requireNonNull(marker, "marker"); + RichText rich = RichText.empty(); + marker.accept(rich); + return marker(ListMarker.ofRuns(rich.runs())); + } + + /** * Uses bullet markers. * @@ -404,18 +526,19 @@ public ListBuilder margin(float top, float right, float bottom, float left) { * Builds the semantic list node. * *

When only {@link #addItem(String)} was used the result is a - * flat list (back-compat with v1.4 / v1.5). As soon as - * {@link #addItem(String, Consumer)} is called at least once the - * result is a nested list — flat items added before the first - * nested call become depth-0 leaves alongside the nested entries, - * preserving source order. Per-depth marker overrides set via - * {@link #markerFor(int, ListMarker)} are baked into each item's - * resolved marker before the node is sealed.

+ * flat list (back-compat with v1.4 / v1.5). As soon as something is + * added that a list of labels cannot hold — a nested item via + * {@link #addItem(String, Consumer)}, or a rich item via + * {@link #addItem(Consumer)}, either of them once — the result is an + * item tree; flat items added before that become depth-0 leaves + * alongside the rest, preserving source order. Per-depth marker + * overrides set via {@link #markerFor(int, ListMarker)} are baked + * into each item's resolved marker before the node is sealed.

* * @return list node */ public ListNode build() { - if (!usedNestedAuthoring) { + if (!needsItemTree) { // Back-compat flat path. node.items() carries the labels // and node.nestedItems() is empty; rendering matches the // v1.4 / v1.5 flat-list behaviour exactly. @@ -441,7 +564,17 @@ public ListNode build() { } // Nested path. Source order across flat and nested entries is // preserved because both flow through the unified `items` list. - List resolved = applyMarkerOverrides(items, 0, markerOverrides); + // A flat list's marker is its own, whether its items are strings or + // runs. Only a list whose author declared depth hands depth 0 to the + // per-depth cascade — that is the nested contract, and markerFor(0, …) + // is how it is overridden. Without this a dashed list would render one + // bullet the moment one of its items needed styling. + Map effectiveOverrides = markerOverrides; + if (!declaredDepth) { + effectiveOverrides = new LinkedHashMap<>(markerOverrides); + effectiveOverrides.putIfAbsent(0, marker); + } + List resolved = applyMarkerOverrides(items, 0, effectiveOverrides); return new ListNode( name, List.of(), diff --git a/core/src/main/java/com/demcha/compose/document/dsl/TimelineBuilder.java b/core/src/main/java/com/demcha/compose/document/dsl/TimelineBuilder.java index 1d2914df5..2458722cd 100644 --- a/core/src/main/java/com/demcha/compose/document/dsl/TimelineBuilder.java +++ b/core/src/main/java/com/demcha/compose/document/dsl/TimelineBuilder.java @@ -39,7 +39,7 @@ * *

The rail is one logical line, computed after layout from where the markers and * entries actually landed and drawn as one fragment per page it crosses. How far it - * runs is a {@link TimelineRailExtent}; where it runs comes from the marker anchor, + * runs is a {@link TimelineRailEnd} at each end; where it runs comes from the marker anchor, * a gutter to the left of the markers by default or through them after * {@link #markerOnRail()}. It is drawn beneath the markers, so a filled marker * covers the line passing under it.

@@ -61,10 +61,13 @@ public final class TimelineBuilder { private final List entries = new ArrayList<>(); private DocumentStroke railStroke = DocumentStroke.of(DEFAULT_RAIL, 1.5); private String railDeclaredBy; - private TimelineRailExtent railExtent = TimelineRailExtent.ENTRY_BOUNDS; + private TimelineRailEnd railStart = TimelineRailEnd.ENTRY_BOUND; + private TimelineRailEnd railEnd = TimelineRailEnd.ENTRY_BOUND; private TimelineMarkerAnchor markerAnchor; private double gutter = 8.0; private double markerGap = 8.0; + /** Unset until asked for, because unset means "the same as the marker gap". */ + private Double leadingGap; private TimelineAxisSize axis = new TimelineAxisSize.Weight(0.10); private String axisDeclaredBy; private DocumentRowColumn leadingColumn; @@ -143,8 +146,11 @@ public TimelineBuilder rail(Consumer spec) { Objects.requireNonNull(spec, "spec"); TimelineRailBuilder builder = new TimelineRailBuilder(); spec.accept(builder); - if (builder.extent() != null) { - this.railExtent = builder.extent(); + if (builder.start() != null) { + this.railStart = builder.start(); + } + if (builder.end() != null) { + this.railEnd = builder.end(); } if (builder.stroke() == null) { return this; @@ -234,6 +240,34 @@ public TimelineBuilder markerGap(double gap) { return this; } + /** + * Sets the horizontal gap between the leading column and the marker. + * + *

Without this the gaps either side of the marker column are one number, and that + * over-constrains a three-column timeline. Writing {@code x0} for where the columns + * start, {@code L} for the leading width and {@code A} for the axis, one gap {@code s} + * puts the rail at {@code x0 + L + s + A/2} and the content at {@code x0 + L + 2s + A}; + * subtract them and {@code L = (rail − x0) − (s + A/2)}. The leading width is then + * decided by where the rail and the content sit, whatever {@code A} and {@code s} are + * given — so a design stating all three, as a dated timeline does, cannot have them. + * Nor can it recover them by padding the leading column, which only narrows what goes + * inside it, or by widening the axis, which re-pins {@code L} to the same number.

+ * + *

With the two gaps separate, {@code L} is the caller's and this absorbs the + * difference. Unset, it is {@link #markerGap(double)} — which is the single-gap layout + * exactly, so a timeline that does not ask for this is laid out as it was.

+ * + * @param gap gap in points; negative values are ignored + * @return this builder + * @since 2.4.0 + */ + public TimelineBuilder leadingGap(double gap) { + if (gap >= 0) { + this.leadingGap = gap; + } + return this; + } + /** * Sets the relative width of the marker column (its weight against a content * weight of 1.0). Increase it for large numbered discs on narrow timelines. @@ -471,21 +505,17 @@ private TimelineSpec normalize() { // One owner per timeline, allocated here. Every marker below anchors on this // instance, so the pass that draws the rail asks for it and gets these markers and // nobody else's — two timelines on a page never merge. - if (railExtent == TimelineRailExtent.TIMELINE_BOUNDS) { - throw new IllegalArgumentException( - "TimelineRailExtent.TIMELINE_BOUNDS is not implemented. On one page it is the " - + "same line as ENTRY_BOUNDS, and across pages there is nothing to measure it " - + "against — a timeline's own box draws nothing. Use ENTRY_BOUNDS or " - + "MARKER_TO_MARKER."); - } TimelineRailSpec railSpec = new TimelineRailSpec(railStroke); // The gutter is only knowable here, so the default anchor is resolved here too — // and it is the same model the opted-in one uses, not a branch beside it. TimelineMarkerAnchor anchor = markerAnchor == null ? TimelineMarkerAnchor.atLeftEdge(gutter) : markerAnchor; - return new TimelineSpec(new TimelineRailOwner(railSpec, railExtent, anchor), - railSpec, leadingColumn, gutter, markerGap, axis, anchor, entrySpacing, - keepTogether, keepEntriesTogether, List.copyOf(specs)); + // Unset resolves to the marker gap, which is the single-gap layout the two columns + // either side of the axis have always shared. + double resolvedLeadingGap = leadingGap == null ? markerGap : leadingGap; + return new TimelineSpec(new TimelineRailOwner(railSpec, railStart, railEnd, anchor), + railSpec, leadingColumn, resolvedLeadingGap, gutter, markerGap, axis, anchor, + entrySpacing, keepTogether, keepEntriesTogether, List.copyOf(specs)); } /** @@ -502,7 +532,9 @@ private static void layout(TimelineSpec spec, SectionBuilder timeline) { // only when the rail moved into the axis; with the rail beside it the body spans the // entry as it always has, and the header row publishes nothing. boolean bodyClearsTheAxis = spec.markerAnchor().railRunsThroughTheAxis(); - int contentColumn = spec.leadingColumn() == null ? 1 : 2; + // [axis][content] with no leading column; [leading][gap][axis][gap][content] with + // one, because the two gaps are columns rather than one row spacing. + int contentColumn = spec.leadingColumn() == null ? 1 : 4; List entries = spec.entries(); for (int i = 0; i < entries.size(); i++) { TimelineEntrySpec entry = entries.get(i); @@ -524,7 +556,6 @@ private static void layout(TimelineSpec spec, SectionBuilder timeline) { .padding(new DocumentInsets(0, 0, bottom, spec.gutter())) .spacing(4); Consumer headerSpec = header -> { - header.spacing(spec.markerGap()); DocumentRowColumn axis = column(spec.axis()); if (spec.leadingColumn() == null && spec.axis() instanceof TimelineAxisSize.Weight weight) { // The same two columns either way — columns(weight, weight) resolves @@ -532,17 +563,35 @@ private static void layout(TimelineSpec spec, SectionBuilder timeline) { // weights(...) is what a timeline has always put on its RowNode, and // RowNode.weights() is public; spelling it the other way empties that // list for every timeline that exists. Sugar where the sugar applies. + header.spacing(spec.markerGap()); header.weights(weight.weight(), 1.0); } else if (spec.leadingColumn() == null) { + header.spacing(spec.markerGap()); header.columns(axis, DocumentRowColumn.weight(1.0)); } else { - header.columns(spec.leadingColumn(), axis, DocumentRowColumn.weight(1.0)); + // Three columns need two gaps, and a row spaces every pair of its + // columns by one number — which is what tied the leading width to + // the rail and content positions. So the gaps are columns of their + // own and the row spaces nothing. With leadingGap defaulting to + // markerGap this resolves to the same widths the single spacing + // gave: fixed columns take their width either way, and the weighted + // remainder is the same subtraction in a different order. + header.spacing(0.0); + header.columns(spec.leadingColumn(), + DocumentRowColumn.fixed(spec.leadingGap()), + axis, + DocumentRowColumn.fixed(spec.markerGap()), + DocumentRowColumn.weight(1.0)); // Present even when this entry put nothing in it: the column is the // timeline's, not the entry's, and an entry that skipped it must // still start its marker where every other entry starts one. header.addSection(entry.leading() == null ? column -> { } : entry.leading()); + header.addSection(column -> { }); } header.addSection(anchoredMarker(spec, entry, index)); + if (spec.leadingColumn() != null) { + header.addSection(column -> { }); + } header.addSection(entry.beside()); }; diff --git a/core/src/main/java/com/demcha/compose/document/dsl/TimelineRailBuilder.java b/core/src/main/java/com/demcha/compose/document/dsl/TimelineRailBuilder.java index 2dfbeaf9a..8bd459f0d 100644 --- a/core/src/main/java/com/demcha/compose/document/dsl/TimelineRailBuilder.java +++ b/core/src/main/java/com/demcha/compose/document/dsl/TimelineRailBuilder.java @@ -23,7 +23,8 @@ public final class TimelineRailBuilder { private DocumentStroke stroke; - private TimelineRailExtent extent; + private TimelineRailEnd start; + private TimelineRailEnd end; TimelineRailBuilder() { } @@ -41,19 +42,37 @@ public TimelineRailBuilder stroke(DocumentStroke stroke) { } /** - * Sets how far the rail runs. + * Sets where the rail begins. * - *

Independent of where it runs: an extent is the line's two ends, and its x comes - * from the marker anchor. A timeline that sets neither keeps - * {@link TimelineRailExtent#ENTRY_BOUNDS}, which is what it already draws.

+ *

Independent of where it ends, and of where it runs: the two ends are chosen one at + * a time and the x comes from the marker anchor. A rail that is asked for neither end + * begins at {@link TimelineRailEnd#ENTRY_BOUND}, which is what a timeline already + * draws.

* - * @param extent the rail's extent + * @param start where the rail's first end sits * @return this builder - * @throws NullPointerException if {@code extent} is null + * @throws NullPointerException if {@code start} is null * @since 2.4.0 */ - public TimelineRailBuilder extent(TimelineRailExtent extent) { - this.extent = Objects.requireNonNull(extent, "extent"); + public TimelineRailBuilder from(TimelineRailEnd start) { + this.start = Objects.requireNonNull(start, "start"); + return this; + } + + /** + * Sets where the rail ends. + * + *

The companion of {@link #from(TimelineRailEnd)} and just as independent: a rail + * may begin on its first marker and still run to the foot of its last entry. Unasked, + * it ends at {@link TimelineRailEnd#ENTRY_BOUND}.

+ * + * @param end where the rail's last end sits + * @return this builder + * @throws NullPointerException if {@code end} is null + * @since 2.4.0 + */ + public TimelineRailBuilder to(TimelineRailEnd end) { + this.end = Objects.requireNonNull(end, "end"); return this; } @@ -62,8 +81,13 @@ DocumentStroke stroke() { return stroke; } - /** The extent this rail was given, or null when the caller set none. */ - TimelineRailExtent extent() { - return extent; + /** The start this rail was given, or null when the caller asked for none. */ + TimelineRailEnd start() { + return start; + } + + /** The end this rail was given, or null when the caller asked for none. */ + TimelineRailEnd end() { + return end; } } diff --git a/core/src/main/java/com/demcha/compose/document/dsl/TimelineRailEnd.java b/core/src/main/java/com/demcha/compose/document/dsl/TimelineRailEnd.java new file mode 100644 index 000000000..8dfd086e0 --- /dev/null +++ b/core/src/main/java/com/demcha/compose/document/dsl/TimelineRailEnd.java @@ -0,0 +1,44 @@ +package com.demcha.compose.document.dsl; + +/** + * Where one end of a timeline's rail sits. + * + *

The two ends are chosen separately, because a design that wants them the same is only + * one of four and was never the interesting case. A rail can begin at the first marker and + * still run to the foot of the last entry; that is a real design — a sidebar whose line + * starts at the first dot and carries on past the last one to close the block — and with a + * single value naming both ends it could not be asked for. The previous model had two + * symmetric constants and no way to mix them, so a template wanting this one painted the + * top of an entry-bounds rail over with the page colour and redrew the rest. Measured, that + * did not even work: an accent draws above the mask.

+ * + *

Nothing here says where the rail sits horizontally. That comes from the marker anchor, + * and the two are independent on purpose: an end moves the line's start or finish and never + * moves it sideways.

+ * + * @author Artem Demchyshyn + * @since 2.4.0 + */ +public enum TimelineRailEnd { + + /** + * The entries' own bound on that page — the top of the first entry at the start, the + * foot of the last at the end. + * + *

What a timeline written before there was a choice draws at both ends, to the + * point: measured against the per-entry border it replaces, the two agree to 0.000000 + * on every page. The gaps between entries are inside it, because an entry's spacing is + * padding within its own box, and there is no tail after the last entry because the + * last entry has no such padding.

+ */ + ENTRY_BOUND, + + /** + * The marker's anchor point — the first marker's at the start, the last's at the end. + * + *

No rail above the first marker or below the last. With one entry and both ends on + * the marker the extent is zero and no rail is drawn at all, because a zero-length line + * is not a shorter line.

+ */ + MARKER +} diff --git a/core/src/main/java/com/demcha/compose/document/dsl/TimelineRailExtent.java b/core/src/main/java/com/demcha/compose/document/dsl/TimelineRailExtent.java deleted file mode 100644 index da2f6a5c5..000000000 --- a/core/src/main/java/com/demcha/compose/document/dsl/TimelineRailExtent.java +++ /dev/null @@ -1,49 +0,0 @@ -package com.demcha.compose.document.dsl; - -/** - * How far a timeline's rail runs. - * - *

The two ends of the line, and nothing about where it sits horizontally — that comes - * from the marker anchor. The two are independent on purpose: a rail can start and stop at - * the markers while passing through their left edges, or span the entries while passing - * through their centres.

- * - * @author Artem Demchyshyn - * @since 2.4.0 - */ -public enum TimelineRailExtent { - - /** - * From the first marker's anchor point to the last's. - * - *

No rail above the first marker or below the last. With a single entry the extent - * is zero and no rail is drawn at all — a zero-length line is not a shorter line.

- */ - MARKER_TO_MARKER, - - /** - * The union of the entries' resolved boxes, page by page. - * - *

What a timeline written before there was a choice already draws, to the point: - * measured against the per-entry border it replaces, the two agree to 0.000000 in both - * ends on every page. The gaps between entries are inside it, because an entry's - * spacing is padding within its own box; there is no tail after the last entry, - * because the last entry has no such padding.

- * - *

Page by page is load-bearing. An entry that spans pages has a different - * extent on each of them, and its box as a whole is a coordinate belonging to no - * page.

- */ - ENTRY_BOUNDS, - - /** - * The timeline's own box, rather than the union of the entries in it. - * - *

Defined but not implemented: it is indistinguishable from - * {@link #ENTRY_BOUNDS} on a single page — a timeline has no padding of its own — and - * across pages there is nothing to measure it against, because the container draws - * nothing. Asking for it throws rather than quietly resolving to the neighbour it - * happens to equal in the easy case.

- */ - TIMELINE_BOUNDS -} diff --git a/core/src/main/java/com/demcha/compose/document/dsl/TimelineRailOwner.java b/core/src/main/java/com/demcha/compose/document/dsl/TimelineRailOwner.java index 5a2af7fc4..8608f4447 100644 --- a/core/src/main/java/com/demcha/compose/document/dsl/TimelineRailOwner.java +++ b/core/src/main/java/com/demcha/compose/document/dsl/TimelineRailOwner.java @@ -42,12 +42,15 @@ final class TimelineRailOwner implements ResolvedLayoutPass { private static final double EPS = 1e-9; private final TimelineRailSpec rail; - private final TimelineRailExtent extent; + private final TimelineRailEnd start; + private final TimelineRailEnd end; private final TimelineMarkerAnchor markerAnchor; - TimelineRailOwner(TimelineRailSpec rail, TimelineRailExtent extent, TimelineMarkerAnchor markerAnchor) { + TimelineRailOwner(TimelineRailSpec rail, TimelineRailEnd start, TimelineRailEnd end, + TimelineMarkerAnchor markerAnchor) { this.rail = rail; - this.extent = extent; + this.start = start; + this.end = end; this.markerAnchor = markerAnchor; } @@ -103,11 +106,19 @@ public List contribute(LayoutGraph graph, ResolvedLayout /** * The rail's vertical extent, one segment per page it appears on. * - *

Both extents are built from the entries' resolved slices, because those already + *

Every segment starts as the entries' own resolved slices, because those already * carry the one thing neither the markers nor the node boxes do: what a page's content - * band is, on that page, after per-page margins. {@code MARKER_TO_MARKER} then trims - * the first and last of them back to the markers rather than deriving a band of its - * own.

+ * band is, on that page, after per-page margins. An end asked for on the marker then + * trims that page's slice back to the marker rather than deriving a band of its own — + * and only that page's, which is what lets the two ends be chosen one at a time. A + * middle page is trimmed by neither, because neither the first marker nor the last is + * on it.

+ * + *

Trimming back is the whole of it: {@code min} at the top and {@code max} + * at the bottom, so an end on a marker can only shorten the line, never invent rail + * outside the entries. A page past the marker that bounds it drops out for the same + * reason — with the start on the first marker there is nothing to draw on a page before + * it.

*/ private List segments(ResolvedLayoutMetadata metadata, List markers) { Map byPage = new LinkedHashMap<>(); @@ -116,7 +127,9 @@ private List segments(ResolvedLayoutMetadata metadata, List segments(ResolvedLayoutMetadata metadata, List trimmed = new ArrayList<>(); for (Segment segment : byPage.values()) { - if (segment.page < first.pageIndex() || segment.page > last.pageIndex()) { + if (startsOnMarker && segment.page < first.pageIndex()) { + continue; + } + if (endsOnMarker && segment.page > last.pageIndex()) { continue; } - double top = segment.page == first.pageIndex() ? Math.min(segment.top, startY) : segment.top; - double bottom = segment.page == last.pageIndex() ? Math.max(segment.bottom, endY) : segment.bottom; + double top = startsOnMarker && segment.page == first.pageIndex() + ? Math.min(segment.top, startY) + : segment.top; + double bottom = endsOnMarker && segment.page == last.pageIndex() + ? Math.max(segment.bottom, endY) + : segment.bottom; trimmed.add(new Segment(segment.page, top, bottom)); } return trimmed; diff --git a/core/src/main/java/com/demcha/compose/document/dsl/TimelineRailSpec.java b/core/src/main/java/com/demcha/compose/document/dsl/TimelineRailSpec.java index 6677091ad..ed6c1dd8d 100644 --- a/core/src/main/java/com/demcha/compose/document/dsl/TimelineRailSpec.java +++ b/core/src/main/java/com/demcha/compose/document/dsl/TimelineRailSpec.java @@ -10,7 +10,7 @@ * configuration for the layout to read rather than an old shape and a new one.

* *

A stroke is all it takes to describe, because the rail's geometry is not here: how far - * it runs is a {@link TimelineRailExtent} and where it runs comes from the marker anchor, + * it runs is a {@link TimelineRailEnd} at each end and where it runs comes from the marker anchor, * both resolved after layout. This is only what it is drawn with.

* * @param stroke the rail's colour and width diff --git a/core/src/main/java/com/demcha/compose/document/dsl/TimelineSpec.java b/core/src/main/java/com/demcha/compose/document/dsl/TimelineSpec.java index eefe9b6c9..3f8f613bd 100644 --- a/core/src/main/java/com/demcha/compose/document/dsl/TimelineSpec.java +++ b/core/src/main/java/com/demcha/compose/document/dsl/TimelineSpec.java @@ -18,6 +18,8 @@ * @param rail the connector rail * @param leadingColumn how wide the column before the marker is, or null when the * timeline has no leading column at all + * @param leadingGap horizontal gap between the leading column and the marker + * column; carries no meaning without a leading column * @param gutter space between the rail and the entry's content * @param markerGap horizontal gap between the marker column and the content * beside it @@ -32,6 +34,7 @@ record TimelineSpec(TimelineRailOwner owner, TimelineRailSpec rail, DocumentRowColumn leadingColumn, + double leadingGap, double gutter, double markerGap, TimelineAxisSize axis, diff --git a/core/src/main/java/com/demcha/compose/document/layout/LayoutCompiler.java b/core/src/main/java/com/demcha/compose/document/layout/LayoutCompiler.java index a518eea7e..bb4872714 100644 --- a/core/src/main/java/com/demcha/compose/document/layout/LayoutCompiler.java +++ b/core/src/main/java/com/demcha/compose/document/layout/LayoutCompiler.java @@ -275,8 +275,31 @@ private void compileComposite(PreparedNode prepared, boolean keepWhole = node.keepTogether() && outerHeight <= state.activeInnerHeight() + CAPACITY_TOLERANCE; double startReservation = margin.top() + padding.top(); + + // A box must not open on a page its first indivisible unit cannot start on. + // The box is recorded here and its children are placed afterwards, so when the + // first thing inside it has to move — it asked to be kept whole, or it is + // indivisible and too tall for the space left — the box stays behind spanning a + // page it holds nothing on. Every consumer of that box then believes it: a + // section's accent paints a stub at the foot of the page beside nothing, and + // anything deriving an extent from it runs to the bottom margin, which is how a + // timeline's rail grew a tail below its last entry. + // + // So the box relocates too. This is the keep-together rule above asked of the + // leading unit instead of the whole node, and the same fallback applies: a unit + // that would not fit a fresh page either has nothing to gain from the break, so + // it flows where it stands. A box whose first unit does fit is untouched, which + // is why this moves no content — the content had already moved. + double leadingUnit = leadingUnitHeight(node, regionWidth, prepareContext, + state.activeInnerHeight()); + boolean relocateWithLeadingUnit = + leadingUnit > state.remainingHeight() + EPS + && leadingUnit <= state.activeInnerHeight() + CAPACITY_TOLERANCE; + if (keepWhole && outerHeight > state.remainingHeight() + EPS && state.usedHeight > EPS) { state.newPage(); + } else if (relocateWithLeadingUnit && state.usedHeight > EPS) { + state.newPage(); } else if (startReservation > state.remainingHeight() + EPS && state.usedHeight > EPS) { state.newPage(); } diff --git a/core/src/main/java/com/demcha/compose/document/layout/ListItemNormalizer.java b/core/src/main/java/com/demcha/compose/document/layout/ListItemNormalizer.java index 9fb9aa229..0e3561801 100644 --- a/core/src/main/java/com/demcha/compose/document/layout/ListItemNormalizer.java +++ b/core/src/main/java/com/demcha/compose/document/layout/ListItemNormalizer.java @@ -77,9 +77,12 @@ private static void normalizeNested(ListNode node, ? item.marker() : ListMarker.defaultForDepth(depth); String content = ListMarker.normalizeItemText(item.label(), node.normalizeMarkers()); - boolean rendered = rendersSomething(marker, content); + // A rich item renders whatever its runs draw, which its label — their + // plain-text reading — need not describe: runs of an icon and a chip + // carry no text at all and are still content. + boolean rendered = item.isRich() || rendersSomething(marker, content); if (rendered) { - out.add(new ListItemSpec(depth, marker, content)); + out.add(new ListItemSpec(depth, marker, content, item.runs())); } // Children are walked either way: an item that draws nothing is a // reason to skip that one row, never a reason to lose the sub-tree diff --git a/core/src/main/java/com/demcha/compose/document/layout/ListMarkerGeometry.java b/core/src/main/java/com/demcha/compose/document/layout/ListMarkerGeometry.java index a7316b130..c01ee1497 100644 --- a/core/src/main/java/com/demcha/compose/document/layout/ListMarkerGeometry.java +++ b/core/src/main/java/com/demcha/compose/document/layout/ListMarkerGeometry.java @@ -2,6 +2,8 @@ import com.demcha.compose.document.layout.payloads.ListItemSpec; import com.demcha.compose.document.layout.payloads.MarkerContentItem; +import com.demcha.compose.document.layout.payloads.ParagraphLine; +import com.demcha.compose.document.node.ListMarker; import com.demcha.compose.document.node.ListNode; import com.demcha.compose.engine.components.content.text.TextStyle; import com.demcha.compose.engine.measurement.TextMeasurementSystem; @@ -45,12 +47,16 @@ private ListMarkerGeometry() { * @param node the list, for its marker gap and text style * @param availableItemWidth width one row may occupy, inside the list padding * @param measurement text measurement service + * @param drawnMarkers the already-measured single line of each drawn + * marker used in the list, keyed by the marker; + * empty when every marker is text * @return one resolved item per spec, in the same order */ static List resolve(List specs, ListNode node, double availableItemWidth, - TextMeasurementSystem measurement) { + TextMeasurementSystem measurement, + Map drawnMarkers) { if (specs.isEmpty()) { return List.of(); } @@ -75,10 +81,20 @@ static List resolve(List specs, double markerX = depth == 0 ? 0.0 : contentXByDepth[depth - 1]; boolean hasMarker = spec.hasMarker(); - double markerWidth = hasMarker - ? markerWidths.computeIfAbsent(spec.markerText(), - text -> measurement.textWidth(style, text)) - : 0.0; + // A drawn marker was measured as a line of inline runs before this + // loop, by the same pipeline that measures the item's own content, so + // its width is the width of what it actually draws — a disc's + // diameter, an icon's box — rather than of any text. + ParagraphLine drawn = drawnMarkers.get(spec.marker()); + double markerWidth; + if (!hasMarker) { + markerWidth = 0.0; + } else if (drawn != null) { + markerWidth = drawn.width(); + } else { + markerWidth = markerWidths.computeIfAbsent(spec.markerText(), + text -> measurement.textWidth(style, text)); + } double gap = hasMarker ? node.markerGap() : 0.0; double contentX = markerX + markerWidth + gap; // Floored at the width the text pipeline already treats as its @@ -91,7 +107,8 @@ static List resolve(List specs, ParagraphWrapping.MIN_TEXT_WIDTH, availableItemWidth - contentX); contentXByDepth[depth] = contentX; - out.add(new MarkerContentItem(spec, markerX, markerWidth, gap, contentX, contentWidth)); + out.add(new MarkerContentItem(spec, markerX, markerWidth, gap, contentX, contentWidth, + drawn == null || !hasMarker ? List.of() : drawn.spans())); } return List.copyOf(out); } diff --git a/core/src/main/java/com/demcha/compose/document/layout/TextFlowSupport.java b/core/src/main/java/com/demcha/compose/document/layout/TextFlowSupport.java index 58a4a7345..74e2b5f41 100644 --- a/core/src/main/java/com/demcha/compose/document/layout/TextFlowSupport.java +++ b/core/src/main/java/com/demcha/compose/document/layout/TextFlowSupport.java @@ -15,7 +15,9 @@ import com.demcha.compose.engine.measurement.TextMeasurementSystem; import java.util.ArrayList; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; import static com.demcha.compose.document.layout.DocumentNodeAdapters.*; import static com.demcha.compose.document.layout.NodeDefinitionSupport.EPS; @@ -41,6 +43,13 @@ public final class TextFlowSupport { */ private static final String NESTED_LIST_INDENT_UNIT = "  "; + /** + * The width a drawn list marker is measured at — wide enough that nothing a + * marker could reasonably be will wrap, so its measured width is the width of + * what it draws rather than of the column it happens to sit in. + */ + private static final double MARKER_MEASURE_WIDTH = 100_000.0; + // ------------------------------------------------------------------ // Paragraph entry points // ------------------------------------------------------------------ @@ -208,6 +217,9 @@ public static PreparedNode prepareList(ListNode node, private static PreparedNode prepareLegacyPrefixList(ListNode node, PrepareContext ctx, BoxConstraints constraints) { + // The list's own marker reaches a flat list without going through the + // nested walk, so it is checked here as well as there. + refuseDrawnMarker(node.marker()); ListNode effective = node.nestedItems().isEmpty() ? node : flattenNestedListNode(node); @@ -233,12 +245,32 @@ private static PreparedNode prepareMarkerContentList(ListNode node, PrepareContext ctx, BoxConstraints constraints) { double availableItemWidth = Math.max(0.0, constraints.availableWidth() - node.padding().horizontal()); + List specs = ListItemNormalizer.normalize(node); List geometry = ListMarkerGeometry.resolve( - ListItemNormalizer.normalize(node), node, availableItemWidth, ctx.textMeasurement()); + specs, node, availableItemWidth, ctx.textMeasurement(), prepareDrawnMarkers(specs, node, ctx)); List items = new ArrayList<>(geometry.size()); for (MarkerContentItem item : geometry) { - ParagraphNode content = new ParagraphNode( + // A rich item's content is its runs, wrapped by the same inline + // algorithm a paragraph uses. That is the point of expressing it as + // runs rather than as a second rich-text model: the marker column, + // the gap and contentX resolve for it exactly as for a plain item, + // and the wrapping inside that width is the paragraph's own. + ParagraphNode content = item.spec().isRich() + ? new ParagraphNode( + "", + item.content(), + item.spec().runs(), + node.textStyle(), + node.align(), + node.lineSpacing(), + "", + DocumentTextIndent.NONE, + null, + null, + DocumentInsets.zero(), + DocumentInsets.zero()) + : new ParagraphNode( "", item.content(), node.textStyle(), @@ -268,6 +300,56 @@ private static PreparedNode prepareMarkerContentList(ListNode node, new PreparedListLayout(items, maxLineWidth, totalHeight, resolvedWidth, geometry)); } + /** + * Measures each distinct drawn marker in the list as one line of inline + * runs, through the pipeline that measures the items' own content. + * + *

A marker's width has to be known before any content origin can be, so + * this runs ahead of {@link ListMarkerGeometry}. Measuring it as a paragraph + * line rather than as a special case is what makes a disc, an icon and a + * coloured glyph all resolve to the width of the thing they draw, and what + * lets the emit phase place the marker's pieces with spans the renderers + * already know how to draw.

+ * + *

Measured unbounded, because a marker is one line by definition and a + * marker wider than its column overflows — the same answer a too-wide text + * marker already gets, rather than wrapping into a second line the row has + * no space for.

+ */ + private static Map prepareDrawnMarkers(List specs, + ListNode node, + PrepareContext ctx) { + Map lines = null; + for (ListItemSpec spec : specs) { + ListMarker marker = spec.marker(); + if (!marker.isRich()) { + continue; + } + if (lines == null) { + lines = new LinkedHashMap<>(); + } else if (lines.containsKey(marker)) { + // A list shows the same handful of markers over and over, one per + // depth, so each distinct one is measured once for the whole list. + continue; + } + ParagraphNode asParagraph = new ParagraphNode( + "", "", marker.runs(), node.textStyle(), TextAlign.LEFT, 0.0, "", + DocumentTextIndent.NONE, null, null, + DocumentInsets.zero(), DocumentInsets.zero()); + PreparedParagraphLayout layout = prepareParagraphLayout( + asParagraph, MARKER_MEASURE_WIDTH, ctx.textMeasurement(), false); + if (layout.visualLines().size() != 1) { + throw new IllegalStateException( + "a list marker is one line: \"" + marker.value() + "\" measured " + + layout.visualLines().size() + " lines. A marker sits on the item's " + + "first line and shares its baseline, so it has nowhere to put a " + + "second one"); + } + lines.put(marker, layout.visualLines().get(0)); + } + return lines == null ? Map.of() : lines; + } + /** * Widest point any row reaches — the marker column and the content column * are both candidates, and a marker-only row is measured by its marker. @@ -318,9 +400,39 @@ private static ListNode flattenNestedListNode(ListNode node) { node.markerGap()); } + /** + * Refuses a drawn marker on the legacy layout, where a marker is characters + * at the front of the item's text and a drawing has nowhere to go. Its plain + * reading is empty for a marker that draws only a disc or an icon, so + * rendering that would leave the list markerless with no signal at all. + */ + private static void refuseDrawnMarker(ListMarker marker) { + if (marker != null && marker.isRich()) { + throw new IllegalStateException( + "a drawn list marker needs marker/content geometry: call " + + "hangingIndent(true) on the list. The legacy layout puts the marker " + + "inside the item's text, which can hold characters but not a drawing"); + } + } + private static void flattenNestedItems(List items, int depth, List output) { for (ListItem item : items) { + // A rich item's content is a sequence of independently styled runs and + // a label is a single string: there is no flattening of one into the + // other that keeps what the author asked for. Rendering the plain + // reading instead would silently drop every style, icon and chip, + // which is a worse answer than saying so. This is the only walk a rich + // item can reach the legacy layout through, so the check belongs here + // rather than in a second pass over the tree. + if (item.isRich()) { + throw new IllegalStateException( + "a list item made of inline runs needs marker/content geometry: call " + + "hangingIndent(true) on the list. The legacy layout makes an item's " + + "marker part of its text and so carries one style for the whole item, " + + "which cannot hold the runs of \"" + item.label() + "\""); + } ListMarker marker = item.marker() != null ? item.marker() : ListMarker.defaultForDepth(depth); + refuseDrawnMarker(marker); StringBuilder prefix = new StringBuilder(NESTED_LIST_INDENT_UNIT.repeat(depth)); if (marker.isVisible()) { // ListMarker.normalize already appends a trailing space @@ -502,6 +614,21 @@ private static ParagraphLine markerLine(ListNode node, MarkerContentItem geometry) { ParagraphLine first = itemLayout.visualLines().get(0); String text = geometry.markerText(); + // A drawn marker's pieces were measured already, as inline runs. They are + // placed in a line whose metrics are still the content's first line, so a + // disc or an icon rides the item's own baseline exactly as a glyph does + // and does not make the row taller; one drawn larger than the line + // overflows it, the answer a marker wider than its column already gets. + List spans = geometry.hasDrawnMarker() + ? geometry.markerSpans() + : List.of(new ParagraphTextSpan( + text, + toTextStyle(node.textStyle()), + geometry.measuredMarkerWidth(), + first.textLineHeight(), + null, + null, + false)); return new ParagraphLine( text, geometry.measuredMarkerWidth(), @@ -509,14 +636,7 @@ private static ParagraphLine markerLine(ListNode node, first.textLineHeight(), first.textAscent(), first.baselineOffsetFromBottom(), - List.of(new ParagraphTextSpan( - text, - toTextStyle(node.textStyle()), - geometry.measuredMarkerWidth(), - first.textLineHeight(), - null, - null, - false))); + spans); } private static ParagraphFragmentPayload paragraphPayload(ListNode node, diff --git a/core/src/main/java/com/demcha/compose/document/layout/payloads/ListItemSpec.java b/core/src/main/java/com/demcha/compose/document/layout/payloads/ListItemSpec.java index 5ec2bd98c..8dc57b214 100644 --- a/core/src/main/java/com/demcha/compose/document/layout/payloads/ListItemSpec.java +++ b/core/src/main/java/com/demcha/compose/document/layout/payloads/ListItemSpec.java @@ -1,7 +1,9 @@ package com.demcha.compose.document.layout.payloads; +import com.demcha.compose.document.node.InlineRun; import com.demcha.compose.document.node.ListMarker; +import java.util.List; import java.util.Objects; /** @@ -36,7 +38,7 @@ * @author Artem Demchyshyn * @since 2.4.0 */ -public record ListItemSpec(int depth, ListMarker marker, String content) { +public record ListItemSpec(int depth, ListMarker marker, String content, List runs) { /** * Normalizes nullable inputs and rejects a negative depth. @@ -49,6 +51,27 @@ public record ListItemSpec(int depth, ListMarker marker, String content) { } marker = marker == null ? ListMarker.none() : marker; content = content == null ? "" : content; + runs = runs == null ? List.of() : List.copyOf(runs); + } + + /** + * Creates a spec whose content is plain text. + * + * @param depth nesting depth + * @param marker the item's resolved marker + * @param content the item's text + */ + public ListItemSpec(int depth, ListMarker marker, String content) { + this(depth, marker, content, List.of()); + } + + /** + * Returns whether this item's content is runs rather than plain text. + * + * @return {@code true} when the item carries inline runs + */ + public boolean isRich() { + return !runs.isEmpty(); } /** diff --git a/core/src/main/java/com/demcha/compose/document/layout/payloads/MarkerContentItem.java b/core/src/main/java/com/demcha/compose/document/layout/payloads/MarkerContentItem.java index 066028c69..ff7aa7a3f 100644 --- a/core/src/main/java/com/demcha/compose/document/layout/payloads/MarkerContentItem.java +++ b/core/src/main/java/com/demcha/compose/document/layout/payloads/MarkerContentItem.java @@ -1,5 +1,6 @@ package com.demcha.compose.document.layout.payloads; +import java.util.List; import java.util.Objects; /** @@ -40,6 +41,11 @@ * unexplained inset * @param contentX {@code markerX + measuredMarkerWidth + markerGap} * @param contentWidth the width the item's text wraps within + * @param markerSpans the measured pieces a drawn marker is made of — + * a disc, an icon, a glyph in a colour of its own — + * and empty for a text marker, whose one span the + * emit phase builds from the item's own first line + * so that it shares that line's metrics * @author Artem Demchyshyn * @since 2.4.0 */ @@ -49,7 +55,8 @@ public record MarkerContentItem( double measuredMarkerWidth, double markerGap, double contentX, - double contentWidth + double contentWidth, + List markerSpans ) { /** @@ -62,6 +69,7 @@ public record MarkerContentItem( */ public MarkerContentItem { Objects.requireNonNull(spec, "spec"); + markerSpans = markerSpans == null ? List.of() : List.copyOf(markerSpans); requireFinite(markerX, "markerX"); requireFinite(measuredMarkerWidth, "measuredMarkerWidth"); requireFinite(markerGap, "markerGap"); @@ -78,6 +86,34 @@ public record MarkerContentItem( } } + /** + * Creates an item whose marker is text, so it has no pre-measured spans. + * + * @param spec the item's authored depth, marker and content + * @param markerX where the marker starts + * @param measuredMarkerWidth the marker's measured width + * @param markerGap the gap applied between marker and content + * @param contentX {@code markerX + measuredMarkerWidth + markerGap} + * @param contentWidth the width the item's text wraps within + */ + public MarkerContentItem(ListItemSpec spec, + double markerX, + double measuredMarkerWidth, + double markerGap, + double contentX, + double contentWidth) { + this(spec, markerX, measuredMarkerWidth, markerGap, contentX, contentWidth, List.of()); + } + + /** + * Returns whether this item's marker is drawn rather than typed. + * + * @return {@code true} when the marker has pre-measured spans + */ + public boolean hasDrawnMarker() { + return !markerSpans.isEmpty(); + } + private static void requireFinite(double value, String name) { if (Double.isNaN(value) || Double.isInfinite(value) || value < 0) { throw new IllegalArgumentException(name + " must be finite and non-negative: " + value); diff --git a/core/src/main/java/com/demcha/compose/document/node/InlineRun.java b/core/src/main/java/com/demcha/compose/document/node/InlineRun.java index dce057960..9fe17454c 100644 --- a/core/src/main/java/com/demcha/compose/document/node/InlineRun.java +++ b/core/src/main/java/com/demcha/compose/document/node/InlineRun.java @@ -1,5 +1,7 @@ package com.demcha.compose.document.node; +import java.util.List; + /** * Marker for a single inline run inside a {@link ParagraphNode}. * @@ -14,4 +16,74 @@ */ public sealed interface InlineRun permits InlineTextRun, InlineImageRun, InlineShapeRun, InlineSvgRun, InlineHighlightRun { + + /** + * Returns what a sequence of runs reads as in plain text. + * + *

Text runs and highlight chips contribute their text; image, shape and + * SVG runs contribute nothing, because they are not text and inventing a + * placeholder for them would put characters in the reading that the author + * never wrote.

+ * + *

This lives next to the {@code permits} clause on purpose. Which + * variants read as text is knowledge that has to be revisited every time a + * variant is added, and a second copy of it elsewhere is a copy that will + * be missed: the reduction is here once, and both + * {@link ParagraphNode#text()} and a rich + * {@link com.demcha.compose.document.node.ListItem#label()} are derived + * from it.

+ * + * @param runs runs in source order; {@code null} reads as empty + * @return the concatenated text of the runs that carry text + * @since 2.4.0 + */ + static String plainText(List runs) { + if (runs == null || runs.isEmpty()) { + return ""; + } + StringBuilder out = new StringBuilder(); + for (InlineRun run : runs) { + if (run instanceof InlineTextRun textRun) { + out.append(textRun.text()); + } else if (run instanceof InlineHighlightRun highlight) { + out.append(highlight.text()); + } + } + return out.toString(); + } + + /** + * Returns the runs that carry text, in source order, for the surfaces that + * consume text and its styling but cannot draw a picture — the semantic DOCX + * export, and text-only tests. + * + *

Image, shape and SVG runs are dropped (emoji lower to SVG runs, so they + * drop too). A highlight chip degrades to a plain text run, because its + * background is a fixed-layout decoration while its text is content, and its + * newlines collapse to spaces to match how the PDF tokenizer lowers a chip — + * one line — so both surfaces read a chip the same way.

+ * + *

That collapsing is the one thing this does not share with + * {@link #plainText(List)}, which concatenates a chip's text as authored.

+ * + * @param runs runs in source order; {@code null} reads as empty + * @return the text-carrying runs, chips degraded to plain runs + * @since 2.4.0 + */ + static List textRuns(List runs) { + if (runs == null || runs.isEmpty()) { + return List.of(); + } + List textRuns = new java.util.ArrayList<>(runs.size()); + for (InlineRun run : runs) { + if (run instanceof InlineTextRun textRun) { + textRuns.add(textRun); + } else if (run instanceof InlineHighlightRun highlight) { + String chipText = highlight.text() + .replace("\r\n", " ").replace('\r', ' ').replace('\n', ' '); + textRuns.add(new InlineTextRun(chipText, highlight.textStyle(), highlight.linkTarget())); + } + } + return List.copyOf(textRuns); + } } diff --git a/core/src/main/java/com/demcha/compose/document/node/ListItem.java b/core/src/main/java/com/demcha/compose/document/node/ListItem.java index 7a17a0357..4799bd7bc 100644 --- a/core/src/main/java/com/demcha/compose/document/node/ListItem.java +++ b/core/src/main/java/com/demcha/compose/document/node/ListItem.java @@ -16,21 +16,89 @@ * item. Direct {@code new ListItem(...)} construction is supported but * should be reserved for record-shaped fixtures and serialization.

* - * @param label visible item text + *

An item's content is either its {@code label} or, when it needs more than + * one style, a sequence of {@code runs} — the same {@link InlineRun} sequence a + * paragraph is made of, so {@code "Label: description"} with the label bold is + * one list item rather than a hand-built row of two columns. When {@code runs} + * is non-empty it is the content, and {@code label} is its plain-text reading, + * kept so that anything reading an item as text still can.

+ * + * @param label visible item text; the plain-text reading when {@code runs} is + * set + * @param runs inline runs making up the content, empty when the item is + * just its label * @param marker per-item marker override, or {@code null} to inherit * the per-depth default from the parent list * @param children nested child items, empty for leaves * @author Artem Demchyshyn */ -public record ListItem(String label, ListMarker marker, List children) { +public record ListItem(String label, List runs, ListMarker marker, + List children) { /** - * Normalizes nullable inputs and copy-protects {@code children}. + * Normalizes nullable inputs and copy-protects {@code runs} and {@code children}. */ public ListItem { label = label == null ? "" : label; + runs = runs == null ? List.of() : List.copyOf(runs); children = children == null ? List.of() : List.copyOf(children); } + /** + * Creates an item whose content is its label, which is all an item could + * carry before it could carry runs. + * + *

Kept as its own constructor rather than folded into the canonical one, + * so code written against the three-argument shape still compiles and still + * links.

+ * + * @param label visible item text + * @param marker per-item marker override, or {@code null} to inherit + * @param children nested child items, empty for leaves + */ + public ListItem(String label, ListMarker marker, List children) { + this(label, List.of(), marker, children); + } + + /** + * Creates a leaf item whose content is a sequence of inline runs, reading as + * whatever those runs say. + * + * @param runs the runs making up the content + * @return leaf item + * @since 2.4.0 + */ + public static ListItem ofRuns(List runs) { + return new ListItem(InlineRun.plainText(runs), runs, null, List.of()); + } + + /** + * Creates a leaf item whose content is a sequence of inline runs, reading as + * the given label. + * + *

Use this over {@link #ofRuns(List)} when the runs draw something that + * does not read as itself — a row that is an icon and a chip has no text to + * derive a label from, and {@code label} is then what anything reading the + * item as text gets.

+ * + * @param label plain-text reading of the runs + * @param runs the runs making up the content + * @return leaf item + * @since 2.4.0 + */ + public static ListItem ofRuns(String label, List runs) { + return new ListItem(label, runs, null, List.of()); + } + + /** + * Returns whether this item's content is runs rather than its label alone. + * + * @return {@code true} when the item carries inline runs + * @since 2.4.0 + */ + public boolean isRich() { + return !runs.isEmpty(); + } + /** * Creates a leaf item with the given label and inherited marker. * diff --git a/core/src/main/java/com/demcha/compose/document/node/ListMarker.java b/core/src/main/java/com/demcha/compose/document/node/ListMarker.java index fa19caa25..e8cfdbc90 100644 --- a/core/src/main/java/com/demcha/compose/document/node/ListMarker.java +++ b/core/src/main/java/com/demcha/compose/document/node/ListMarker.java @@ -1,22 +1,74 @@ package com.demcha.compose.document.node; +import java.util.List; + /** * Marker rendered before each item in a canonical list node. * *

The marker keeps the public list API small while still allowing custom * list prefixes for CVs, reports, and template-specific layouts.

* - * @param value visible marker prefix, or an empty string for markerless lists + *

A marker is either a piece of text — {@code value}, which is all a marker + * could be before it could be drawn — or a sequence of {@code runs}: the same + * {@link InlineRun} sequence a paragraph and a list item are made of. Runs are + * what let a marker be a coloured disc, an icon or a glyph in a face and colour + * of its own, independent of the item beside it. When {@code runs} is non-empty + * it is the marker, and {@code value} is its plain-text reading.

+ * + * @param value visible marker prefix, or an empty string for markerless lists; + * the plain-text reading when {@code runs} is set + * @param runs inline runs the marker draws, empty when the marker is text * @author Artem Demchyshyn */ -public record ListMarker(String value) { +public record ListMarker(String value, List runs) { /** - * Creates a normalized marker. + * Normalizes the text form and copy-protects the runs. * * @param value visible marker value, or {@code null} for no marker + * @param runs inline runs, or {@code null} for a text marker */ public ListMarker { value = normalize(value); + runs = runs == null ? List.of() : List.copyOf(runs); + } + + /** + * Creates a marker that is a piece of text, which is all a marker could be + * before it could be drawn. + * + *

Kept as its own constructor rather than folded into the canonical one, + * so code written against the one-argument shape still compiles and still + * links.

+ * + * @param value visible marker value, or {@code null} for no marker + */ + public ListMarker(String value) { + this(value, List.of()); + } + + /** + * Creates a marker drawn from a sequence of inline runs. + * + *

Its plain-text reading is whatever text the runs carry, which for a + * marker that draws only a disc or an icon is nothing — the reading is what + * a surface that cannot draw falls back to, not the marker itself.

+ * + * @param runs the runs the marker draws + * @return drawn marker + * @since 2.4.0 + */ + public static ListMarker ofRuns(List runs) { + return new ListMarker(InlineRun.plainText(runs), runs); + } + + /** + * Returns whether this marker is drawn from runs rather than being text. + * + * @return {@code true} when the marker carries inline runs + * @since 2.4.0 + */ + public boolean isRich() { + return !runs.isEmpty(); } /** @@ -121,12 +173,17 @@ public static String normalizeItemText(String value, boolean normalizeMarkers) { } /** - * Returns {@code true} when this marker has non-whitespace content. + * Returns {@code true} when this marker draws anything — non-whitespace + * text, or any run at all. + * + *

A drawn marker is visible whatever its reading says, which is the whole + * difference between a marker of a teal disc and a markerless item: both + * read as nothing, and only one of them puts ink on the page.

* * @return whether the marker is visible */ public boolean isVisible() { - return value.chars().anyMatch(ch -> !Character.isWhitespace(ch)); + return isRich() || value.chars().anyMatch(ch -> !Character.isWhitespace(ch)); } /** diff --git a/core/src/main/java/com/demcha/compose/document/node/ParagraphNode.java b/core/src/main/java/com/demcha/compose/document/node/ParagraphNode.java index 888f15f21..0f1ba15dd 100644 --- a/core/src/main/java/com/demcha/compose/document/node/ParagraphNode.java +++ b/core/src/main/java/com/demcha/compose/document/node/ParagraphNode.java @@ -60,15 +60,8 @@ public record ParagraphNode( inlineRuns = normalizeInlineRuns(inlineRuns); text = Objects.requireNonNullElse(text, ""); if (text.isBlank() && !inlineRuns.isEmpty()) { - StringBuilder concatenated = new StringBuilder(); - for (InlineRun run : inlineRuns) { - if (run instanceof InlineTextRun textRun) { - concatenated.append(textRun.text()); - } else if (run instanceof InlineHighlightRun highlight) { - concatenated.append(highlight.text()); - } - } - text = concatenated.toString(); + // One reduction, shared with a rich list item: see InlineRun.plainText. + text = InlineRun.plainText(inlineRuns); } textStyle = textStyle == null ? DocumentTextStyle.DEFAULT : textStyle; align = align == null ? TextAlign.LEFT : align; @@ -338,21 +331,8 @@ private static List normalizeInlineRuns(List runs) { * @return inline text runs in source order */ public List inlineTextRuns() { - if (inlineRuns.isEmpty()) { - return List.of(); - } - List textRuns = new java.util.ArrayList<>(inlineRuns.size()); - for (InlineRun run : inlineRuns) { - if (run instanceof InlineTextRun textRun) { - textRuns.add(textRun); - } else if (run instanceof InlineHighlightRun highlight) { - // Collapse newlines to spaces to match how the PDF tokenizer - // lowers a chip (it stays one line), so both text surfaces agree. - String chipText = highlight.text().replace("\r\n", " ").replace('\r', ' ').replace('\n', ' '); - textRuns.add(new InlineTextRun(chipText, highlight.textStyle(), highlight.linkTarget())); - } - } - return List.copyOf(textRuns); + // One reduction, shared with a rich list item: see InlineRun.textRuns. + return InlineRun.textRuns(inlineRuns); } } diff --git a/core/src/test/java/com/demcha/compose/document/dsl/TimelineBuilderTest.java b/core/src/test/java/com/demcha/compose/document/dsl/TimelineBuilderTest.java index 3a2967a7f..43f60460f 100644 --- a/core/src/test/java/com/demcha/compose/document/dsl/TimelineBuilderTest.java +++ b/core/src/test/java/com/demcha/compose/document/dsl/TimelineBuilderTest.java @@ -547,7 +547,7 @@ void axisWidthTakesOnlyAPositiveFiniteNumberOfPoints() { // --- the leading column -------------------------------------------------- @Test - void aLeadingColumnPutsAThirdColumnBeforeTheMarker() { + void aLeadingColumnPutsTheLeadingContentBeforeTheMarker() { SectionNode timeline = timelineOf(t -> t .leadingColumn(DocumentRowColumn.fixed(48)) .entry(e -> e @@ -555,15 +555,24 @@ void aLeadingColumnPutsAThirdColumnBeforeTheMarker() { .leading(date -> date.addParagraph("2023")) .title("Senior Engineer"))); + // Five columns, because each gap beside the marker is a column of its own: a row + // spaces every pair of its columns by one number, and two gaps that have to differ + // cannot both be that number. Indices 1 and 3 are those gaps and hold nothing. RowNode header = header(entry(timeline, 0)); - assertThat(header.children()).hasSize(3); + assertThat(header.children()).hasSize(5); assertThat(paragraphTexts(header.children().get(0))) .as("leading first, before the marker") .containsExactly("2023"); - assertThat(markerContent(header.children().get(1))) - .as("then the marker") + assertThat(markerContent(header.children().get(2))) + .as("then the marker, a gap after the leading") .isInstanceOf(EllipseNode.class); - assertThat(paragraphTexts(header.children().get(2))).containsExactly("Senior Engineer"); + assertThat(paragraphTexts(header.children().get(4))) + .as("and the content, a gap after the marker") + .containsExactly("Senior Engineer"); + assertThat(paragraphTexts(header.children().get(1))) + .as("the gaps carry nothing") + .isEmpty(); + assertThat(paragraphTexts(header.children().get(3))).isEmpty(); } @Test @@ -576,15 +585,16 @@ void anEntryWithNoLeadingContentStillGetsTheColumn() { .leading(date -> date.addParagraph("2023")).title("With")) .entry(e -> e.marker(TimelineMarker.dot(8, NAVY)).title("Without"))); + RowNode withLeading = header(entry(timeline, 0)); RowNode withoutLeading = header(entry(timeline, 1)); assertThat(withoutLeading.children()) - .as("three columns either way") - .hasSize(3); + .as("the same columns either way") + .hasSameSizeAs(withLeading.children()); assertThat(paragraphTexts(withoutLeading.children().get(0))) - .as("the first is simply empty") + .as("the leading one is simply empty") .isEmpty(); - assertThat(markerContent(withoutLeading.children().get(1))) - .as("so the marker is still the second column, as in the entry above") + assertThat(markerContent(withoutLeading.children().get(2))) + .as("so the marker is in the column it is in above, and they line up") .isInstanceOf(EllipseNode.class); } diff --git a/core/src/test/java/com/demcha/compose/document/node/InlineRunPlainTextTest.java b/core/src/test/java/com/demcha/compose/document/node/InlineRunPlainTextTest.java new file mode 100644 index 000000000..48b59a4ee --- /dev/null +++ b/core/src/test/java/com/demcha/compose/document/node/InlineRunPlainTextTest.java @@ -0,0 +1,95 @@ +package com.demcha.compose.document.node; + +import com.demcha.compose.document.dsl.RichText; +import com.demcha.compose.document.style.DocumentColor; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * The two reductions of a run sequence, and the fact that every surface needing + * one shares them. + * + *

Which {@link InlineRun} variants carry text is knowledge that has to be + * revisited whenever a variant is added, so it lives in one place. These tests + * guard that: a second copy of the rule elsewhere would pass its own tests and + * drift from this one silently.

+ */ +class InlineRunPlainTextTest { + + private static final DocumentColor INK = DocumentColor.rgb(0x33, 0x66, 0x99); + private static final DocumentColor PAPER = DocumentColor.rgb(0xEE, 0xEE, 0xEE); + + @Test + void textAndChipsReadAsTheirTextAndPicturesReadAsNothing() { + List runs = RichText.empty() + .bold("Status: ") + .dot(6.0, INK) + .chip("pending", INK, PAPER) + .plain(" today") + .runs(); + + assertThat(InlineRun.plainText(runs)) + .as("the dot contributes nothing rather than a placeholder character") + .isEqualTo("Status: pending today"); + } + + @Test + void aNullOrEmptySequenceReadsAsNothing() { + assertThat(InlineRun.plainText(null)).isEmpty(); + assertThat(InlineRun.plainText(List.of())).isEmpty(); + assertThat(InlineRun.textRuns(null)).isEmpty(); + assertThat(InlineRun.textRuns(List.of())).isEmpty(); + } + + @Test + void textRunsKeepStylesDropPicturesAndFlattenAChipToOneLine() { + List runs = RichText.empty() + .bold("Head") + .dot(6.0, INK) + .chip("two\nlines", INK, PAPER) + .runs(); + + List textRuns = InlineRun.textRuns(runs); + assertThat(textRuns).as("the dot is dropped, the chip degrades to text").hasSize(2); + assertThat(textRuns.get(0).text()).isEqualTo("Head"); + assertThat(textRuns.get(0).textStyle()) + .as("a run keeps the style it was authored with") + .isNotNull(); + assertThat(textRuns.get(1).text()) + .as("a chip stays one line wherever it is read, so its newline is a space") + .isEqualTo("two lines"); + } + + @Test + void aParagraphsTextIsThatSameReading() { + List runs = RichText.empty() + .bold("Status: ") + .chip("pending", INK, PAPER) + .runs(); + ParagraphNode paragraph = new ParagraphNode("", "", runs, null, null, 0.0, "", + null, null, null, null, null); + + assertThat(paragraph.text()).isEqualTo(InlineRun.plainText(runs)); + assertThat(paragraph.inlineTextRuns()).isEqualTo(InlineRun.textRuns(runs)); + } + + @Test + void aRichListItemReadsAsThatSameReadingToo() { + List runs = RichText.empty() + .bold("Status: ") + .chip("pending", INK, PAPER) + .runs(); + + assertThat(ListItem.ofRuns(runs).label()).isEqualTo("Status: pending"); + assertThat(ListItem.ofRuns(runs).isRich()).isTrue(); + assertThat(ListItem.of("plain").isRich()) + .as("an item that is only a label is not rich, so it keeps the label path") + .isFalse(); + assertThat(ListItem.ofRuns("Done", runs).label()) + .as("an explicit reading wins, for runs that draw something that is not text") + .isEqualTo("Done"); + } +} diff --git a/core/src/test/java/com/demcha/documentation/CodeQlScopeGuardTest.java b/core/src/test/java/com/demcha/documentation/CodeQlScopeGuardTest.java index 7b587b377..2065bfca9 100644 --- a/core/src/test/java/com/demcha/documentation/CodeQlScopeGuardTest.java +++ b/core/src/test/java/com/demcha/documentation/CodeQlScopeGuardTest.java @@ -97,6 +97,77 @@ void everyDeployedModuleWithSourcesIsScanned() throws IOException { .isEmpty(); } + /** + * The inventory the second test compares against is itself read out of the publish + * workflows, so it can be emptied by editing them — and an emptier inventory is an + * easier comparison, not a failing one. Both halves below key on the absence of a + * positive signal instead: a publish workflow that deploys nothing, and a train + * whose declared modules are not among the steps read from it. + * + *

Writing a deploy as {@code -pl :graph-compose-fonts} rather than + * {@code -f fonts/pom.xml} is enough to do it, and nothing about that edit looks + * like it touches the scan.

+ */ + @Test + void everyPublishWorkflowContributesToTheInventoryItIsRead() throws IOException { + Map> byWorkflow = PublishedModules.deployedByWorkflow(PROJECT_ROOT); + + assertThat(byWorkflow) + .describedAs("no publish workflow was found at all — they were renamed, and the " + + "inventory the deployed-module test compares against is now empty") + .isNotEmpty(); + + Set silent = new TreeSet<>(); + byWorkflow.forEach((workflow, modules) -> { + if (modules.isEmpty()) { + silent.add(workflow); + } + }); + + assertThat(silent) + .describedAs("a publish workflow whose deploy steps this guard can no longer " + + "read: whatever it ships is now invisible to the deployed-module test, " + + "which will pass without ever asking about it. Either the workflow " + + "stopped deploying — in which case it should stop being a publish " + + "workflow — or its deploy is written some way other than " + + "`-f /pom.xml`, and this guard has to learn that shape before " + + "the edit lands") + .isEmpty(); + } + + /** + * The train {@code publish.yml} declares matches the steps it carries. + * + *

The workflow states its module set twice and neither statement is derived from + * the other: the {@code order} the resume input is validated against, and the deploy + * steps themselves. A module dropped from the steps — or written in a shape this + * guard cannot read — leaves the two disagreeing, which is the signal that the + * inventory shrank rather than the train.

+ */ + @Test + void theDeployStepsCoverThePublishTrainTheWorkflowDeclares() throws IOException { + List declared = PublishedModules.declaredTrain(PROJECT_ROOT); + List steps = PublishedModules.deployedByWorkflow(PROJECT_ROOT) + .getOrDefault("publish.yml", List.of()); + + assertThat(declared) + .describedAs("publish.yml no longer declares its train as `order=\"...\"` — the " + + "resume validation moved, and with it the second, independent statement " + + "of what a release publishes that this guard holds the steps against") + .isNotEmpty(); + + Set missing = new TreeSet<>(declared); + missing.removeAll(steps); + + assertThat(missing) + .describedAs("publish.yml names these in its train but this guard finds no deploy " + + "step for them. Either the module stopped shipping and belongs out of " + + "the train, or its step is written some way other than " + + "`-f /pom.xml` — in which case the module is deployed, absent " + + "from the inventory, and therefore never checked against the scan") + .isEmpty(); + } + /** * The {@code -pl} selectors of the {@code mvnw} invocation carrying {@code goal}, or * every reactor module when the command carries no {@code -pl} at all — a build diff --git a/core/src/test/java/com/demcha/documentation/PublishedModules.java b/core/src/test/java/com/demcha/documentation/PublishedModules.java index 2871fcf8e..5d7fb0a25 100644 --- a/core/src/test/java/com/demcha/documentation/PublishedModules.java +++ b/core/src/test/java/com/demcha/documentation/PublishedModules.java @@ -33,6 +33,10 @@ private PublishedModules() { private static final Pattern DEPLOY_STEP = Pattern.compile("-f\\s+([\\w-]+)/pom\\.xml"); + /** The {@code order="core render-pdf ..."} line publish.yml validates its resume against. */ + private static final Pattern TRAIN_ORDER = + Pattern.compile("order=\"([^\"]+)\""); + /** * The modules a release actually deploys, read from the publish workflows. * @@ -42,26 +46,72 @@ private PublishedModules() { * comparison, which is precisely the shape that keeps it green.

*/ static List deployed(Path repoRoot) throws IOException { - Path workflows = repoRoot.resolve(".github/workflows"); List deployed = new ArrayList<>(); + deployedByWorkflow(repoRoot).values().forEach(modules -> modules.forEach(module -> { + if (!deployed.contains(module)) { + deployed.add(module); + } + })); + return deployed; + } + + /** + * The modules each publish workflow deploys, keyed by the workflow's file name — + * including the workflows that deploy none. + * + *

Attribution is what lets a caller tell "this workflow publishes nothing" from + * "this workflow was not read". A flat list cannot: both look like a shorter list, + * and a shorter list is exactly what a guard comparing against it wants to see.

+ * + * @param repoRoot the repository root + * @return every {@code publish*.yml}, mapped to the module directories it deploys + * @throws IOException when a workflow cannot be read + */ + static Map> deployedByWorkflow(Path repoRoot) throws IOException { + Path workflows = repoRoot.resolve(".github/workflows"); + Map> byWorkflow = new LinkedHashMap<>(); try (var files = Files.list(workflows)) { for (Path workflow : files.sorted().toList()) { String name = workflow.getFileName().toString(); if (!name.startsWith("publish") || !name.endsWith(".yml")) { continue; } + List modules = new ArrayList<>(); for (String line : Files.readAllLines(workflow)) { if (!line.contains("deploy")) { continue; } Matcher module = DEPLOY_STEP.matcher(line); - if (module.find() && !deployed.contains(module.group(1))) { - deployed.add(module.group(1)); + if (module.find() && !modules.contains(module.group(1))) { + modules.add(module.group(1)); } } + byWorkflow.put(name, modules); } } - return deployed; + return byWorkflow; + } + + /** + * The publish train {@code publish.yml} declares for itself, in order. + * + *

The workflow states its module set twice — once as the {@code order} the resume + * input is validated against, and once as the deploy steps themselves. Neither is + * derived from the other, so holding them together catches the step list drifting + * away from the train without anything having to restate it a third time.

+ * + * @param repoRoot the repository root + * @return the module directories the train names, or an empty list when the + * declaration is absent + * @throws IOException when the workflow cannot be read + */ + static List declaredTrain(Path repoRoot) throws IOException { + Path workflow = repoRoot.resolve(".github/workflows/publish.yml"); + if (!Files.isRegularFile(workflow)) { + return List.of(); + } + Matcher order = TRAIN_ORDER.matcher(Files.readString(workflow)); + return order.find() ? List.of(order.group(1).strip().split("\\s+")) : List.of(); } /** The module directories the root reactor builds, in declaration order. */ diff --git a/docs/templates/v2-layered/README.md b/docs/templates/v2-layered/README.md index 00cd3ccc1..9b82f6135 100644 --- a/docs/templates/v2-layered/README.md +++ b/docs/templates/v2-layered/README.md @@ -11,10 +11,10 @@ > archived at [templates/v1-classic/](../v1-classic/README.md). The **template surface** for building business documents on -GraphCompose. All four families ship on it — CV (the reference +GraphCompose. All five families ship on it — CV (the reference implementation, `com.demcha.compose.document.templates.cv`), -cover-letter, invoice, and proposal — and any new template family -follows the same shape. +cover-letter, invoice, proposal, and receipt — and any new template +family follows the same shape. This is the entry point. Pick the doc that matches your goal. @@ -97,7 +97,8 @@ The detailed contract for each layer is in - ❌ **Not one shape for every family.** `presets/` is the only package a family always has. CV carries all four; cover letter has no - `widgets/`; invoice and proposal are presets alone, reading data + `widgets/`; invoice and proposal are presets alone; receipt carries + `components/` + `widgets/` + `presets/`. All of them read their data records from the shared `templates.data.`. Add a layer when the family needs one. - ❌ **Not a framework with magic.** Every file is plain @@ -123,6 +124,14 @@ The detailed contract for each layer is in - **Examples**: [`examples/cv/v2/`](../../../examples/src/main/java/com/demcha/examples/templates/cv/v2) has runnable rendering examples for the shipped presets. +- **Receipt family**: the newest family, and the smallest complete one + to read end-to-end — + [`receipt/package-info.java`](../../../templates/src/main/java/com/demcha/compose/document/templates/receipt/package-info.java) + for the layer map, + [`receipt/AUTHORS.md`](../../../templates/src/main/java/com/demcha/compose/document/templates/receipt/AUTHORS.md) + for its recipes, and + [`ModernReceiptExample`](../../../examples/src/main/java/com/demcha/examples/templates/receipt/ModernReceiptExample.java) + for a rendered transfer confirmation. - **Archived classic surface**: [`docs/templates/v1-classic/README.md`](../v1-classic/README.md) describes the older spec / preset / theme split that was removed in 2.0; kept for diff --git a/docs/templates/v2-layered/using-templates.md b/docs/templates/v2-layered/using-templates.md index e260f3a37..2d84290c0 100644 --- a/docs/templates/v2-layered/using-templates.md +++ b/docs/templates/v2-layered/using-templates.md @@ -234,6 +234,14 @@ whole or the paginator raises `AtomicNodeTooLargeException`. Lifting a cap without teaching the preset to pick its own page boundaries turns a CV that silently lost an entry into one that fails to render. +`ProfessionalSidebar.create()`, `NavySidebar.create()`, +`SerifHeadline.create()` and `CharcoalGold.create()` are compositions +for a fixed amount of content that cap nothing: each reproduces a +specific one-page sheet, so a CV longer than that sheet raises +`AtomicNodeTooLargeException` instead of losing an entry to a cap. Size +the document to them — roughly five or six roles with three or four +highlights each, alongside the other blocks. + If the document's length is the author's rather than the template's, pick a preset that paginates — `TimelineMinimal` splits its own columns and carries every entry it is given onto as many pages as it needs. diff --git a/docs/templates/which-template-system.md b/docs/templates/which-template-system.md index 327b60986..50ae32543 100644 --- a/docs/templates/which-template-system.md +++ b/docs/templates/which-template-system.md @@ -88,7 +88,7 @@ migration is the theme + data-record swap: |---|---| | `InvoiceTemplateV1` / `InvoiceTemplateV2` | `templates.invoice.presets.ModernInvoice` — `create()` or `create(BrandTheme)`, data record `InvoiceDocumentSpec`. | | `ProposalTemplateV1` / `ProposalTemplateV2` | `templates.proposal.presets.ModernProposal` — same shape, data record `ProposalDocumentSpec`. | -| `WeeklyScheduleTemplateV1` | No 2.0 template yet. The `templates.data.schedule` records still ship; author the rendering on the canonical DSL. | +| `WeeklyScheduleTemplateV1` | No template yet. Model the rota on `templates.data.rota` (`StructuredRotaDocumentSpec`) and author the rendering on the canonical DSL. The `templates.data.schedule` records still ship but are **deprecated since 2.4.0**: they hold their colours in the data, know nothing of staff bands, and nothing ever rendered them. | ### Legacy PDF API → canonical DSL diff --git a/examples/src/main/java/com/demcha/examples/GenerateAllExamples.java b/examples/src/main/java/com/demcha/examples/GenerateAllExamples.java index 201445dee..210f111ab 100644 --- a/examples/src/main/java/com/demcha/examples/GenerateAllExamples.java +++ b/examples/src/main/java/com/demcha/examples/GenerateAllExamples.java @@ -81,6 +81,13 @@ import com.demcha.examples.templates.coverletter.CvPanelLetterV2Example; import com.demcha.examples.templates.coverletter.CvSidebarPortraitLetterV2Example; import com.demcha.examples.templates.coverletter.CvTimelineMinimalLetterV2Example; +import com.demcha.examples.templates.cv.v2.CharcoalGoldExample; +import com.demcha.examples.templates.cv.v2.SlateOrangeExample; +import com.demcha.examples.templates.cv.v2.MidnightNavyExample; +import com.demcha.examples.templates.cv.v2.OrangeOpsExample; +import com.demcha.examples.templates.cv.v2.VioletGridExample; +import com.demcha.examples.templates.cv.v2.TealPulseExample; +import com.demcha.examples.templates.cv.v2.TerracottaRailExample; import com.demcha.examples.templates.cv.v2.CvBlueBannerExample; import com.demcha.examples.templates.cv.v2.CvBoxedV2Example; import com.demcha.examples.templates.cv.v2.CvCenteredHeadlineExample; @@ -98,11 +105,22 @@ import com.demcha.examples.templates.cv.v2.CvPanelExample; import com.demcha.examples.templates.cv.v2.CvSidebarPortraitExample; import com.demcha.examples.templates.cv.v2.CvTimelineMinimalExample; +import com.demcha.examples.templates.cv.v2.NavySidebarExample; +import com.demcha.examples.templates.cv.v2.ProfessionalSidebarExample; +import com.demcha.examples.templates.cv.v2.SerifHeadlineExample; +import com.demcha.examples.templates.invoice.ClassicInvoiceV2Example; +import com.demcha.examples.templates.invoice.ConsultingInvoiceV2Example; +import com.demcha.examples.templates.invoice.LumaStudioInvoiceV2Example; +import com.demcha.examples.templates.invoice.PaymentsInvoiceV2Example; +import com.demcha.examples.templates.invoice.WorkspaceInvoiceV2Example; import com.demcha.examples.templates.invoice.InvoiceCinematicFileExample; import com.demcha.examples.templates.invoice.ModernInvoiceV2Example; import com.demcha.examples.templates.proposal.CinematicProposalFileExample; +import com.demcha.examples.templates.proposal.EditorialProposalV2Example; +import com.demcha.examples.templates.proposal.NorthlineProposalV2Example; import com.demcha.examples.templates.proposal.ProposalCinematicFileExample; import com.demcha.examples.templates.proposal.ModernProposalV2Example; +import com.demcha.examples.templates.receipt.ModernReceiptExample; import com.demcha.examples.templates.schedule.WeeklyScheduleFileExample; /** @@ -143,6 +161,16 @@ public static void main(String[] args) throws Exception { System.out.println("Generated: " + CvPanelExample.generate()); System.out.println("Generated: " + CvSidebarPortraitExample.generate()); System.out.println("Generated: " + CvTimelineMinimalExample.generate()); + System.out.println("Generated: " + NavySidebarExample.generate()); + System.out.println("Generated: " + ProfessionalSidebarExample.generate()); + System.out.println("Generated: " + SerifHeadlineExample.generate()); + System.out.println("Generated: " + CharcoalGoldExample.generate()); + System.out.println("Generated: " + TerracottaRailExample.generate()); + System.out.println("Generated: " + TealPulseExample.generate()); + System.out.println("Generated: " + SlateOrangeExample.generate()); + System.out.println("Generated: " + VioletGridExample.generate()); + System.out.println("Generated: " + OrangeOpsExample.generate()); + System.out.println("Generated: " + MidnightNavyExample.generate()); // Cover letters (v2 layered — 15 paired letters, one per CV preset) System.out.println("Generated: " + CvBlueBannerLetterV2Example.generate()); @@ -164,12 +192,22 @@ public static void main(String[] args) throws Exception { // Invoices System.out.println("Generated: " + InvoiceCinematicFileExample.generate()); System.out.println("Generated: " + ModernInvoiceV2Example.generate()); + System.out.println("Generated: " + ClassicInvoiceV2Example.generate()); + System.out.println("Generated: " + ConsultingInvoiceV2Example.generate()); + System.out.println("Generated: " + LumaStudioInvoiceV2Example.generate()); + System.out.println("Generated: " + PaymentsInvoiceV2Example.generate()); + System.out.println("Generated: " + WorkspaceInvoiceV2Example.generate()); // Proposals System.out.println("Generated: " + ProposalCinematicFileExample.generate()); System.out.println("Generated: " + ModernProposalV2Example.generate()); + System.out.println("Generated: " + NorthlineProposalV2Example.generate()); + System.out.println("Generated: " + EditorialProposalV2Example.generate()); System.out.println("Generated: " + CinematicProposalFileExample.generate()); + // Receipts + System.out.println("Generated: " + ModernReceiptExample.generate()); + // Schedule System.out.println("Generated: " + WeeklyScheduleFileExample.generate()); diff --git a/examples/src/main/java/com/demcha/examples/support/CharcoalGoldSampleData.java b/examples/src/main/java/com/demcha/examples/support/CharcoalGoldSampleData.java new file mode 100644 index 000000000..971fc1c6c --- /dev/null +++ b/examples/src/main/java/com/demcha/examples/support/CharcoalGoldSampleData.java @@ -0,0 +1,186 @@ +package com.demcha.examples.support; + +import com.demcha.compose.document.image.DocumentImageData; +import com.demcha.compose.document.templates.core.identity.Contact; +import com.demcha.compose.document.templates.core.identity.Link; +import com.demcha.compose.document.templates.cv.data.CvDocument; +import com.demcha.compose.document.templates.cv.data.CvEntry; +import com.demcha.compose.document.templates.cv.data.CvIdentity; +import com.demcha.compose.document.templates.cv.data.CvSkill; +import com.demcha.compose.document.templates.cv.data.EntriesSection; +import com.demcha.compose.document.templates.cv.data.ParagraphSection; +import com.demcha.compose.document.templates.cv.data.RowStyle; +import com.demcha.compose.document.templates.cv.data.RowsSection; +import com.demcha.compose.document.templates.cv.data.SkillGroup; +import com.demcha.compose.document.templates.cv.data.SkillsSection; + +import java.io.IOException; +import java.io.InputStream; +import java.io.UncheckedIOException; +import java.util.List; +import java.util.Objects; + +/** + * Sample data for the Charcoal Gold CV example. + * + *

Sized to the design: the preset draws a fixed one-page sheet, and a CV + * much longer than this one does not compose at all — the two columns are a + * single atomic row, so it raises {@code AtomicNodeTooLargeException}.

+ * + *

The credential marks are this preset's own vocabulary — + * {@code certificate}, {@code trophy}, {@code growth}, {@code star} — and + * each entry names the one it wants. The portrait is a neutral silhouette + * rather than a photograph, because the example ships in the repository; a + * real CV passes the candidate's own image the same way.

+ */ +public final class CharcoalGoldSampleData { + + private static final String NEWLINE = String.valueOf((char) 10); + private static final String PORTRAIT = "/cv-portrait-placeholder.png"; + + private CharcoalGoldSampleData() { + } + + /** + * A delivery lead's one-page CV. + * + * @return the sample document + */ + public static CvDocument sample() { + return CvDocument.builder() + .identity(CvIdentity.builder() + .name("HELENA", "MARSH") + .jobTitle("DELIVERY LEAD") + .contact(new Contact("+44 161 496 0114", + "helena@example.com", + "Manchester, United Kingdom")) + .link(new Link("helenamarsh.co.uk", "https://helenamarsh.co.uk")) + .link(new Link("linkedin.com/in/hmarsh", + "https://www.linkedin.com/in/hmarsh")) + .portrait(portrait()) + .build()) + .section(new ParagraphSection("Summary", String.join(NEWLINE, + "Delivery lead with nine years running software programmes for" + + " regulated clients, from discovery through the audit that" + + " follows go-live.", + "Happiest with a small team, a short feedback loop, and a plan that" + + " survives contact with the first week."))) + .section(SkillsSection.of("SKILLS", new SkillGroup("Core", List.of( + CvSkill.of("Delivery Management", 1.0), + CvSkill.of("Stakeholder Management", 1.0), + CvSkill.of("Risk & Assurance", 1.0), + CvSkill.of("Budget Ownership", 0.8), + CvSkill.of("Agile Coaching", 0.8), + CvSkill.of("Vendor Management", 0.8), + CvSkill.of("Roadmapping", 1.0), + CvSkill.of("Jira / Confluence", 1.0), + CvSkill.of("Power BI", 0.6), + CvSkill.of("Public Speaking", 0.6))))) + .section(RowsSection.builder("LANGUAGES", RowStyle.PLAIN) + .row("English", "Native") + .row("Welsh", "Native") + .row("Spanish", "B2 – Upper Intermediate") + .row("Italian", "A2 – Basic") + .build()) + .section(EntriesSection.builder("EDUCATION") + .entry(CvEntry.builder("MSc Programme Management") + .subtitle("University of Manchester") + .date("2015 – 2016") + .build()) + .entry(CvEntry.builder("BA Economics") + .subtitle("University of Leeds") + .date("2012 – 2015") + .build()) + .build()) + .section(EntriesSection.builder("EXPERIENCE") + .entry(CvEntry.builder("Delivery Lead") + .subtitle("NORTHWALL GROUP") + .place("MANCHESTER, UK") + .date("2021 – Present") + .link("https://northwall.example.com") + .body(List.of( + "Runs a portfolio of four programmes worth £8M against" + + " a fixed regulatory deadline.", + "Rebuilt the intake process so a request is either" + + " scheduled or refused within a week.", + "Coaches three delivery managers and chairs the monthly" + + " assurance review.", + "Cut third-party spend by 18% by consolidating four" + + " vendor contracts into one.")) + .build()) + .entry(CvEntry.builder("Senior Project Manager") + .subtitle("BRIDGEMOOR CONSULTING") + .place("LEEDS, UK") + .date("2018 – 2021") + .body(List.of( + "Delivered fourteen client programmes across finance" + + " and public sector.", + "Owned budgets, timelines and the escalation path to" + + " the client's board.", + "Introduced the risk register the practice still uses.")) + .build()) + .entry(CvEntry.builder("Project Manager") + .subtitle("ALDGATE DIGITAL") + .place("LONDON, UK") + .date("2016 – 2018") + .body(List.of( + "Ran delivery for two product teams and the platform" + + " work beneath them.", + "Built the reporting pack the leadership team read" + + " weekly.")) + .build()) + .build()) + .section(EntriesSection.builder("CERTIFICATIONS") + .entry(CvEntry.builder("PMP® Certification") + .subtitle("Project Management Institute") + .date("2019") + .icon("certificate") + .build()) + .entry(CvEntry.builder("PRINCE2® Practitioner") + .subtitle("AXELOS") + .date("2018") + .icon("certificate") + .build()) + .entry(CvEntry.builder("Certified Scrum Professional") + .subtitle("Scrum Alliance") + .date("2017") + .icon("certificate") + .build()) + .build()) + .section(EntriesSection.builder("ACHIEVEMENTS") + .entry(CvEntry.builder("Programme of the Year") + .subtitle("Northwall Group") + .date("2024") + .icon("trophy") + .build()) + .entry(CvEntry.builder("Intake Redesign") + .subtitle("Lead time down 62%") + .date("2023") + .icon("growth") + .build()) + .entry(CvEntry.builder("Client Choice Award") + .subtitle("Bridgemoor Consulting") + .date("2020") + .icon("star") + .build()) + .build()) + .section(new ParagraphSection("TECHNICAL TOOLS", String.join(NEWLINE, + "Jira", "Confluence", "Miro", "Power BI", "Smartsheet", "Slack"))) + .build(); + } + + /** + * The packaged silhouette that stands in for a photograph. + * + * @return the portrait image data + */ + private static DocumentImageData portrait() { + try (InputStream in = Objects.requireNonNull( + CharcoalGoldSampleData.class.getResourceAsStream(PORTRAIT), + "cv-portrait-placeholder.png missing from examples/src/main/resources/")) { + return DocumentImageData.fromBytes(in.readAllBytes()); + } catch (IOException e) { + throw new UncheckedIOException("Failed to read the sample portrait", e); + } + } +} diff --git a/examples/src/main/java/com/demcha/examples/support/ConsultingInvoiceSampleData.java b/examples/src/main/java/com/demcha/examples/support/ConsultingInvoiceSampleData.java new file mode 100644 index 000000000..ff61b3188 --- /dev/null +++ b/examples/src/main/java/com/demcha/examples/support/ConsultingInvoiceSampleData.java @@ -0,0 +1,127 @@ +package com.demcha.examples.support; + +import com.demcha.compose.document.image.DocumentImageData; +import com.demcha.compose.document.templates.data.invoice.InvoiceBrand; +import com.demcha.compose.document.templates.data.invoice.InvoiceContactBlock; +import com.demcha.compose.document.templates.data.invoice.InvoiceMasthead; +import com.demcha.compose.document.templates.data.invoice.InvoiceNotesBlock; +import com.demcha.compose.document.templates.data.invoice.InvoicePaymentBlock; +import com.demcha.compose.document.templates.data.invoice.InvoiceRecipient; +import com.demcha.compose.document.templates.data.invoice.InvoiceServiceLines; +import com.demcha.compose.document.templates.data.invoice.InvoiceSummaryBlock; +import com.demcha.compose.document.templates.data.invoice.InvoiceTotalsBlock; +import com.demcha.compose.document.templates.data.invoice.StructuredInvoiceData; +import com.demcha.compose.document.templates.data.invoice.StructuredInvoiceDocumentSpec; + +import java.io.IOException; +import java.io.InputStream; +import java.io.UncheckedIOException; +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.List; + +/** + * Shared sample data for the Consulting Invoice example. + * + *

The sample is the preset's reference content: a + * professional-services invoice with a brand logo, five masthead metadata + * rows (one emphasized), five priced service lines, a subtotal / tax + * stack with the total band, five bank fields, and notes naming both query + * channels.

+ * + *

Kept in lockstep with the qa module's + * {@code ConsultingInvoiceFixtures} — the two modules cannot share a + * source file, so a content change here belongs there too.

+ */ +public final class ConsultingInvoiceSampleData { + + private ConsultingInvoiceSampleData() { + } + + /** The single-page reference invoice. */ + public static StructuredInvoiceDocumentSpec sample() { + return StructuredInvoiceDocumentSpec.from(baseBuilder(serviceLines(5)).build()); + } + + /** The sample logo, read from the examples resources. */ + public static DocumentImageData sampleLogo() { + String path = "/consulting-invoice-logo.png"; + try (InputStream input = ConsultingInvoiceSampleData.class.getResourceAsStream(path)) { + if (input == null) { + throw new IllegalStateException("Missing sample logo: " + path); + } + return DocumentImageData.fromBytes(input.readAllBytes()); + } catch (IOException e) { + throw new UncheckedIOException("Failed to read sample logo: " + path, e); + } + } + + private static StructuredInvoiceData.Builder baseBuilder(InvoiceServiceLines lines) { + return StructuredInvoiceData.builder() + .brand(new InvoiceBrand(sampleLogo(), "NORTHPOINT", "CONSULTING", + "Strategy. Solutions. Results.")) + .supplier(new InvoiceContactBlock("Northpoint Consulting Pty Ltd", + List.of("Level 8, 1 Collins Street", "Melbourne VIC 3000 Australia"), + "+61 3 9876 5432", "hello@northpoint.com.au", "northpoint.com.au", + "ABN", "12 345 678 901")) + .masthead(new InvoiceMasthead("INVOICE", List.of( + new InvoiceMasthead.Entry("Invoice Number:", "INV-2025-0478", false), + new InvoiceMasthead.Entry("Issue Date:", "26 May 2025", false), + new InvoiceMasthead.Entry("Due Date:", "25 June 2025", true), + new InvoiceMasthead.Entry("Project:", "Digital Strategy Engagement", false), + new InvoiceMasthead.Entry("PO Number:", "PO-7892", false)))) + .billTo(new InvoiceRecipient("BILLED TO", "Greenfield Industries Ltd.", + "Accounts Payable Department", + List.of("12 Innovation Drive", "Melbourne VIC 3000", "Australia"), + "Email:", "ap@greenfield.com.au")) + .summary(new InvoiceSummaryBlock("INVOICE SUMMARY", + "Professional services rendered in accordance with the statement of " + + "work for the period", + "1 May 2025 – 25 May 2025.")) + .serviceLines(lines) + .totals(new InvoiceTotalsBlock(List.of( + new InvoiceTotalsBlock.Row("SUBTOTAL", new BigDecimal("14000.00")), + new InvoiceTotalsBlock.Row("GST (10%)", new BigDecimal("1400.00"))), + "TOTAL DUE", new BigDecimal("15400.00"))) + .payment(new InvoicePaymentBlock("PAYMENT INFORMATION", List.of( + new InvoicePaymentBlock.Field("Bank Name:", "Example Bank"), + new InvoicePaymentBlock.Field("Account Name:", + "Northpoint Consulting Pty Ltd"), + new InvoicePaymentBlock.Field("BSB:", "123-456"), + new InvoicePaymentBlock.Field("Account Number:", "12345678"), + new InvoicePaymentBlock.Field("Reference:", "INV-2025-0478")), + "Please ensure the invoice number is included in the payment reference.", + "Payment is due within 30 days from the issue date.", "30 days")) + .notes(new InvoiceNotesBlock("NOTES", List.of( + "Thank you for your business.", + "If you have any questions regarding this invoice, please contact us " + + "at accounts@northpoint.com.au or +61 3 9876 5432."), + "accounts@northpoint.com.au", "+61 3 9876 5432")) + .currencyCode("AUD"); + } + + private static InvoiceServiceLines serviceLines(int count) { + String[][] source = { + {"Strategic Consultation", "Leadership alignment workshops", "1–10 May 2025", + "10.00", "hrs", "250.00", "2500.00"}, + {"Market & Competitive Analysis", "Research and analysis report", "1–15 May 2025", + "1.00", "ea", "3500.00", "3500.00"}, + {"Digital Roadmap Development", "Strategy and roadmap creation", "5–20 May 2025", + "1.00", "ea", "4800.00", "4800.00"}, + {"Stakeholder Review Sessions", "Facilitation and documentation", "15–22 May 2025", + "6.00", "hrs", "220.00", "1320.00"}, + {"Presentation & Final Report", "Executive presentation and delivery", + "20–25 May 2025", "1.00", "ea", "1880.00", "1880.00"}}; + List lines = new ArrayList<>(); + for (int index = 0; index < count; index++) { + String[] row = source[index % source.length]; + lines.add(new InvoiceServiceLines.Line(index + 1, row[0], row[1], row[2], + new BigDecimal(row[3]), row[4], new BigDecimal(row[5]), + new BigDecimal(row[6]))); + } + return new InvoiceServiceLines( + new InvoiceServiceLines.Columns("#", "DESCRIPTION", "SERVICE PERIOD", "QTY", + "UNIT PRICE", "AMOUNT"), + lines); + } +} diff --git a/examples/src/main/java/com/demcha/examples/support/EditorialProposalSampleData.java b/examples/src/main/java/com/demcha/examples/support/EditorialProposalSampleData.java new file mode 100644 index 000000000..343a08e61 --- /dev/null +++ b/examples/src/main/java/com/demcha/examples/support/EditorialProposalSampleData.java @@ -0,0 +1,147 @@ +package com.demcha.examples.support; + +import com.demcha.compose.document.templates.data.proposal.ProposalAcceptance; +import com.demcha.compose.document.templates.data.proposal.ProposalBrand; +import com.demcha.compose.document.templates.data.proposal.ProposalDeliverables; +import com.demcha.compose.document.templates.data.proposal.ProposalGlance; +import com.demcha.compose.document.templates.data.proposal.ProposalGoals; +import com.demcha.compose.document.templates.data.proposal.ProposalInvestment; +import com.demcha.compose.document.templates.data.proposal.ProposalMetaLine; +import com.demcha.compose.document.templates.data.proposal.ProposalPhaseGrid; +import com.demcha.compose.document.templates.data.proposal.ProposalScope; +import com.demcha.compose.document.templates.data.proposal.ProposalSummaryBlock; +import com.demcha.compose.document.templates.data.proposal.ProposalTermsBlock; +import com.demcha.compose.document.templates.data.proposal.ProposalTitleLines; +import com.demcha.compose.document.templates.data.proposal.StructuredProposalData; +import com.demcha.compose.document.templates.data.proposal.StructuredProposalDocumentSpec; + +import java.util.List; + +/** + * Shared sample data for the Editorial proposal example. + * + *

Kept in lockstep with the qa module's + * {@code EditorialProposalFixtures} — the two modules cannot share a source + * file, so a content change here belongs there too.

+ * + *

The sample is the preset's reference content: a two-page + * brand-refresh proposal exercising every band — all four glance facts + * (one with the optional note), four goal cells, five scope rows, both + * deliverable columns, the four-phase grid, an investment table with + * ordinary, subtotal and optional rows plus the total band, four terms, + * and the three-field signing card.

+ * + */ +public final class EditorialProposalSampleData { + + private EditorialProposalSampleData() { + } + + public static StructuredProposalDocumentSpec sample() { + return StructuredProposalDocumentSpec.from(StructuredProposalData.builder() + .brand(new ProposalBrand("N", "NORTHLINE", "STUDIO", "PROPOSAL", + "northlinestudio.com", "NORTHLINE STUDIO")) + .title(new ProposalTitleLines("Proposal —", "Brand Refresh &", + "Website Redesign")) + .meta(new ProposalMetaLine("PREPARED FOR ASTERA HEALTH CO.", + "PREPARED BY NORTHLINE STUDIO", "25 AUGUST 2026")) + .executiveSummary(new ProposalSummaryBlock("Executive Summary", "", List.of( + "Thank you for the opportunity to partner with Astera Health Co. " + + "on your next chapter. This proposal outlines our approach to " + + "refreshing your brand identity and redesigning your website to " + + "better reflect your mission, elevate credibility, and deliver a " + + "seamless experience for the people you serve.", + "Our process blends strategic thinking with thoughtful design to " + + "create a cohesive brand system and a modern, conversion-focused " + + "website. The outcome will be a clear, confident presence that " + + "strengthens trust, improves engagement, and supports your " + + "growth goals."))) + .glance(new ProposalGlance("", List.of( + new ProposalGlance.Fact("fact-duration", "PROJECT DURATION", + "6 weeks", null), + new ProposalGlance.Fact("fact-start", "START WINDOW", + "September 2026", null), + new ProposalGlance.Fact("fact-contact", "PRIMARY CONTACT", + "Project Lead", null), + new ProposalGlance.Fact("fact-validity", "PROPOSAL VALIDITY", + "14 days", "(until 8 September 2026)")))) + .goals(new ProposalGoals("Project Goals", "goal-check", List.of( + new ProposalGoals.Goal("", + "Strengthen brand credibility and visual consistency."), + new ProposalGoals.Goal("", + "Improve website usability and drive meaningful action."), + new ProposalGoals.Goal("", + "Communicate your value clearly across all touchpoints."), + new ProposalGoals.Goal("", + "Build a scalable digital foundation for future growth.")))) + .scope(new ProposalScope("Scope of Work", "", List.of( + new ProposalScope.Item("01", "Discovery & Research", + "Stakeholder interviews, brand audit, competitor review, and " + + "audience insights to inform strategy."), + new ProposalScope.Item("02", "Visual Identity Refinement", + "Refine logo system, color palette, typography, and visual " + + "guidelines for a cohesive, modern identity."), + new ProposalScope.Item("03", "Website UX/UI Design", + "Design a responsive, accessibility-minded website with " + + "intuitive navigation and clear conversion pathways."), + new ProposalScope.Item("04", "Content Structure & Messaging", + "Define site architecture, key messaging, and content " + + "hierarchy to communicate value and build trust."), + new ProposalScope.Item("05", "Launch Support", + "Developer handoff, QA support, training, and go-live " + + "assistance to ensure a smooth launch.")))) + .deliverables(new ProposalDeliverables("Deliverables", "", + List.of("Brand strategy summary", + "Refined logo suite & brand guidelines", + "Color, typography & design system", + "Website UX wireframes", + "High-fidelity website designs"), + List.of("Responsive design for desktop, tablet & mobile", + "Content strategy & page templates", + "Developer-ready assets & specifications", + "Launch checklist & training session"))) + .timeline(new ProposalPhaseGrid("Timeline", "", + List.of("PHASE", "FOCUS", "DURATION", "OUTPUT"), List.of( + new ProposalPhaseGrid.Phase("01", "Discover", + "Research, audit, insights, and strategy", "1 week", + "Discovery report & creative direction"), + new ProposalPhaseGrid.Phase("02", "Design", + "Brand refinement and website UX/UI design", "2 weeks", + "Design system & high-fidelity designs"), + new ProposalPhaseGrid.Phase("03", "Develop Prep", + "Content structure, developer handoff & asset preparation", + "1 week", "Specs, assets & content framework"), + new ProposalPhaseGrid.Phase("04", "Launch Support", + "QA, training & go-live assistance", "2 weeks", + "QA sign-off & successful launch")))) + .investment(new ProposalInvestment("Investment", "", + "ITEM", "AMOUNT (GBP)", List.of( + new ProposalInvestment.Row("Discovery & Strategy", "£2,500", + ProposalInvestment.Role.NONE), + new ProposalInvestment.Row("Brand Identity Refinement", "£4,500", + ProposalInvestment.Role.NONE), + new ProposalInvestment.Row("Website UX/UI Design", "£7,000", + ProposalInvestment.Role.NONE), + new ProposalInvestment.Row("Content Structure & Messaging", "£2,000", + ProposalInvestment.Role.NONE), + new ProposalInvestment.Row("Launch Support", "£1,500", + ProposalInvestment.Role.NONE), + new ProposalInvestment.Row("Subtotal", "£17,500", + ProposalInvestment.Role.SUBTOTAL), + new ProposalInvestment.Row("Optional: 3 Months Post-Launch Support", + "£1,750", ProposalInvestment.Role.OPTIONAL)), + "TOTAL INVESTMENT", "£19,250")) + .terms(new ProposalTermsBlock("Terms", "", List.of( + "50% deposit is required to secure the project and schedule the " + + "kick-off.", + "Includes two rounds of revisions per major deliverable.", + "Final files and handoff delivered upon final payment.", + "Ownership of all final deliverables transfers to Astera Health Co. " + + "after full payment is received."))) + .acceptance(new ProposalAcceptance("Acceptance", "", + "By signing below, you agree to the scope, timeline, investment, " + + "and terms outlined in this proposal.", + List.of("Accepted by:", "Signature:", "Date:"))) + .build()); + } +} diff --git a/examples/src/main/java/com/demcha/examples/support/ExampleDataFactory.java b/examples/src/main/java/com/demcha/examples/support/ExampleDataFactory.java index 4ab8455be..501fda72a 100644 --- a/examples/src/main/java/com/demcha/examples/support/ExampleDataFactory.java +++ b/examples/src/main/java/com/demcha/examples/support/ExampleDataFactory.java @@ -11,6 +11,8 @@ import com.demcha.compose.document.templates.cv.data.SkillsSection; import com.demcha.compose.document.templates.data.invoice.InvoiceDocumentSpec; import com.demcha.compose.document.templates.data.proposal.ProposalDocumentSpec; +import com.demcha.compose.document.templates.data.receipt.ReceiptDocumentSpec; +import com.demcha.compose.document.templates.data.receipt.ReceiptStatus; import com.demcha.compose.document.templates.data.schedule.ScheduleSlot; import com.demcha.compose.document.templates.data.schedule.WeeklyScheduleDocumentSpec; @@ -58,6 +60,65 @@ public static InvoiceDocumentSpec sampleInvoice() { .build(); } + /** + * A settled direct-debit collection, for the Modern Receipt preset. + * + *

Northwind Pay is invented, and so is every account, reference, and + * name below — the footer note on the rendered page says so, because a + * sample bank document that does not is a sample somebody eventually + * mistakes for a real one.

+ * + * @return receipt document spec + */ + public static ReceiptDocumentSpec sampleReceipt() { + return ReceiptDocumentSpec.of(receipt -> receipt + .documentTitle("Transfer confirmation") + .issuerName("Northwind Pay") + .generatedOn("09 August 2026") + .reference("NWP-4821-0067") + .amount("Amount collected", "£66.62") + .amountCaption("Direct Debit collected by Harbour Finance Ltd") + .status(ReceiptStatus.settled("Completed")) + .summaryField("Value date", "07 Jul 2026") + .summaryField("Operation date", "07 Jul 2026") + .summaryField("Scheme", "Bacs Direct Debit") + .payer("Paid from", party -> party + .name("Alex Sample") + .addressLines("12 Example Way", "Brentford TW0 0AA", "United Kingdom") + .field("Account", "•••• 4396") + .field("Sort code", "00-00-00")) + .beneficiary("Paid to", party -> party + .name("Harbour Finance Ltd") + .addressLines("1 Sample Quay", "Manchester M0 0AA") + .field("Account", "•••• 5604") + .field("Sort code", "00-00-11")) + .detailGroup("Transfer details", group -> group + .field("Mandate reference", "MND-0110-8054-5652") + .field("Transaction ID", "b71f0c2e-9a44-4f18-bd30-51c7a2e9d840") + .field("Payment scheme", "Bacs Direct Debit") + .field("Statement description", "HARBOUR FIN 4821")) + .detailGroup("Amount breakdown", group -> group + .field("Amount", "£66.62") + .field("Transfer fee", "£0.00") + .field("Exchange rate", "1.00 GBP / GBP") + .emphasized("Total debited", "£66.62")) + .event("Instructed", "07 Jul 2026, 08:12 BST", + "Collection request submitted to the scheme.") + .event("In clearing", "08 Jul 2026, 09:00 BST", + "Three-working-day Bacs cycle.") + .event("Settled", "09 Jul 2026, 06:30 BST", + "Funds debited and confirmed by the receiving bank.") + .note("Keep this confirmation for your records. It is not a tax invoice.") + .note("Disputed collections can be reversed under the Direct Debit Guarantee.") + .verification("https://example.com/verify/NWP-4821-0067", + "Scan to check this confirmation against the issuing records.") + .supportLine("Support +44 20 0000 0000") + .supportLine("help@northwind-pay.example") + .legalNote("Northwind Pay is a fictional institution used in GraphCompose " + + "examples; every name, account, and reference on this page is " + + "invented. Nothing here is a record of a real payment.")); + } + public static ProposalDocumentSpec sampleProposal() { return ProposalDocumentSpec.builder() .title("Proposal") @@ -98,12 +159,12 @@ public static ProposalDocumentSpec sampleProposal() { public static WeeklyScheduleDocumentSpec sampleWeeklySchedule() { return WeeklyScheduleDocumentSpec.builder() - .title("Scott's Weekly Floor Schedule") + .title("Quayside Weekly Floor Schedule") .weekLabel("Week Of 30 Mar - 05 Apr 2026") .day("mon", "Monday\n30th", "Clean crushed ice\nMachine & area", "request") .day("tue", "Tuesday\n31st", "Pianist 18:30\nClean ice machine", "off") - .day("wed", "Wednesday\n1st", "Motown GF\nPianist FF", "hol") - .day("thu", "Thursday\n2nd", "Ex hire terrace\ndinner\nMGM meeting\n3:30pm", "stock") + .day("wed", "Wednesday\n1st", "Soul GF\nPianist FF", "hol") + .day("thu", "Thursday\n2nd", "Ex hire terrace\ndinner\nPartner meeting\n3:30pm", "stock") .day("fri", "Friday\n3rd", "Good Friday\nPianist 19:00", "standby") .day("sat", "Saturday\n4th", "Masterclass 2x2PAX\nPianist 19:00", "training") .day("sun", "Sunday\n5th", "Easter Sunday\nFull stock take", "bar-back") @@ -115,62 +176,62 @@ public static WeeklyScheduleDocumentSpec sampleWeeklySchedule() { .category("training", "TRAINING", new Color(245, 131, 24), new Color(183, 82, 0)) .category("bar-back", "BAR BACK", new Color(176, 132, 76), new Color(120, 88, 44)) .headerMetric("COVERS", "27 / 37", "41 / 36", "30 / 29", "57 / 63", "46 / 71", "73 / 97", "155 / 26") - .headerMetric("TEAM FOCUS", "Alex floor", "Glass count", "Vee lab", "Sergii lab", "Pianist", "Alex floor", "Sergii floor") - .person("sergii", "SERGII", 10) - .person("mark", "MARK", 20) - .person("alex", "ALEX", 30) - .person("bianca", "BIANCA", 40) + .headerMetric("TEAM FOCUS", "Iva floor", "Glass count", "Nils lab", "Rowan lab", "Pianist", "Iva floor", "Rowan floor") + .person("rowan", "ROWAN", 10) + .person("nils", "NILS", 20) + .person("iva", "IVA", 30) + .person("pilar", "PILAR", 40) .person("sam", "SAM", 50) .person("kira", "KIRA", 60) - .person("daria", "DARIA", 70) - .person("violetta", "VIOLETTA", 80) - .person("peter", "PETER", 90) + .person("freya", "FREYA", 70) + .person("noor", "NOOR", 80) + .person("bowen", "BOWEN", 90) .person("den", "DEN", 100) - .person("daniel", "DANIEL", 110) - .assignment("sergii", "mon", "stock", slot("09:00", "18:00")) - .assignment("sergii", "thu", "request", slot("09:00", "18:00")) - .assignment("sergii", "fri", "training", slot("08:00", "16:00"), slot("16:00", "22:00")) - .assignment("sergii", "sun", "standby", slot("12:00", "16:00"), slot("16:00", "22:00")) - .assignment("mark", "mon", "request", slot("16:00", "00:00")) - .assignment("mark", "tue", "off", slot("12:00", "18:00")) - .assignment("mark", "wed", "request", slot("16:00", "00:00")) - .assignment("mark", "fri", "standby", slot("16:00", "01:00")) - .assignment("mark", "sun", "bar-back", slot("17:00", "03:00")) - .assignment("alex", "tue", "off", slot("16:00", "00:00")) - .assignment("alex", "wed", "request", slot("16:00", "00:00")) - .assignment("alex", "thu", "request", slot("16:00", "00:00")) - .assignment("alex", "fri", "standby", slot("16:00", "22:00")) - .assignment("alex", "sat", "training", slot("12:00", "16:00"), slot("17:00", "01:00")) - .assignment("bianca", "thu", "standby", slot("16:00", "00:00")) - .assignment("bianca", "fri", "standby", slot("17:00", "01:00")) - .assignment("bianca", "sat", "training", slot("12:00", "16:00"), slot("17:00", "01:00")) - .assignment("bianca", "sun", "request", slot("12:00", "16:00"), slot("17:00", "00:00")) - .assignment("artem", "tue", "off", slot("17:00", "00:00")) - .assignment("artem", "wed", "request", slot("17:00", "00:00")) - .assignment("artem", "thu", "request", slot("17:00", "00:00")) - .assignment("artem", "fri", "standby", slot("16:00", "01:00")) - .assignment("artem", "sat", "training", slot("12:00", "16:00"), slot("17:00", "01:00")) - .assignment("kharren", "wed", "request", slot("09:00", "17:00")) - .assignment("kharren", "thu", "request", slot("09:00", "17:00")) - .assignment("kharren", "fri", "standby", slot("09:00", "17:00")) - .assignment("kharren", "sat", "training", slot("16:00", "22:00")) - .assignment("kharren", "sun", "request", slot("09:00", "16:00"), slot("17:00", "22:00")) - .assignment("daria", "sat", "training", slot("16:00", "01:00")) - .assignment("daria", "sun", "request", slot("12:00", "16:00"), slot("17:00", "00:00")) - .assignment("violetta", "tue", "request", slot("09:00", "17:00")) - .assignment("violetta", "wed", "request", slot("09:00", "18:00")) - .assignment("violetta", "fri", "standby", slot("16:00", "22:00")) - .assignment("violetta", "sat", "request", slot("09:00", "17:00")) - .assignment("peter", "fri", "standby", slot("16:00", "01:00")) - .assignment("peter", "sat", "training", slot("12:00", "16:00"), slot("17:00", "01:00")) - .assignment("dmytro", "mon", "request", slot("17:00", "00:00")) - .assignment("dmytro", "sat", "training", slot("12:00", "16:00"), slot("17:00", "01:00")) - .assignment("dmytro", "sun", "request", slot("12:00", "16:00"), slot("17:00", "00:00")) - .assignment("daniel", "tue", "request", slot("17:00", "00:00")) - .assignment("daniel", "wed", "request", slot("17:00", "00:00")) - .assignment("daniel", "thu", "standby", slot("17:00", "00:00")) - .assignment("daniel", "fri", "standby", slot("17:00", "01:00")) - .assignment("daniel", "sun", "request", slot("12:00", "16:00"), slot("17:00", "00:00")) + .person("hugo", "HUGO", 110) + .assignment("rowan", "mon", "stock", slot("09:00", "18:00")) + .assignment("rowan", "thu", "request", slot("09:00", "18:00")) + .assignment("rowan", "fri", "training", slot("08:00", "16:00"), slot("16:00", "22:00")) + .assignment("rowan", "sun", "standby", slot("12:00", "16:00"), slot("16:00", "22:00")) + .assignment("nils", "mon", "request", slot("16:00", "00:00")) + .assignment("nils", "tue", "off", slot("12:00", "18:00")) + .assignment("nils", "wed", "request", slot("16:00", "00:00")) + .assignment("nils", "fri", "standby", slot("16:00", "01:00")) + .assignment("nils", "sun", "bar-back", slot("17:00", "03:00")) + .assignment("iva", "tue", "off", slot("16:00", "00:00")) + .assignment("iva", "wed", "request", slot("16:00", "00:00")) + .assignment("iva", "thu", "request", slot("16:00", "00:00")) + .assignment("iva", "fri", "standby", slot("16:00", "22:00")) + .assignment("iva", "sat", "training", slot("12:00", "16:00"), slot("17:00", "01:00")) + .assignment("pilar", "thu", "standby", slot("16:00", "00:00")) + .assignment("pilar", "fri", "standby", slot("17:00", "01:00")) + .assignment("pilar", "sat", "training", slot("12:00", "16:00"), slot("17:00", "01:00")) + .assignment("pilar", "sun", "request", slot("12:00", "16:00"), slot("17:00", "00:00")) + .assignment("omar", "tue", "off", slot("17:00", "00:00")) + .assignment("omar", "wed", "request", slot("17:00", "00:00")) + .assignment("omar", "thu", "request", slot("17:00", "00:00")) + .assignment("omar", "fri", "standby", slot("16:00", "01:00")) + .assignment("omar", "sat", "training", slot("12:00", "16:00"), slot("17:00", "01:00")) + .assignment("lena", "wed", "request", slot("09:00", "17:00")) + .assignment("lena", "thu", "request", slot("09:00", "17:00")) + .assignment("lena", "fri", "standby", slot("09:00", "17:00")) + .assignment("lena", "sat", "training", slot("16:00", "22:00")) + .assignment("lena", "sun", "request", slot("09:00", "16:00"), slot("17:00", "22:00")) + .assignment("freya", "sat", "training", slot("16:00", "01:00")) + .assignment("freya", "sun", "request", slot("12:00", "16:00"), slot("17:00", "00:00")) + .assignment("noor", "tue", "request", slot("09:00", "17:00")) + .assignment("noor", "wed", "request", slot("09:00", "18:00")) + .assignment("noor", "fri", "standby", slot("16:00", "22:00")) + .assignment("noor", "sat", "request", slot("09:00", "17:00")) + .assignment("bowen", "fri", "standby", slot("16:00", "01:00")) + .assignment("bowen", "sat", "training", slot("12:00", "16:00"), slot("17:00", "01:00")) + .assignment("sasha", "mon", "request", slot("17:00", "00:00")) + .assignment("sasha", "sat", "training", slot("12:00", "16:00"), slot("17:00", "01:00")) + .assignment("sasha", "sun", "request", slot("12:00", "16:00"), slot("17:00", "00:00")) + .assignment("hugo", "tue", "request", slot("17:00", "00:00")) + .assignment("hugo", "wed", "request", slot("17:00", "00:00")) + .assignment("hugo", "thu", "standby", slot("17:00", "00:00")) + .assignment("hugo", "fri", "standby", slot("17:00", "01:00")) + .assignment("hugo", "sun", "request", slot("12:00", "16:00"), slot("17:00", "00:00")) .footerNote("Add or remove people by editing only the people and assignments lists.") .footerNote("Category colours and labels are driven entirely from the shared category catalog.") .build(); diff --git a/examples/src/main/java/com/demcha/examples/support/LumaStudioInvoiceSampleData.java b/examples/src/main/java/com/demcha/examples/support/LumaStudioInvoiceSampleData.java new file mode 100644 index 000000000..175b40c11 --- /dev/null +++ b/examples/src/main/java/com/demcha/examples/support/LumaStudioInvoiceSampleData.java @@ -0,0 +1,108 @@ +package com.demcha.examples.support; + +import com.demcha.compose.document.templates.data.invoice.InvoiceBrand; +import com.demcha.compose.document.templates.data.invoice.InvoiceContactBlock; +import com.demcha.compose.document.templates.data.invoice.InvoiceMasthead; +import com.demcha.compose.document.templates.data.invoice.InvoiceNotesBlock; +import com.demcha.compose.document.templates.data.invoice.InvoicePaymentBlock; +import com.demcha.compose.document.templates.data.invoice.InvoiceRecipient; +import com.demcha.compose.document.templates.data.invoice.InvoiceServiceLines; +import com.demcha.compose.document.templates.data.invoice.InvoiceTotalsBlock; +import com.demcha.compose.document.templates.data.invoice.StructuredInvoiceData; +import com.demcha.compose.document.templates.data.invoice.StructuredInvoiceDocumentSpec; + +import java.math.BigDecimal; +import java.util.List; + +/** + * Shared sample data for the Luma Studio Invoice example. + * + *

The sample is the preset's reference content: a design studio's + * invoice with a monogram lockup, five labelled masthead metadata rows, + * a billed-to and a shipped-to party, five priced service lines with a VAT + * column, a subtotal / VAT stack closing on the total-due band, five bank + * fields, two closing notes and the sign-off band.

+ * + *

Kept in lockstep with the qa module's + * {@code LumaStudioInvoiceFixtures} — the two modules cannot share a source + * file, so a content change here belongs there too.

+ */ +public final class LumaStudioInvoiceSampleData { + + /** The break the sheet stacks its multi-line notes on. */ + private static final String NEWLINE = String.valueOf((char) 10); + + private LumaStudioInvoiceSampleData() { + } + + /** The single-page reference invoice. */ + public static StructuredInvoiceDocumentSpec sample() { + return StructuredInvoiceDocumentSpec.from(StructuredInvoiceData.builder() + .brand(new InvoiceBrand(null, "LUMA & CO. STUDIO", null, + "Branding. Design. Digital.", "L", "&Co.")) + .supplier(new InvoiceContactBlock("LUMA & CO. STUDIO", + List.of("Studio 3.02, The Loom", "14 Gower Street", + "London WC1E 6BT", "United Kingdom"), + "+44 (0)20 7946 0832", "hello@lumaandco.studio", + "www.lumaandco.studio", + "Company No.", "12578934", "VAT No.", "369 4567 89")) + .masthead(new InvoiceMasthead("INVOICE", List.of( + new InvoiceMasthead.Entry("INVOICE NO.", "INV-2024-0587", false), + new InvoiceMasthead.Entry("ISSUE DATE", "20 May 2024", false), + new InvoiceMasthead.Entry("DUE DATE", "19 June 2024", false), + new InvoiceMasthead.Entry("PAYMENT TERMS", "30 Days", false), + new InvoiceMasthead.Entry("CURRENCY", "GBP", false)))) + .billTo(new InvoiceRecipient("BILL TO", "Northfield Consulting Ltd", + "Attn: Sarah Mitchell", + List.of("21 Jubilee Way", "London SE1 3SS", "United Kingdom"), "", "")) + .shipTo(new InvoiceRecipient("SHIP TO", "Northfield Consulting Ltd", "", + List.of("The Foundry, 2nd Floor", "17-19 Great Suffolk Street", + "London SE1 0NS", "United Kingdom"), "", "")) + .serviceLines(serviceLines()) + .totals(new InvoiceTotalsBlock(List.of( + new InvoiceTotalsBlock.Row("SUBTOTAL", new BigDecimal("8500.00")), + new InvoiceTotalsBlock.Row("VAT (20%)", new BigDecimal("1700.00"))), + "TOTAL DUE", new BigDecimal("10200.00"))) + .notes(new InvoiceNotesBlock("NOTES", List.of( + "Thank you for your business." + NEWLINE + + "If you have any questions regarding" + NEWLINE + + "this invoice, please get in touch.", + "All work remains the intellectual property" + NEWLINE + + "of LUMA & CO. STUDIO until payment" + NEWLINE + + "has been received in full."), "", "")) + .payment(new InvoicePaymentBlock("PAYMENT DETAILS", List.of( + new InvoicePaymentBlock.Field("BANK", "Starling Bank"), + new InvoicePaymentBlock.Field("SORT CODE", "60-83-71"), + new InvoicePaymentBlock.Field("ACCOUNT NO.", "98765432"), + new InvoicePaymentBlock.Field("IBAN", "GB36 SRLG 6083 7198 7654 32"), + new InvoicePaymentBlock.Field("BIC", "SRLGGB2L")), + "Please make payment by bank transfer to:", + "Payment is due by 19 June 2024.", "", + "LUMA & CO. STUDIO LTD", + "Thank you for choosing LUMA & CO. STUDIO.")) + .currencyCode("GBP") + .build()); + } + + private static InvoiceServiceLines serviceLines() { + return new InvoiceServiceLines( + new InvoiceServiceLines.Columns("", "DESCRIPTION", "", "QTY", "UNIT PRICE", + "AMOUNT", "VAT"), + List.of( + line(1, "Brand Strategy Workshop", + "Discovery session, research & brand positioning.", "1200.00"), + line(2, "Visual Identity Design", + "Logo suite, colour palette, typography & guidelines.", "2650.00"), + line(3, "Website Design (Up to 8 pages)", + "UX/UI design for desktop and mobile.", "3200.00"), + line(4, "Copywriting", "Website copy & key messaging.", "850.00"), + line(5, "Project Management", + "Planning, coordination & client liaison.", "600.00"))); + } + + private static InvoiceServiceLines.Line line(int number, String title, String description, + String price) { + return new InvoiceServiceLines.Line(number, title, description, "", BigDecimal.ONE, "", + new BigDecimal(price), new BigDecimal(price), "20%"); + } +} diff --git a/examples/src/main/java/com/demcha/examples/support/MidnightNavySampleData.java b/examples/src/main/java/com/demcha/examples/support/MidnightNavySampleData.java new file mode 100644 index 000000000..9b6f85d16 --- /dev/null +++ b/examples/src/main/java/com/demcha/examples/support/MidnightNavySampleData.java @@ -0,0 +1,198 @@ +package com.demcha.examples.support; + +import com.demcha.compose.document.templates.core.identity.Contact; +import com.demcha.compose.document.templates.core.identity.Link; +import com.demcha.compose.document.templates.cv.data.CvDocument; +import com.demcha.compose.document.templates.cv.data.CvEntry; +import com.demcha.compose.document.templates.cv.data.CvIdentity; +import com.demcha.compose.document.templates.cv.data.CvName; +import com.demcha.compose.document.templates.cv.data.CvSkill; +import com.demcha.compose.document.templates.cv.data.EntriesSection; +import com.demcha.compose.document.templates.cv.data.ParagraphSection; +import com.demcha.compose.document.templates.cv.data.SkillGroup; +import com.demcha.compose.document.templates.cv.data.SkillsSection; +import com.demcha.compose.document.templates.cv.data.Slot; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +/** + * Shared sample data for the Midnight Navy CV example. + * + *

Kept in lockstep with the qa module's {@code MidnightNavyFixtures} — the + * two modules cannot share a source file, so a content change here belongs + * there too.

+ */ +public final class MidnightNavySampleData { + + /** The break a body stacks its lines on. */ + private static final String NEWLINE = String.valueOf((char) 10); + + private MidnightNavySampleData() { + } + + /** + * The canonical one-page CV — five contact rows including two links, two + * degrees, ten metered skills, three rated languages, the summary, three + * roles, three achievement discs and two certifications. + * + * @return the document + */ + public static CvDocument sample() { + List placements = new ArrayList<>(); + placements.add(new CvDocument.Placement(Slot.SIDEBAR, education())); + placements.add(new CvDocument.Placement(Slot.SIDEBAR, skills())); + placements.add(new CvDocument.Placement(Slot.SIDEBAR, languages())); + placements.add(new CvDocument.Placement(Slot.MAIN, summary())); + placements.add(new CvDocument.Placement(Slot.MAIN, experience())); + placements.add(new CvDocument.Placement(Slot.MAIN, achievements())); + placements.add(new CvDocument.Placement(Slot.MAIN, certifications())); + return new CvDocument(identity(), placements); + } + + private static CvIdentity identity() { + return new CvIdentity( + CvName.of("Alex", "Morgan"), + "Marketing Manager", + new Contact("+1 (555) 123-4567", + "alex.morgan@email.com", + "New York, NY, USA"), + List.of(new Link("LinkedIn", "https://www.linkedin.com/in/alexmorgan"), + new Link("Portfolio", "https://alexmorgan.com")), + Optional.empty()); + } + + /** The degrees, newest first. */ + private static EntriesSection education() { + return new EntriesSection("EDUCATION", List.of( + CvEntry.builder("MASTER OF BUSINESS ADMINISTRATION") + .subtitle("University of Chicago Booth School of Business") + .date("2016 \u2013 2018") + .build(), + CvEntry.builder("BACHELOR OF SCIENCE IN MARKETING") + .subtitle("Boston University") + .date("2012 \u2013 2016") + .build())); + } + + /** The metered skills, as one flat group. */ + private static SkillsSection skills() { + List entries = new ArrayList<>(); + entries.add(CvSkill.of("Strategic Planning", 0.82)); + entries.add(CvSkill.of("Digital Marketing", 0.84)); + entries.add(CvSkill.of("Market Research", 0.76)); + entries.add(CvSkill.of("Brand Management", 0.84)); + entries.add(CvSkill.of("Data Analysis", 0.81)); + entries.add(CvSkill.of("Project Management", 0.68)); + entries.add(CvSkill.of("Google Analytics", 0.83)); + entries.add(CvSkill.of("SEO / SEM", 0.76)); + entries.add(CvSkill.of("Microsoft Office Suite", 0.75)); + entries.add(CvSkill.of("Communication", 0.96)); + return new SkillsSection("SKILLS", + List.of(new SkillGroup("SKILLS", entries))); + } + + /** The languages. The design shows a rating in fifths. */ + private static SkillsSection languages() { + List entries = new ArrayList<>(); + entries.add(CvSkill.of("English", 1.0)); + entries.add(CvSkill.of("Spanish", 0.8)); + entries.add(CvSkill.of("French", 0.6)); + return new SkillsSection("LANGUAGES", + List.of(new SkillGroup("LANGUAGES", entries))); + } + + /** The opening prose. */ + private static ParagraphSection summary() { + return new ParagraphSection("PROFESSIONAL SUMMARY", + "Results-driven Marketing Manager with 6+ years of " + + "experience in developing and executing data-driven " + + "marketing strategies that drive brand growth and " + + "customer engagement. Proven track record in leading " + + "cross-functional teams, managing successful campaigns, " + + "and delivering measurable results. Passionate about " + + "building strong brands and creating impactful marketing " + + "initiatives."); + } + + /** The roles held, each a headline over its bullets. */ + private static EntriesSection experience() { + return new EntriesSection("EXPERIENCE", List.of( + CvEntry.builder("MARKETING MANAGER") + .subtitle("Starwave Solutions") + .place("New York, NY") + .date("2021 \u2013 Present") + .body(String.join(NEWLINE, + "Develop and implement comprehensive marketing strategies " + + "that increased brand awareness by 40% and lead " + + "generation by 35%.", + "Manage a team of 6 marketing professionals and " + + "collaborate with sales, product, and design teams to " + + "drive campaign success.", + "Oversee digital campaigns across SEO, SEM, social media, " + + "and email marketing resulting in a 25% increase in ROI.", + "Analyze market trends and competitor activities to " + + "identify new opportunities and optimize marketing " + + "efforts.")) + .build(), + CvEntry.builder("SENIOR MARKETING SPECIALIST") + .subtitle("BrightLine Technologies") + .place("New York, NY") + .date("2018 \u2013 2021") + .body(String.join(NEWLINE, + "Executed multi-channel marketing campaigns that improved " + + "customer engagement by 30%.", + "Conducted market research and data analysis to support " + + "strategic decision-making.", + "Managed social media channels and content strategy, " + + "growing followers by 50%.", + "Coordinated with creative teams to develop compelling " + + "content and visuals.")) + .build(), + CvEntry.builder("MARKETING COORDINATOR") + .subtitle("Peak Performance Group") + .place("Boston, MA") + .date("2016 \u2013 2018") + .body(String.join(NEWLINE, + "Supported the development and execution of marketing " + + "plans and campaigns.", + "Assisted in organizing events, webinars, and promotional " + + "activities.", + "Monitored campaign performance and prepared reports for " + + "management.", + "Maintained and updated marketing databases and CRM " + + "systems.")) + .build())); + } + + /** + * The discs. A card has one line and no heading over it, so the line is + * the entry's title. + */ + private static EntriesSection achievements() { + return new EntriesSection("ACHIEVEMENTS", List.of( + CvEntry.builder("Increased brand awareness by 40% within 2 years.") + .icon("trophy") + .build(), + CvEntry.builder("Boosted lead generation by 35% annually.") + .icon("growth") + .build(), + CvEntry.builder("Recognized as Top Performer in 2022.") + .icon("award") + .build())); + } + + /** The certifications, one to a column. */ + private static EntriesSection certifications() { + return new EntriesSection("CERTIFICATIONS", List.of( + CvEntry.builder("Google Analytics Certified") + .subtitle("Google") + .date("2020") + .build(), + CvEntry.builder("HubSpot Content Marketing Certified") + .subtitle("HubSpot Academy") + .date("2021") + .build())); + } +} diff --git a/examples/src/main/java/com/demcha/examples/support/NavySidebarSampleData.java b/examples/src/main/java/com/demcha/examples/support/NavySidebarSampleData.java new file mode 100644 index 000000000..6068025ca --- /dev/null +++ b/examples/src/main/java/com/demcha/examples/support/NavySidebarSampleData.java @@ -0,0 +1,148 @@ +package com.demcha.examples.support; + +import com.demcha.compose.document.image.DocumentImageData; +import com.demcha.compose.document.templates.core.identity.Contact; +import com.demcha.compose.document.templates.core.identity.Link; +import com.demcha.compose.document.templates.cv.data.CvDocument; +import com.demcha.compose.document.templates.cv.data.CvEntry; +import com.demcha.compose.document.templates.cv.data.CvIdentity; +import com.demcha.compose.document.templates.cv.data.EntriesSection; +import com.demcha.compose.document.templates.cv.data.ParagraphSection; +import com.demcha.compose.document.templates.cv.data.RowStyle; +import com.demcha.compose.document.templates.cv.data.RowsSection; +import com.demcha.compose.document.templates.cv.data.SkillGroup; +import com.demcha.compose.document.templates.cv.data.SkillsSection; +import com.demcha.compose.document.templates.cv.data.Slot; + +import java.io.IOException; +import java.io.InputStream; +import java.io.UncheckedIOException; +import java.util.Objects; + +/** + * Sample data for the Navy Sidebar CV example. + * + *

Sized to the design: the preset draws a fixed one-page sheet, and a CV + * much longer than this one does not compose at all — the two columns are a + * single atomic row, so it raises {@code AtomicNodeTooLargeException}.

+ * + *

The portrait is a neutral silhouette rather than a photograph, because + * the example ships in the repository; a real CV passes the candidate's own + * image to {@code CvIdentity.Builder.portrait(...)} the same way.

+ */ +public final class NavySidebarSampleData { + + private static final String PORTRAIT = "/cv-portrait-placeholder.png"; + + private NavySidebarSampleData() { + } + + /** + * A marketing manager's one-page CV. + * + * @return the sample document + */ + public static CvDocument sample() { + return CvDocument.builder() + .identity(CvIdentity.builder() + .name("Priya", "Raghavan") + .jobTitle("Marketing Manager") + .contact(new Contact("+44 20 7946 0812", + "priya.r@example.com", + "Bristol, United Kingdom")) + .link(new Link("linkedin.com/in/praghavan", + "https://linkedin.com/in/praghavan")) + .portrait(portrait()) + .build()) + .section(Slot.MAIN, new ParagraphSection("Summary", + "Marketing manager with eight years in B2B software, most of it" + + " running the demand side end to end: positioning, the" + + " campaigns that carry it, and the reporting that says" + + " whether it worked. Happiest with a small team and a" + + " short feedback loop.")) + .section(Slot.SIDEBAR, EntriesSection.builder("Education") + .entry(CvEntry.builder("MSc Marketing Analytics") + .subtitle("University of Bristol") + .date("2015 - 2016") + .place("Bristol, UK") + .build()) + .entry(CvEntry.builder("BA Business Management") + .subtitle("University of Leeds") + .date("2012 - 2015") + .place("Leeds, UK") + .build()) + .build()) + .section(Slot.SIDEBAR, SkillsSection.of("Skills", SkillGroup.of("Core", + "Positioning", + "Demand Generation", + "Marketing Analytics", + "SEO / SEM", + "Lifecycle Email", + "Content Strategy", + "HubSpot", + "Looker"))) + .section(Slot.SIDEBAR, RowsSection.builder("Languages", RowStyle.PLAIN) + .row("English", "Native") + .row("Tamil", "Native") + .row("German", "Intermediate") + .build()) + .section(Slot.MAIN, EntriesSection.builder("Experience") + .entry("Marketing Manager", + "Ardent Systems, Bristol, UK", + "Mar 2021 - Present", + String.join("\n", + "Rebuilt the demand programme around three named" + + " segments, lifting qualified pipeline 44% in" + + " the first year.", + "Runs a team of four across content, lifecycle and" + + " events, and the agency relationship behind" + + " paid search.", + "Replaced a weekly spreadsheet with a Looker model the" + + " sales team reads without asking for it.")) + .entry("Senior Marketing Executive", + "Halworth Digital, Bristol, UK", + "Sep 2018 - Feb 2021", + String.join("\n", + "Owned lifecycle email end to end, taking trial-to-paid" + + " conversion from 9% to 14%.", + "Launched the customer-story programme that still" + + " supplies the sales deck.", + "Ran competitor and win-loss research each quarter for" + + " the product team.")) + .entry("Marketing Executive", + "Kite & Compass, Leeds, UK", + "Oct 2016 - Aug 2018", + String.join("\n", + "Planned and ran campaigns across search, social and" + + " trade press for six retail clients.", + "Built the reporting pack the agency used for every" + + " monthly review.")) + .build()) + .section(Slot.MAIN, new ParagraphSection("Achievements", String.join("\n", + "Grew organic sessions 60% in a year by rebuilding the site around" + + " search intent rather than the org chart.", + "Cut cost per qualified lead by a third by retiring two channels and" + + " funding the one that worked.", + "Named marketer of the year at Ardent in 2024."))) + .section(Slot.MAIN, new ParagraphSection("Certifications", String.join("\n", + "Google Analytics Individual Qualification", + "HubSpot Content Marketing Certification", + "Professional Certificate in Marketing, CIM"))) + .build(); + } + + /** + * The packaged silhouette that stands in for a photograph. + * + * @return the portrait image data + */ + private static DocumentImageData portrait() { + try (InputStream in = Objects.requireNonNull( + NavySidebarSampleData.class.getResourceAsStream(PORTRAIT), + "cv-portrait-placeholder.png missing from examples/src/main/resources/")) { + return DocumentImageData.fromBytes(in.readAllBytes()); + } catch (IOException e) { + throw new UncheckedIOException("Failed to read the sample portrait", e); + } + } +} diff --git a/examples/src/main/java/com/demcha/examples/support/OrangeOpsSampleData.java b/examples/src/main/java/com/demcha/examples/support/OrangeOpsSampleData.java new file mode 100644 index 000000000..a3c3d4ce8 --- /dev/null +++ b/examples/src/main/java/com/demcha/examples/support/OrangeOpsSampleData.java @@ -0,0 +1,252 @@ +package com.demcha.examples.support; + +import com.demcha.compose.document.templates.core.identity.Contact; +import com.demcha.compose.document.templates.core.identity.Link; +import com.demcha.compose.document.templates.cv.data.CvDocument; +import com.demcha.compose.document.templates.cv.data.CvEntry; +import com.demcha.compose.document.templates.cv.data.CvIdentity; +import com.demcha.compose.document.templates.cv.data.CvName; +import com.demcha.compose.document.templates.cv.data.EntriesSection; +import com.demcha.compose.document.templates.cv.data.ParagraphSection; +import com.demcha.compose.document.templates.cv.data.SkillGroup; +import com.demcha.compose.document.templates.cv.data.SkillsSection; +import com.demcha.compose.document.templates.cv.data.Slot; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +/** + * Shared sample data for the Orange Ops CV example. + * + *

The sample is the preset's reference content: a one-page CV with four + * contact items including one link, eleven skills, four achievement cards, a + * degree, four certifications, the opening prose, three roles, a four-metric + * strip and four closing lines.

+ * + *

Kept in lockstep with the qa module's {@code OrangeOpsFixtures} — the two + * modules cannot share a source file, so a content change here belongs there + * too.

+ */ +public final class OrangeOpsSampleData { + + /** The break a body stacks its lines on. */ + private static final String NEWLINE = String.valueOf((char) 10); + + private OrangeOpsSampleData() { + } + + /** + * The sample CV. + * + * @return the document the example renders + */ + public static CvDocument sample() { + List placements = new ArrayList<>(); + placements.add(new CvDocument.Placement(Slot.SIDEBAR, skills())); + placements.add(new CvDocument.Placement(Slot.SIDEBAR, achievements())); + placements.add(new CvDocument.Placement(Slot.SIDEBAR, education())); + placements.add(new CvDocument.Placement(Slot.SIDEBAR, certifications())); + placements.add(new CvDocument.Placement(Slot.MAIN, profile())); + placements.add(new CvDocument.Placement(Slot.MAIN, experience())); + placements.add(new CvDocument.Placement(Slot.MAIN, metrics())); + placements.add(new CvDocument.Placement(Slot.MAIN, additional())); + return new CvDocument(identity(), placements); + } + + private static CvIdentity identity() { + return new CvIdentity( + CvName.of("Marcus", "Bennett"), + "Warehouse Operations Supervisor", + new Contact("+44 7700 900123", + "marcus.bennett@email.com", + "Birmingham, West Midlands, UK"), + List.of(new Link("LinkedIn", "https://www.linkedin.com/in/marcusbennett")), + Optional.empty()); + } + + /** The skills, as one flat group — the design draws no group names. */ + private static SkillsSection skills() { + return new SkillsSection("KEY SKILLS", List.of(SkillGroup.ofNames("KEY SKILLS", List.of( + "Warehouse Operations Management", + "Team Leadership & Development", + "Inventory Control & Accuracy", + "Logistics & Freight Coordination", + "Order Fulfilment & Dispatch", + "KPI Monitoring & Reporting", + "Lean Process Improvement", + "Health & Safety Compliance", + "WMS & ERP Systems", + "Problem Solving & Decision Making", + "Stakeholder Communication")))); + } + + /** The achievement cards: a mark, a title and a body each. */ + private static EntriesSection achievements() { + return new EntriesSection("ACHIEVEMENTS", List.of( + CvEntry.builder("15% PRODUCTIVITY INCREASE") + .icon("achievement-productivity") + .body("Improved warehouse productivity by 15% through workflow " + + "redesign and performance management.") + .build(), + CvEntry.builder("99.2% INVENTORY ACCURACY") + .icon("achievement-accuracy") + .body("Maintained inventory accuracy above 99% for 12 " + + "consecutive months through cycle count discipline and " + + "process control.") + .build(), + CvEntry.builder("ZERO LOST TIME INCIDENTS") + .icon("achievement-safety") + .body("Led a safety-first culture resulting in zero lost time " + + "incidents over 24 months across operations.") + .build(), + CvEntry.builder("\u00a3280K COST SAVINGS") + .icon("achievement-savings") + .body("Delivered \u00a3280K in annual savings by reducing waste, " + + "lowering overtime and renegotiating carrier contracts.") + .build())); + } + + /** The degree, its institution, place and years one line each. */ + private static EntriesSection education() { + return new EntriesSection("EDUCATION", List.of( + CvEntry.builder("BSc (Hons) Logistics & Supply Chain Management") + .icon("graduation") + .body(String.join(NEWLINE, + "De Montfort University", + "Leicester, UK", + "2011 \u2013 2014")) + .build())); + } + + /** The certifications: a title over its issuer. */ + private static EntriesSection certifications() { + return new EntriesSection("CERTIFICATIONS", List.of( + CvEntry.builder("IOSH Managing Safely") + .subtitle("Institution of Occupational Safety and Health \u2013 2021") + .build(), + CvEntry.builder("Lean Six Sigma Yellow Belt") + .subtitle("The Knowledge Academy \u2013 2020") + .build(), + CvEntry.builder("Counterbalance Forklift Truck Instructor") + .subtitle("RTITB \u2013 2019") + .build(), + CvEntry.builder("First Aid at Work") + .subtitle("St John Ambulance \u2013 2019") + .build())); + } + + /** The opening prose. */ + private static ParagraphSection profile() { + return new ParagraphSection("PROFESSIONAL PROFILE", + "Results-driven Warehouse Operations Supervisor with 8+ " + + "years of experience leading high-performing teams and " + + "optimising warehouse performance in fast-paced " + + "distribution environments. Proven track record of " + + "improving operational efficiency, ensuring inventory " + + "accuracy, and delivering excellent service levels while " + + "maintaining a strong focus on health and safety. Adept " + + "at using data to drive decisions, streamline processes, " + + "and achieve measurable results."); + } + + /** The roles held, each a headline over its bullets. */ + private static EntriesSection experience() { + return new EntriesSection("WORK EXPERIENCE", List.of( + CvEntry.builder("Warehouse Operations Supervisor") + .subtitle("ExpressLink Distribution Ltd | Birmingham, UK") + .date("Mar 2021 \u2013 Present") + .body(String.join(NEWLINE, + "Oversee daily warehouse operations across a 150,000 sq " + + "ft facility, managing a team of 45 staff across inbound, " + + "storage, picking, packing and dispatch.", + "Ensure timely and accurate order fulfilment, achieving " + + "98%+ on-time delivery.", + "Monitor and maintain inventory accuracy through cycle " + + "counts, audits and investigation of variances.", + "Analyse KPIs and operational data to identify trends and " + + "implement improvements.", + "Coordinate with transport and customer service teams to " + + "resolve delivery issues and improve service levels.", + "Drive a culture of safety and accountability; deliver " + + "regular training and ensure full compliance with H&S " + + "regulations.", + "Lead continuous improvement initiatives using Lean " + + "principles to reduce waste and enhance productivity.")) + .build(), + CvEntry.builder("Warehouse Team Leader") + .subtitle("Midland Gate Logistics | Coventry, UK") + .date("May 2017 \u2013 Feb 2021") + .body(String.join(NEWLINE, + "Led a team of 25 warehouse operatives across day-to-day " + + "operations.", + "Managed inbound receipts, putaway, picking and shipping " + + "activities.", + "Maintained accurate stock records and completed regular " + + "cycle counts.", + "Supported the implementation of a new WMS, improving " + + "stock visibility and reporting.", + "Reduced picking errors by 23% through training and " + + "process optimisation.")) + .build(), + CvEntry.builder("Warehouse Coordinator") + .subtitle("TotalSupply Solutions | Northampton, UK") + .date("Jun 2015 \u2013 Apr 2017") + .body(String.join(NEWLINE, + "Coordinated daily operations and allocated tasks to " + + "warehouse staff.", + "Monitored stock levels and initiated replenishment to " + + "avoid stockouts.", + "Assisted with KPI reporting and performance reviews.", + "Ensured compliance with company policies and health & " + + "safety standards.")) + .build())); + } + + /** + * The metric strip. The parenthetical is part of the berth title: the + * preset splits it off and sets it smaller. + */ + private static EntriesSection metrics() { + return new EntriesSection("KEY KPI SNAPSHOT (Recent 12 Months)", List.of( + CvEntry.builder("15%") + .icon("kpi-productivity") + .body("Productivity" + NEWLINE + "Increase") + .build(), + CvEntry.builder("99.2%") + .icon("kpi-accuracy") + .body("Inventory" + NEWLINE + "Accuracy") + .build(), + CvEntry.builder("98.4%") + .icon("kpi-delivery") + .body("On-Time" + NEWLINE + "Delivery") + .build(), + CvEntry.builder("0") + .icon("kpi-safety") + .body("Lost Time" + NEWLINE + "Incidents") + .build())); + } + + /** The closing lines: a mark, a label and its value. */ + private static EntriesSection additional() { + return new EntriesSection("ADDITIONAL INFORMATION", List.of( + CvEntry.builder("Systems:") + .icon("systems") + .body("SAP EWM, Manhattan WMS, Microsoft Dynamics, Excel " + + "(Advanced)") + .build(), + CvEntry.builder("Languages:") + .icon("languages") + .body("English (Native)") + .build(), + CvEntry.builder("Driving Licence:") + .icon("driving") + .body("Full UK Driving Licence") + .build(), + CvEntry.builder("Interests:") + .icon("interests") + .body("Strength training, Motorsport, Volunteering with local " + + "food banks") + .build())); + } +} diff --git a/examples/src/main/java/com/demcha/examples/support/PaymentsInvoiceSampleData.java b/examples/src/main/java/com/demcha/examples/support/PaymentsInvoiceSampleData.java new file mode 100644 index 000000000..3f786c57e --- /dev/null +++ b/examples/src/main/java/com/demcha/examples/support/PaymentsInvoiceSampleData.java @@ -0,0 +1,156 @@ +package com.demcha.examples.support; + +import com.demcha.compose.document.templates.data.invoice.InvoiceBrand; +import com.demcha.compose.document.templates.data.invoice.InvoiceContactBlock; +import com.demcha.compose.document.templates.data.invoice.InvoiceMasthead; +import com.demcha.compose.document.templates.data.invoice.InvoiceNotesBlock; +import com.demcha.compose.document.templates.data.invoice.InvoicePaymentBlock; +import com.demcha.compose.document.templates.data.invoice.InvoiceRecipient; +import com.demcha.compose.document.templates.data.invoice.InvoiceServiceLines; +import com.demcha.compose.document.templates.data.invoice.InvoiceSummaryBlock; +import com.demcha.compose.document.templates.data.invoice.InvoiceTotalsBlock; +import com.demcha.compose.document.templates.data.invoice.StructuredInvoiceData; + +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.List; + +/** + * Shared sample data for the Payments invoice example. + * + *

Kept in lockstep with the qa module's {@code PaymentsInvoiceFixtures} — the + * two modules cannot share a source file, so a content change here belongs + * there too.

+ */ +public final class PaymentsInvoiceSampleData { + + private PaymentsInvoiceSampleData() { + } + + /** + * The sample invoice — seven metadata rows, two addressed parties, six + * marked service lines, seven payment fields, two summed rows and a + * two-line note. + * + * @return the document the example renders + */ + public static StructuredInvoiceData sample() { + return new StructuredInvoiceData( + brand(), supplier(), masthead(), billTo(), shipTo(), + new InvoiceSummaryBlock("", "", ""), + serviceLines(), totals(), payment(), notes(), "GBP"); + } + + /** No logo: the wordmark is what a document without one falls back to. */ + private static InvoiceBrand brand() { + return new InvoiceBrand(null, "Meridian", "", "", "", ""); + } + + private static InvoiceContactBlock supplier() { + return new InvoiceContactBlock( + "Meridian Payments Ltd.", + List.of("1 Harbour Exchange", "Canary Wharf", "London E14 9GE", + "United Kingdom"), + "", "", "", + "", "Company No. 09048900", + "", "VAT No. GB 123 4567 89"); + } + + private static InvoiceMasthead masthead() { + List entries = new ArrayList<>(); + entries.add(new InvoiceMasthead.Entry("Invoice Number", "INV-2025-05-10472", false)); + entries.add(new InvoiceMasthead.Entry("Invoice Date", "10 May 2025", false)); + entries.add(new InvoiceMasthead.Entry("Billing Period", "1 - 30 April 2025", false)); + entries.add(new InvoiceMasthead.Entry("Account ID", "acct_4Q8xTb2mKp", false)); + entries.add(new InvoiceMasthead.Entry("Payment Terms", "Net 30", false)); + entries.add(new InvoiceMasthead.Entry("Due Date", "14 June 2025", false)); + entries.add(new InvoiceMasthead.Entry("Status", "UNPAID", true)); + return new InvoiceMasthead("INVOICE", entries); + } + + /** The subline is where a billed party's registration goes on this sheet. */ + private static InvoiceRecipient billTo() { + return new InvoiceRecipient("BILL TO", "Northwind Ltd.", + "VAT No. GB 987 6543 21", + List.of("Attention: Finance Team", "42 Bridgewater Street", + "Manchester M15 4QT", "United Kingdom"), + "", ""); + } + + private static InvoiceRecipient shipTo() { + return new InvoiceRecipient("SHIP TO", "Northwind Ltd.", "", + List.of("Operations Centre", "7 Kingsway Park", + "Leeds LS12 6BD", "United Kingdom"), + "", ""); + } + + private static InvoiceServiceLines serviceLines() { + InvoiceServiceLines.Columns columns = new InvoiceServiceLines.Columns( + "", "DESCRIPTION", "", "QUANTITY", "UNIT PRICE", "AMOUNT", "VAT"); + List lines = new ArrayList<>(); + lines.add(line(1, "Subscription Billing", + "Subscription management and recurring billing", + "1", "250.00", "250.00", "20%", "card")); + lines.add(line(2, "Payment Processing", + "Online card payments processing (volume tier)", + "1", "1,180.00", "1,180.00", "20%", "card-settings")); + lines.add(line(3, "Fraud Screening", + "Advanced fraud screening and rules", + "1", "320.00", "320.00", "20%", "shield")); + lines.add(line(4, "Company Formation", + "Incorporation and compliance filings", + "1", "180.00", "180.00", "20%", "globe")); + lines.add(line(5, "In-Person Terminals", + "Terminal rental and firmware updates", + "4", "39.00", "156.00", "20%", "mobile")); + lines.add(line(6, "Priority Support", + "Named engineer, one-hour response", + "1", "100.00", "100.00", "20%", "headset")); + return new InvoiceServiceLines(columns, lines); + } + + private static InvoiceServiceLines.Line line(int number, String title, String description, + String quantity, String unitPrice, + String amount, String vat, String icon) { + return new InvoiceServiceLines.Line(number, title, description, "", + decimal(quantity), "", decimal(unitPrice), decimal(amount), vat, icon); + } + + private static InvoiceTotalsBlock totals() { + List rows = new ArrayList<>(); + rows.add(new InvoiceTotalsBlock.Row("Subtotal (excl. VAT)", decimal("2,186.00"))); + rows.add(new InvoiceTotalsBlock.Row("VAT at 20%", decimal("437.20"))); + return new InvoiceTotalsBlock(rows, "TOTAL DUE", decimal("2,623.20")); + } + + private static InvoicePaymentBlock payment() { + List fields = new ArrayList<>(); + fields.add(new InvoicePaymentBlock.Field("Bank Name", "Harbour Bank plc")); + fields.add(new InvoicePaymentBlock.Field("Account Name", "Meridian Payments Ltd.")); + fields.add(new InvoicePaymentBlock.Field("Sort Code", "40-02-50")); + fields.add(new InvoicePaymentBlock.Field("Account Number", "71295408")); + fields.add(new InvoicePaymentBlock.Field("IBAN", "GB29 HRBK 4002 5071 2954 08")); + fields.add(new InvoicePaymentBlock.Field("SWIFT / BIC", "HRBKGB2L")); + fields.add(new InvoicePaymentBlock.Field("Reference", "INV-2025-05-10472")); + return new InvoicePaymentBlock("PAYMENT DETAILS", fields, + "Please include the invoice number as payment reference.", + "Due by 14 June 2025", + "Payment due by 14 June 2025", + "", + "Questions? We're here to help."); + } + + private static InvoiceNotesBlock notes() { + return new InvoiceNotesBlock("NOTES", + List.of("Thank you for your business. This invoice covers services and " + + "fees for the period stated above.", + "Late payment may incur interest at the statutory rate."), + "support@meridianpayments.example", + "+44 (0) 20 3966 1900"); + } + + /** A printed figure back to a number: the grouping comes off. */ + private static BigDecimal decimal(String printed) { + return new BigDecimal(printed.replace(",", "")); + } +} diff --git a/examples/src/main/java/com/demcha/examples/support/ProfessionalSidebarSampleData.java b/examples/src/main/java/com/demcha/examples/support/ProfessionalSidebarSampleData.java new file mode 100644 index 000000000..75b1de94b --- /dev/null +++ b/examples/src/main/java/com/demcha/examples/support/ProfessionalSidebarSampleData.java @@ -0,0 +1,125 @@ +package com.demcha.examples.support; + +import com.demcha.compose.document.templates.core.identity.Contact; +import com.demcha.compose.document.templates.core.identity.Link; +import com.demcha.compose.document.templates.cv.data.CvDocument; +import com.demcha.compose.document.templates.cv.data.CvIdentity; +import com.demcha.compose.document.templates.cv.data.CvSkill; +import com.demcha.compose.document.templates.cv.data.EntriesSection; +import com.demcha.compose.document.templates.cv.data.ParagraphSection; +import com.demcha.compose.document.templates.cv.data.SkillGroup; +import com.demcha.compose.document.templates.cv.data.SkillsSection; +import com.demcha.compose.document.templates.cv.data.Slot; + +import java.util.List; + +/** + * Sample data for the Professional Sidebar CV example. + * + *

Sized to the design: the preset draws a fixed one-page sheet, and a CV + * much longer than this one does not compose at all — the two columns are a + * single atomic row, so it raises {@code AtomicNodeTooLargeException}. The + * sidebar's one-line rows are sized to it too: a degree, skill or language + * longer than the narrow column measures overflows the row it is anchored in + * rather than wrapping inside it.

+ */ +public final class ProfessionalSidebarSampleData { + + /** The en dash the design sets between dates. */ + private static final String DASH = "–"; + + private ProfessionalSidebarSampleData() { + } + + /** + * A backend engineer's one-page CV. + * + * @return the sample document + */ + public static CvDocument sample() { + return CvDocument.builder() + .identity(CvIdentity.builder() + .name("MARTA", "LINDQVIST") + .jobTitle("SENIOR BACKEND ENGINEER") + .contact(new Contact("+46 8 123 456 78", + "marta.l@example.com", + "Stockholm, Sweden")) + .link(new Link("linkedin.com/in/mlindqvist", + "https://linkedin.com/in/mlindqvist")) + .link(new Link("martalindqvist.dev", + "https://martalindqvist.dev")) + .build()) + .section(Slot.MAIN, new ParagraphSection("PROFILE", + "Backend engineer with nine years on payment and identity systems," + + " most of it owning services end to end: API design, the data" + + " model underneath, and the on-call rota that follows." + + " Comfortable in a team that ships small and often, and the" + + " one who writes the runbook nobody asked for.")) + .section(Slot.SIDEBAR, SkillsSection.of("SKILLS", + new SkillGroup("Core", List.of( + CvSkill.of("Java", 0.9), + CvSkill.of("Kotlin", 0.72), + CvSkill.of("Spring Boot", 0.86), + CvSkill.of("PostgreSQL", 0.8), + CvSkill.of("Kafka", 0.68), + CvSkill.of("Kubernetes", 0.62), + CvSkill.of("Terraform", 0.55), + CvSkill.of("Observability", 0.74), + CvSkill.of("Threat Modelling", 0.6), + CvSkill.of("Mentoring", 0.78))))) + .section(Slot.SIDEBAR, EntriesSection.builder("EDUCATION") + .entry("MSc COMPUTER SCIENCE", "KTH Royal Institute", + "2014 " + DASH + " 2016", "") + .entry("BSc SOFTWARE DESIGN", "Uppsala University", + "2011 " + DASH + " 2014", "") + .build()) + .section(Slot.SIDEBAR, SkillsSection.of("LANGUAGES", + new SkillGroup("Spoken", List.of( + CvSkill.of("Swedish", 1.0), + CvSkill.of("English", 1.0), + CvSkill.of("German", 0.6))))) + .section(Slot.MAIN, EntriesSection.builder("EXPERIENCE") + .entry("SENIOR BACKEND ENGINEER", + "Nordkassa | Stockholm, SE", + "2021 " + DASH + " Present", + String.join("\n", + "Led the split of the settlement monolith into four" + + " services with no customer-visible downtime.", + "Cut p99 authorisation latency from 780ms to 210ms by" + + " reworking the ledger write path.", + "Introduced contract tests between payments and risk," + + " ending a class of release-day rollbacks.", + "Mentors three engineers; runs the internal design" + + " review.")) + .entry("BACKEND ENGINEER", + "Vinge Identity | Gothenburg, SE", + "2018 " + DASH + " 2021", + String.join("\n", + "Built the OAuth2 and OIDC provider now serving eleven" + + " internal products.", + "Moved session storage to Redis with a migration that" + + " ran live for six weeks.", + "Wrote the audit pipeline the compliance team still" + + " reports from.")) + .entry("SOFTWARE ENGINEER", + "Almgren Data | Uppsala, SE", + "2016 " + DASH + " 2018", + String.join("\n", + "Delivered ingest and reporting services for retail" + + " forecasting customers.", + "Replaced nightly batch reconciliation with a streaming" + + " job on Kafka.")) + .build()) + .section(Slot.MAIN, EntriesSection.builder("PROJECTS") + .entry("LEDGERKIT", "", "2024", + "Open-source double-entry ledger for JVM services, with a" + + " property-based test suite over the posting rules.") + .entry("ONCALL DIGEST", "", "2022", + "Weekly incident summariser that reads alert history and drafts" + + " the retrospective agenda.") + .build()) + .section(Slot.MAIN, new ParagraphSection("REFERENCES", + "Available on request.")) + .build(); + } +} diff --git a/examples/src/main/java/com/demcha/examples/support/SerifHeadlineSampleData.java b/examples/src/main/java/com/demcha/examples/support/SerifHeadlineSampleData.java new file mode 100644 index 000000000..5c636db27 --- /dev/null +++ b/examples/src/main/java/com/demcha/examples/support/SerifHeadlineSampleData.java @@ -0,0 +1,177 @@ +package com.demcha.examples.support; + +import com.demcha.compose.document.templates.core.identity.Contact; +import com.demcha.compose.document.templates.core.identity.Link; +import com.demcha.compose.document.templates.cv.data.CvDocument; +import com.demcha.compose.document.templates.cv.data.CvEntry; +import com.demcha.compose.document.templates.cv.data.CvIdentity; +import com.demcha.compose.document.templates.cv.data.CvSkill; +import com.demcha.compose.document.templates.cv.data.EntriesSection; +import com.demcha.compose.document.templates.cv.data.ParagraphSection; +import com.demcha.compose.document.templates.cv.data.RowStyle; +import com.demcha.compose.document.templates.cv.data.RowsSection; +import com.demcha.compose.document.templates.cv.data.SkillsSection; + +import java.util.List; + +/** + * Sample data for the Serif Headline CV example. + * + *

Sized to the design: the preset draws a fixed one-page sheet, and a CV + * much longer than this one does not compose at all — the body is a single + * atomic row, so it raises {@code AtomicNodeTooLargeException}.

+ * + *

The project and achievement marks are this preset's own vocabulary — + * {@code cart}, {@code api}, {@code trophy}, {@code chart}, {@code rocket} — + * and each entry names the one it wants. The marks come in two colours: + * {@code cart} and {@code api} are drawn in the navy of the design's ink, + * the other three in its gold. This sample keeps a band to one colour, since + * mixing them inside one row reads as a mistake rather than a choice.

+ */ +public final class SerifHeadlineSampleData { + + private static final String NEWLINE = String.valueOf((char) 10); + + private SerifHeadlineSampleData() { + } + + /** + * A backend engineer's one-page CV. + * + * @return the sample document + */ + public static CvDocument sample() { + return CvDocument.builder() + .identity(CvIdentity.builder() + .name("Tomas", "Halvorsen") + .jobTitle("Platform Engineer") + .contact(new Contact("+47 22 12 34 56", + "tomas.h@example.com", + "Oslo, Norway")) + .link(new Link("linkedin.com/in/thalvorsen", + "https://www.linkedin.com/in/thalvorsen")) + .link(new Link("github.com/thalvorsen", + "https://github.com/thalvorsen")) + .build()) + .section(new ParagraphSection("Summary", + "Platform engineer with seven years spent making other teams'" + + " deployments boring: build pipelines, the service" + + " templates behind them, and the observability that says" + + " whether a release went well. Writes the runbook before" + + " the incident.")) + .section(EntriesSection.builder("Experience") + .entry(CvEntry.builder("Senior Platform Engineer") + .subtitle("Nordvik Systems") + .place("Oslo, NO") + .date("Feb 2022 - Present") + .body(List.of( + "Rebuilt the deployment pipeline around a golden path," + + " cutting median lead time from two days to" + + " forty minutes.", + "Owns the Kubernetes platform four product teams ship" + + " onto, and the on-call rota that follows it.", + "Introduced service-level objectives the teams actually" + + " read, and retired the dashboards nobody did.")) + .build()) + .entry(CvEntry.builder("Platform Engineer") + .subtitle("Bergen Data") + .place("Bergen, NO") + .date("Aug 2019 - Jan 2022") + .body(List.of( + "Moved forty services off hand-rolled scripts onto a" + + " shared Terraform module set.", + "Built the log and metric pipeline the incident process" + + " still runs on.", + "Ran the migration to a single identity provider across" + + " every internal tool.")) + .build()) + .entry(CvEntry.builder("Backend Engineer") + .subtitle("Fjordline Software") + .place("Oslo, NO") + .date("Sep 2017 - Jul 2019") + .body(List.of( + "Delivered booking and payment services for a ferry" + + " operator's public API.", + "Replaced a nightly reconciliation batch with a" + + " streaming job.")) + .build()) + .build()) + .section(EntriesSection.builder("Projects") + .entry(CvEntry.builder("Ledgerkit") + .subtitle("Java, PostgreSQL, Testcontainers") + .body("Open-source double-entry ledger for JVM services, with a" + + " property-based suite over the posting rules.") + .icon("chart") + .link("https://github.com/thalvorsen/ledgerkit") + .build()) + .entry(CvEntry.builder("Runbook Digest") + .subtitle("Kotlin, Kafka, OpenSearch") + .body("Reads alert history and drafts the retrospective agenda" + + " before the meeting starts.") + .icon("rocket") + .link("https://github.com/thalvorsen/runbook-digest") + .build()) + .build()) + .section(EntriesSection.builder("Education") + .entry(CvEntry.builder("M.Sc. in Computer Science") + .subtitle("University of Oslo") + .date("2015 - 2017") + .place("Oslo, NO") + .build()) + .entry(CvEntry.builder("B.Sc. in Informatics") + .subtitle("NTNU") + .date("2012 - 2015") + .place("Trondheim, NO") + .build()) + .build()) + .section(SkillsSection.builder("Skills") + .leveledGroup("Languages", List.of( + CvSkill.of("Java", 0.86), + CvSkill.of("Kotlin", 0.72), + CvSkill.of("Go", 0.48), + CvSkill.of("SQL", 0.8))) + .leveledGroup("Platform", List.of( + CvSkill.of("Kubernetes", 0.84), + CvSkill.of("Terraform", 0.78), + CvSkill.of("GitHub Actions", 0.75), + CvSkill.of("Argo CD", 0.6))) + .leveledGroup("Data", List.of( + CvSkill.of("PostgreSQL", 0.82), + CvSkill.of("Kafka", 0.7), + CvSkill.of("OpenSearch", 0.55))) + .leveledGroup("Practices", List.of( + CvSkill.of("Observability", 0.8), + CvSkill.of("Incident Response", 0.76), + CvSkill.of("Threat Modelling", 0.55))) + .build()) + .section(new ParagraphSection("Soft Skills", String.join(NEWLINE, + "Mentoring | Facilitation", + "Writing | Pragmatism", + "Patience | Curiosity"))) + .section(RowsSection.builder("Certifications", RowStyle.PLAIN) + .row("Certified Kubernetes", "Administrator (CNCF)") + .row("Terraform Associate", "(HashiCorp)") + .row("AWS Solutions Architect", "Associate (AWS)") + .row("Professional Cloud", "Architect (Google)") + .row("Site Reliability", "Foundations (Linux Foundation)") + .build()) + .section(EntriesSection.builder("Achievements") + .entry(CvEntry.builder("Lead Time Cut by 96%") + .body("Took median deployment lead time from two days to forty" + + " minutes across four teams.") + .icon("chart") + .build()) + .entry(CvEntry.builder("Zero-Downtime Migration") + .body("Moved the booking platform onto Kubernetes over six" + + " weeks without a customer-visible outage.") + .icon("rocket") + .build()) + .entry(CvEntry.builder("Engineer of the Year 2024") + .body("Recognised for the platform work and for the mentoring" + + " that came with it.") + .icon("trophy") + .build()) + .build()) + .build(); + } +} diff --git a/examples/src/main/java/com/demcha/examples/support/ShowcaseMetadata.java b/examples/src/main/java/com/demcha/examples/support/ShowcaseMetadata.java index 3c5f42cb4..fe0ea9c0a 100644 --- a/examples/src/main/java/com/demcha/examples/support/ShowcaseMetadata.java +++ b/examples/src/main/java/com/demcha/examples/support/ShowcaseMetadata.java @@ -60,6 +60,16 @@ record Entry(String title, String description, List tags, String codeUrl cv("cv-minimal-underlined-v2", "CvMinimalUnderlinedExample", "Minimal Underlined", "Single-column layout with underlined section titles and tight whitespace — minimalist reference shape.", "minimal"); cv("cv-mint-editorial-v2", "CvMintEditorialExample", "Mint Editorial", "Magazine-style editorial CV with mint accent palette and two-column body.", "editorial", "mint"); cv("cv-mint-editorial-v2-custom", "CvMintEditorialCustomExample", "Mint Editorial (custom band)", "The same preset with one colour changed through its Options — a kraft-paper masthead band, everything else left at the preset's defaults.", "editorial", "mint"); + cv("cv-professional-sidebar-v2", "ProfessionalSidebarExample", "Professional Sidebar", "Navy monogram plate over a pale sidebar of contact marks, skill meters, an education rail and language ratings, beside a white column of profile, roles and projects.", "sidebar", "navy"); + cv("cv-navy-sidebar-v2", "NavySidebarExample", "Navy Sidebar", "Navy plate with a ringed portrait, contact marks, degrees, skills and languages, beside a white column of summary, badged sections and roles strung on a timeline rail.", "sidebar", "navy", "portrait"); + cv("cv-serif-headline-v2", "SerifHeadlineExample", "Serif Headline", "Volkhov masthead over a two-column body: roles on a timeline rail and marked project cards beside degrees and skill meters, closing with full-width certification and achievement bands.", "serif", "two-column"); + cv("cv-charcoal-gold-v2", "CharcoalGoldExample", "Charcoal Gold", "Charcoal sidebar with a ringed photograph, rated skills and languages, beside a paper column with a two-tone name, a dated experience rail and paired credential columns.", "sidebar", "photo", "gold"); + cv("cv-terracotta-rail-v2", "TerracottaRailExample", "Terracotta Rail", "Serif monogram over a terracotta rule, contact channels behind their marks and two bulleted lists, beside a letter-spaced masthead, roles on a ringed rail, a projects grid and the degrees.", "sidebar", "monogram", "terracotta"); + cv("cv-teal-pulse-v2", "TealPulseExample", "Teal Pulse", "Clinical sheet in five bands: a heart crossed by a pulse beside the name, a contact strip on rules, competencies beside the summary and roles, and a three-column closing band over a tracked tagline.", "clinical", "badges", "teal"); + cv("cv-slate-orange-v2", "SlateOrangeExample", "Slate Orange", "Full-bleed slate masthead with an orange monogram tile, over a sidebar of marked competencies, trophied achievements and rated languages beside a profile, a dated experience rail and a credentials footer.", "sidebar", "masthead", "orange"); + cv("cv-violet-grid-v2", "VioletGridExample", "Violet Grid", "Single-column sheet in bands: a two-tone name beside the contact list, a six-up grid of marked skills on dotted rules, a tools strip, a dated timeline, tinted project tiles and a closing quotation.", "single-column", "grid", "violet"); + cv("cv-orange-ops-v2", "OrangeOpsExample", "Orange Ops", "Operations sheet with a two-tone name over a slanted role bar and accent slashes, a contact strip on hairlines, and a sidebar of skills, achievement discs and credentials beside a profile, dated roles and a four-metric strip.", "sidebar", "metrics", "orange"); + cv("cv-midnight-navy-v2", "MidnightNavyExample", "Midnight Navy", "Full-height navy plate carrying an outlined monogram, a tracked role line, metered skills and dotted languages, beside a paper column with the summary, roles on a rail, achievement discs and divided certification columns.", "sidebar", "monogram", "navy"); // ===== Templates / Cover Letter (v2 layered, paired 1:1 with CV) ===== // Registered directly: letter() points at the layered preset examples under @@ -87,12 +97,22 @@ record Entry(String title, String description, List tags, String codeUrl // ===== Templates / Invoice ===== invoice("invoice-cinematic", "InvoiceCinematicFileExample", "Cinematic Invoice", "Layered ModernInvoice preset with theme-driven layout, advanced tables, and totals.", "invoice", "cinematic"); + invoice("invoice-consulting-v2", "v2/ConsultingInvoiceV2Example", "Consulting Invoice", "The ConsultingInvoice preset on the structured invoice model — brand lockup with the caller's logo, labelled masthead metadata, priced service lines with service periods, a totals stack and bank payment fields.", "invoice"); + invoice("invoice-luma-studio-v2", "v2/LumaStudioInvoiceV2Example", "Luma Studio Invoice", "The LumaStudioInvoice preset on the structured invoice model — a cream sidebar carrying the brand lockup and its ornament, a billed-to / shipped-to pair, priced service lines with a VAT column, and the notes and bank details above a sign-off band.", "invoice"); + invoice("invoice-payments-v2", "v2/PaymentsInvoiceV2Example", "Payments Invoice", "The PaymentsInvoice preset on the structured invoice model — a diagonal band crossing the masthead, a half-split issuer and metadata header, two addressed parties, marked service lines that repeat their header across pages, and a settlement row pairing bank details against the totals.", "invoice"); + invoice("invoice-workspace-v2", "v2/WorkspaceInvoiceV2Example", "Workspace Invoice", "The WorkspaceInvoice preset on the structured invoice model — a brand masthead over an accent bar, a half-split issuer and metadata header, two addressed parties on discs, service lines whose marks sit on coloured tiles, and a settlement row above a closing band.", "invoice"); invoice("invoice-modern-v2", "v2/ModernInvoiceV2Example", "Modern Invoice", "The ModernInvoice preset composed straight from an InvoiceDocumentSpec — line items, totals and payment block driven by the BrandTheme rather than per-document styling.", "invoice"); + invoice("invoice-classic-v2", "v2/ClassicInvoiceV2Example", "Classic Invoice", "The ClassicInvoice preset — letterhead header band, TOTAL DUE hero strip, BILL TO / FROM columns, and a dedicated Summary table after the line items.", "invoice"); // ===== Templates / Proposal ===== proposal("proposal-cinematic", "ProposalCinematicFileExample", "Cinematic Proposal", "Layered ModernProposal layout with cover panel, hero spread, and rich typography.", "proposal", "cinematic"); proposal("project-proposal-cinematic", "CinematicProposalFileExample", "Project Proposal (cinematic)", "End-to-end project proposal with mountain hero, scope panels, and pricing summary.", "proposal", "cinematic"); proposal("proposal-modern-v2", "v2/ModernProposalV2Example", "Modern Proposal", "The ModernProposal preset composed straight from its document spec — cover, scope sections and pricing table themed through BrandTheme.", "proposal"); + proposal("proposal-editorial-v2", "v2/EditorialProposalV2Example", "Editorial Proposal", "The EditorialProposal preset — the same structured proposal document as Northline, set in a serif display face with an orange accent, a drawn brand mark and headings over accent rules.", "proposal"); + proposal("proposal-northline-v2", "v2/NorthlineProposalV2Example", "Northline Proposal", "The NorthlineProposal preset on the structured proposal model — brand header, stacked title, glance card, goal cells, numbered scope, phase grid, investment table and signing card across two pages.", "proposal"); + + // ===== Templates / Receipt ===== + receipt("receipt-modern", "ModernReceiptExample", "Modern Receipt", "A settled transfer confirmation on the layered receipt family — hero amount with a status chip, payer/beneficiary panel, dotted-leader detail rows, a status timeline, and a footer pinned to the page bottom with a verification QR code.", "receipt", "qr"); // ===== Templates / Schedule ===== schedule("weekly-schedule", "Weekly Schedule", "Multi-day weekly schedule with shift assignments, category fills, and repeated header.", "schedule", "table"); @@ -194,6 +214,7 @@ static String groupLabel(String category, String group) { case "templates/invoice" -> "Invoice"; case "templates/proposal" -> "Proposal"; case "templates/schedule" -> "Schedule"; + case "templates/receipt" -> "Payment Receipt"; case "features/lists" -> "Lists & Bullets"; case "features/tables" -> "Tables"; case "features/canvas" -> "Canvas / Free Placement"; @@ -256,6 +277,11 @@ private static void proposal(String id, String exampleClass, String title, Strin EX_BASE + "/templates/proposal/" + exampleClass + ".java")); } + private static void receipt(String id, String exampleClass, String title, String desc, String... tags) { + ENTRIES.put(id, entry(title, desc, withCategory("receipt", tags), + EX_BASE + "/templates/receipt/" + exampleClass + ".java")); + } + private static void schedule(String id, String title, String desc, String... tags) { ENTRIES.put(id, entry(title, desc, withCategory("schedule", tags), EX_BASE + "/templates/schedule/WeeklyScheduleFileExample.java")); diff --git a/examples/src/main/java/com/demcha/examples/support/SlateOrangeSampleData.java b/examples/src/main/java/com/demcha/examples/support/SlateOrangeSampleData.java new file mode 100644 index 000000000..00220b388 --- /dev/null +++ b/examples/src/main/java/com/demcha/examples/support/SlateOrangeSampleData.java @@ -0,0 +1,207 @@ +package com.demcha.examples.support; + +import com.demcha.compose.document.templates.core.identity.Contact; +import com.demcha.compose.document.templates.core.identity.Link; +import com.demcha.compose.document.templates.cv.data.CvDocument; +import com.demcha.compose.document.templates.cv.data.CvEntry; +import com.demcha.compose.document.templates.cv.data.CvIdentity; +import com.demcha.compose.document.templates.cv.data.CvName; +import com.demcha.compose.document.templates.cv.data.CvSkill; +import com.demcha.compose.document.templates.cv.data.EntriesSection; +import com.demcha.compose.document.templates.cv.data.ParagraphSection; +import com.demcha.compose.document.templates.cv.data.SkillGroup; +import com.demcha.compose.document.templates.cv.data.SkillsSection; +import com.demcha.compose.document.templates.cv.data.Slot; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +/** + * Shared sample data for the Slate Orange CV example. + * + *

The sample is the preset's reference content: a one-page CV with four + * contact lines including a link, ten marked competencies, three + * achievements, three rated languages, three closing facts, a profile, four + * roles, a degree and four certifications.

+ * + *

Kept in lockstep with the qa module's {@code SlateOrangeFixtures} — + * the two modules cannot share a source file, so a content change here + * belongs there too.

+ */ +public final class SlateOrangeSampleData { + + /** The break a body stacks its lines on. */ + private static final String NEWLINE = String.valueOf((char) 10); + + private SlateOrangeSampleData() { + } + + /** The single-page reference CV. */ + public static CvDocument sample() { + List placements = new ArrayList<>(); + placements.add(new CvDocument.Placement(Slot.MAIN, specialisms())); + placements.add(new CvDocument.Placement(Slot.SIDEBAR, competencies())); + placements.add(new CvDocument.Placement(Slot.SIDEBAR, achievements())); + placements.add(new CvDocument.Placement(Slot.SIDEBAR, languages())); + placements.add(new CvDocument.Placement(Slot.SIDEBAR, facts())); + placements.add(new CvDocument.Placement(Slot.MAIN, profile())); + placements.add(new CvDocument.Placement(Slot.MAIN, experience())); + placements.add(new CvDocument.Placement(Slot.FOOTER, education())); + placements.add(new CvDocument.Placement(Slot.FOOTER, certifications())); + return new CvDocument(identity(), placements); + } + + private static CvIdentity identity() { + return new CvIdentity( + CvName.of("DANIEL", "HARPER"), + "VERSATILE PROFESSIONAL", + new Contact("(555) 123-4567", + "daniel.harper@email.com", + "Austin, Texas \u2022 Open to Relocate"), + List.of(new Link("LinkedIn", "https://www.linkedin.com/in/danielharper")), + Optional.empty()); + } + + /** The strip under the role. Only the body is drawn; the title names the berth. */ + private static ParagraphSection specialisms() { + return new ParagraphSection("SPECIALISMS", String.join(NEWLINE, + "OPERATIONS", "CUSTOMER SUCCESS", "COORDINATION")); + } + + /** The competencies, each behind the mark its entry names. */ + private static EntriesSection competencies() { + return new EntriesSection("CORE COMPETENCIES", List.of( + CvEntry.builder("Customer Service Excellence").icon("customer-service").build(), + CvEntry.builder("Operations & Process Support").icon("operations").build(), + CvEntry.builder("Project & Calendar Management").icon("calendar").build(), + CvEntry.builder("Data Management & Reporting").icon("reporting").build(), + CvEntry.builder("Communication & Stakeholders").icon("communication").build(), + CvEntry.builder("Problem Solving & Adaptability").icon("problem-solving").build(), + CvEntry.builder("Sales & Relationship Building").icon("sales").build(), + CvEntry.builder("Microsoft Office & Google Suite").icon("office-suite").build(), + CvEntry.builder("CRM Systems (Salesforce, HubSpot)").icon("crm").build(), + CvEntry.builder("Time Management & Prioritization").icon("time-management").build())); + } + + /** The achievements: a bold title over its body, beside one shared trophy. */ + private static EntriesSection achievements() { + return new EntriesSection("SELECTED ACHIEVEMENTS", List.of( + CvEntry.builder("Process Improvement") + .icon("achievement") + .body("Designed a new tracking system that reduced reporting time " + + "by 30% and improved accuracy.") + .build(), + CvEntry.builder("Customer Satisfaction") + .icon("achievement") + .body("Maintained a 96% customer satisfaction rating through " + + "responsive support and issue resolution.") + .build(), + CvEntry.builder("Revenue Contribution") + .icon("achievement") + .body("Consistently exceeded monthly sales goals by 15\u201320% " + + "through relationship building and consultative service.") + .build())); + } + + /** The languages, each carrying both the rating and the word for it. */ + private static SkillsSection languages() { + return new SkillsSection("LANGUAGES", List.of(new SkillGroup("LANGUAGES", List.of( + CvSkill.of("English", 1.0, "Native"), + CvSkill.of("Spanish", 0.8, "Professional Working"), + CvSkill.of("French", 0.4, "Basic"))))); + } + + /** The closing facts, on the same mark column at a wider pitch. */ + private static EntriesSection facts() { + return new EntriesSection("ADDITIONAL INFORMATION", List.of( + CvEntry.builder("Availability: Full-time").icon("availability").build(), + CvEntry.builder("Willing to Relocate: Yes").icon("relocation").build(), + CvEntry.builder("Remote Work: Open to Hybrid/Remote").icon("remote-work").build())); + } + + private static ParagraphSection profile() { + return new ParagraphSection("PROFESSIONAL PROFILE", + "Adaptable and resourceful professional with 6+ years of " + + "experience across operations, customer service, sales, and " + + "project support. Known for strong communication, " + + "organizational skills, and the ability to thrive in " + + "fast-paced environments. Adept at managing priorities, " + + "building relationships, and delivering results that drive " + + "efficiency, customer satisfaction, and business growth."); + } + + /** The roles held, each a headline over its bullets. */ + private static EntriesSection experience() { + return new EntriesSection("PROFESSIONAL EXPERIENCE", List.of( + CvEntry.builder("Operations Coordinator") + .subtitle("BrightStart Logistics, Austin, TX") + .date("Jan 2022 \u2013 Present") + .body(String.join(NEWLINE, + "Coordinate daily operations and scheduling for a team of " + + "12 across multiple sites.", + "Develop and maintain dashboards and reports to track KPIs " + + "and operational metrics.", + "Streamline communication between departments, reducing " + + "response time by 25%.", + "Manage vendor relationships and assist with contract and " + + "invoice reconciliation.")) + .build(), + CvEntry.builder("Customer Support Specialist") + .subtitle("TechWave Solutions, Remote") + .date("Jun 2020 \u2013 Dec 2021") + .body(String.join(NEWLINE, + "Provided technical support to customers via phone, email, " + + "and live chat.", + "Resolved an average of 40+ inquiries daily with a " + + "first-contact resolution rate of 92%.", + "Created knowledge base articles and improved internal " + + "documentation.", + "Collaborated with product and engineering teams to " + + "escalate and resolve issues.")) + .build(), + CvEntry.builder("Sales Associate") + .subtitle("Urban Outfitters, Austin, TX") + .date("Mar 2019 \u2013 May 2020") + .body(String.join(NEWLINE, + "Consistently exceeded sales targets by 15\u201320% through " + + "product knowledge and personalized customer service.", + "Built and maintained strong customer relationships, " + + "driving repeat business.", + "Merchandised floor displays and contributed to visual " + + "standards and promotions.", + "Recognized as \u201cTop Performer\u201d for Q4 2019.")) + .build(), + CvEntry.builder("Administrative Assistant / Project Assistant") + .subtitle("Greenfield Nonprofit Initiative, Austin, TX") + .date("May 2018 \u2013 Feb 2019") + .body(String.join(NEWLINE, + "Supported project managers with scheduling, documentation, " + + "and meeting logistics.", + "Managed calendars, travel arrangements, and expense " + + "reports.", + "Maintained donor records and prepared reports for " + + "leadership and stakeholders.", + "Assisted with event planning and community outreach " + + "initiatives.")) + .build())); + } + + /** The degree; the design's third line is where and when, as one line. */ + private static EntriesSection education() { + return new EntriesSection("EDUCATION", List.of( + CvEntry.builder("Bachelor of Arts in Communications") + .subtitle("University of Texas at Austin") + .place("Austin, TX \u2022 Graduated May 2018") + .build())); + } + + /** The certifications, one bulleted line each. */ + private static EntriesSection certifications() { + return new EntriesSection("CERTIFICATIONS & TRAINING", List.of( + CvEntry.builder("Google Project Management Certificate (Coursera) \u2013 2023").build(), + CvEntry.builder("HubSpot Customer Service Certification \u2013 2022").build(), + CvEntry.builder("Microsoft Excel: Advanced Formulas & Functions \u2013 2021").build(), + CvEntry.builder("ServSafe Food Handler Certification \u2013 2019").build())); + } +} diff --git a/examples/src/main/java/com/demcha/examples/support/StructuredProposalSampleData.java b/examples/src/main/java/com/demcha/examples/support/StructuredProposalSampleData.java new file mode 100644 index 000000000..0c315f917 --- /dev/null +++ b/examples/src/main/java/com/demcha/examples/support/StructuredProposalSampleData.java @@ -0,0 +1,147 @@ +package com.demcha.examples.support; + +import com.demcha.compose.document.templates.data.proposal.ProposalAcceptance; +import com.demcha.compose.document.templates.data.proposal.ProposalBrand; +import com.demcha.compose.document.templates.data.proposal.ProposalDeliverables; +import com.demcha.compose.document.templates.data.proposal.ProposalGlance; +import com.demcha.compose.document.templates.data.proposal.ProposalGoals; +import com.demcha.compose.document.templates.data.proposal.ProposalInvestment; +import com.demcha.compose.document.templates.data.proposal.ProposalMetaLine; +import com.demcha.compose.document.templates.data.proposal.ProposalPhaseGrid; +import com.demcha.compose.document.templates.data.proposal.ProposalScope; +import com.demcha.compose.document.templates.data.proposal.ProposalSummaryBlock; +import com.demcha.compose.document.templates.data.proposal.ProposalTermsBlock; +import com.demcha.compose.document.templates.data.proposal.ProposalTitleLines; +import com.demcha.compose.document.templates.data.proposal.StructuredProposalData; +import com.demcha.compose.document.templates.data.proposal.StructuredProposalDocumentSpec; + +import java.util.List; + +/** + * Shared sample data for the structured proposal examples. + * + *

Kept in lockstep with the qa module's {@code NorthlineProposalFixtures} + * — the two modules cannot share a source file, so a content change here + * belongs there too.

+ * + *

The sample is the preset's reference content: a two-page + * brand-refresh proposal exercising every band — all four glance facts + * (one with the optional note), four goal cells, five scope rows, both + * deliverable columns, the four-phase grid, an investment table with + * ordinary, subtotal and optional rows plus the total band, four terms, + * and the three-field signing card.

+ */ +public final class StructuredProposalSampleData { + + private StructuredProposalSampleData() { + } + + public static StructuredProposalDocumentSpec sample() { + return StructuredProposalDocumentSpec.from(StructuredProposalData.builder() + .brand(new ProposalBrand("N", "NORTHLINE", "STUDIO", "PROPOSAL", + "northlinestudio.com", "NORTHLINE STUDIO")) + .title(new ProposalTitleLines("Proposal –", "Brand Refresh &", + "Website Redesign")) + .meta(new ProposalMetaLine("PREPARED FOR ASTERA HEALTH CO.", + "PREPARED BY NORTHLINE STUDIO", "25 AUGUST 2026")) + .executiveSummary(new ProposalSummaryBlock("EXECUTIVE SUMMARY", + "badge-summary", List.of( + "Thank you for the opportunity to partner with Astera Health Co. " + + "on your next chapter. This proposal outlines our approach to " + + "refreshing your brand identity and redesigning your website to " + + "better reflect your mission, elevate credibility, and deliver a " + + "seamless experience for the people you serve.", + "Our process blends strategic thinking with thoughtful design to " + + "create a cohesive brand system and a modern, conversion-focused " + + "website. The outcome will be a clear, confident presence that " + + "strengthens trust, improves engagement, and supports your " + + "growth goals."))) + .glance(new ProposalGlance("PROJECT AT A GLANCE", List.of( + new ProposalGlance.Fact("fact-duration", "PROJECT DURATION", + "6 weeks", null), + new ProposalGlance.Fact("fact-start", "START WINDOW", + "September 2026", null), + new ProposalGlance.Fact("fact-contact", "PRIMARY CONTACT", + "Project Lead", null), + new ProposalGlance.Fact("fact-validity", "PROPOSAL VALIDITY", + "14 days", "(until 8 September 2026)")))) + .goals(new ProposalGoals("PROJECT GOALS", "badge-goals", List.of( + new ProposalGoals.Goal("goal-brand", + "Strengthen brand credibility and visual consistency."), + new ProposalGoals.Goal("goal-usability", + "Improve website usability and drive meaningful action."), + new ProposalGoals.Goal("goal-message", + "Communicate your value clearly across all touchpoints."), + new ProposalGoals.Goal("goal-growth", + "Build a scalable digital foundation for future growth.")))) + .scope(new ProposalScope("SCOPE OF WORK", "badge-scope", List.of( + new ProposalScope.Item("01", "Discovery & Research", + "Stakeholder interviews, brand audit, competitor review, and " + + "audience insights to inform strategy."), + new ProposalScope.Item("02", "Visual Identity Refinement", + "Refine logo system, color palette, typography, and visual " + + "guidelines for a cohesive, modern identity."), + new ProposalScope.Item("03", "Website UX/UI Design", + "Design a responsive, accessibility-minded website with " + + "intuitive navigation and clear conversion pathways."), + new ProposalScope.Item("04", "Content Structure & Messaging", + "Define site architecture, key messaging, and content " + + "hierarchy to communicate value and build trust."), + new ProposalScope.Item("05", "Launch Support", + "Developer handoff, QA support, training, and go-live " + + "assistance to ensure a smooth launch.")))) + .deliverables(new ProposalDeliverables("DELIVERABLES", "badge-deliverables", + List.of("Brand strategy summary", + "Refined logo suite & brand guidelines", + "Color, typography & design system", + "Website UX wireframes", + "High-fidelity website designs"), + List.of("Responsive design for desktop, tablet & mobile", + "Content strategy & page templates", + "Developer-ready assets & specifications", + "Launch checklist & training session"))) + .timeline(new ProposalPhaseGrid("TIMELINE", "badge-timeline", + List.of("PHASE", "FOCUS", "DURATION", "OUTPUT"), List.of( + new ProposalPhaseGrid.Phase("01", "Discover", + "Research, audit, insights, and strategy", "1 week", + "Discovery report & creative direction"), + new ProposalPhaseGrid.Phase("02", "Design", + "Brand refinement and website UX/UI design", "2 weeks", + "Design system & high-fidelity designs"), + new ProposalPhaseGrid.Phase("03", "Develop Prep", + "Content structure, developer handoff & asset preparation", + "1 week", "Specs, assets & content framework"), + new ProposalPhaseGrid.Phase("04", "Launch Support", + "QA, training & go-live assistance", "2 weeks", + "QA sign-off & successful launch")))) + .investment(new ProposalInvestment("INVESTMENT", "badge-investment", + "ITEM", "AMOUNT (GBP)", List.of( + new ProposalInvestment.Row("Discovery & Strategy", "£2,500", + ProposalInvestment.Role.NONE), + new ProposalInvestment.Row("Brand Identity Refinement", "£4,500", + ProposalInvestment.Role.NONE), + new ProposalInvestment.Row("Website UX/UI Design", "£7,000", + ProposalInvestment.Role.NONE), + new ProposalInvestment.Row("Content Structure & Messaging", "£2,000", + ProposalInvestment.Role.NONE), + new ProposalInvestment.Row("Launch Support", "£1,500", + ProposalInvestment.Role.NONE), + new ProposalInvestment.Row("Subtotal", "£17,500", + ProposalInvestment.Role.SUBTOTAL), + new ProposalInvestment.Row("Optional: 3 Months Post-Launch Support", + "£1,750", ProposalInvestment.Role.OPTIONAL)), + "TOTAL INVESTMENT", "£19,250")) + .terms(new ProposalTermsBlock("TERMS", "badge-terms", List.of( + "50% deposit is required to secure the project and schedule the " + + "kick-off.", + "Includes two rounds of revisions per major deliverable.", + "Final files and handoff delivered upon final payment.", + "Ownership of all final deliverables transfers to Astera Health Co. " + + "after full payment is received."))) + .acceptance(new ProposalAcceptance("ACCEPTANCE", "badge-acceptance", + "By signing below, you agree to the scope, timeline, investment, " + + "and terms outlined in this proposal.", + List.of("Accepted by:", "Signature:", "Date:"))) + .build()); + } +} diff --git a/examples/src/main/java/com/demcha/examples/support/TealPulseSampleData.java b/examples/src/main/java/com/demcha/examples/support/TealPulseSampleData.java new file mode 100644 index 000000000..1022015bd --- /dev/null +++ b/examples/src/main/java/com/demcha/examples/support/TealPulseSampleData.java @@ -0,0 +1,167 @@ +package com.demcha.examples.support; + +import com.demcha.compose.document.templates.core.identity.Contact; +import com.demcha.compose.document.templates.core.identity.Link; +import com.demcha.compose.document.templates.cv.data.CvDocument; +import com.demcha.compose.document.templates.cv.data.CvEntry; +import com.demcha.compose.document.templates.cv.data.CvIdentity; +import com.demcha.compose.document.templates.cv.data.CvName; +import com.demcha.compose.document.templates.cv.data.CvSkill; +import com.demcha.compose.document.templates.cv.data.EntriesSection; +import com.demcha.compose.document.templates.cv.data.ParagraphSection; +import com.demcha.compose.document.templates.cv.data.SkillGroup; +import com.demcha.compose.document.templates.cv.data.SkillsSection; +import com.demcha.compose.document.templates.cv.data.Slot; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.OptionalDouble; + +/** + * Shared sample data for the Teal Pulse CV example. + * + *

The sample is the preset's reference content: a nurse's one-page CV with + * four contact channels including a link, twelve competencies, a summary, + * three roles with their highlights, a degree, four certifications, three + * facts and the closing tagline.

+ * + *

Kept in lockstep with the qa module's {@code TealPulseFixtures} — the + * two modules cannot share a source file, so a content change here belongs + * there too.

+ */ +public final class TealPulseSampleData { + + /** The break an entry stacks its highlights on. */ + private static final String NEWLINE = String.valueOf((char) 10); + + /** The dash this sheet writes a date range with. */ + private static final String EN_DASH = String.valueOf((char) 0x2013); + + private TealPulseSampleData() { + } + + /** The single-page reference CV. */ + public static CvDocument sample() { + List placements = new ArrayList<>(); + placements.add(new CvDocument.Placement(Slot.SIDEBAR, competencies())); + placements.add(new CvDocument.Placement(Slot.MAIN, summary())); + placements.add(new CvDocument.Placement(Slot.MAIN, experience())); + placements.add(new CvDocument.Placement(Slot.FOOTER, education())); + placements.add(new CvDocument.Placement(Slot.FOOTER, certifications())); + placements.add(new CvDocument.Placement(Slot.FOOTER, facts())); + placements.add(new CvDocument.Placement(Slot.FOOTER, tagline())); + return new CvDocument(identity(), placements); + } + + private static CvIdentity identity() { + return new CvIdentity( + CvName.of("ISABELLA", "MOORE"), + "REGISTERED NURSE", + new Contact("+44 7700 900123", "isabella.moore@email.com", + "Manchester, United Kingdom"), + List.of(new Link("LinkedIn", "https://www.linkedin.com/in/isabellamoore-rn")), + Optional.empty()); + } + + private static SkillsSection competencies() { + List entries = new ArrayList<>(); + for (String name : new String[] { + "Patient Assessment", "Care Planning", "Medication Administration", + "Clinical Documentation", "Infection Prevention", "IV Therapy", + "Wound Care", "Patient Education", "Safeguarding", "Team Collaboration", + "Discharge Coordination", "Electronic Health Records"}) { + entries.add(new CvSkill(name, OptionalDouble.empty())); + } + return new SkillsSection("CORE COMPETENCIES", + List.of(new SkillGroup("CORE COMPETENCIES", entries))); + } + + private static ParagraphSection summary() { + return new ParagraphSection("PROFESSIONAL SUMMARY", + "Compassionate Registered Nurse with 7+ years of experience delivering " + + "high-quality patient care across acute medical, surgical, and " + + "community settings. Skilled in patient assessment, medication " + + "administration, multidisciplinary collaboration, discharge " + + "planning, and maintaining accurate clinical documentation. Known " + + "for calm decision-making, strong communication, and patient-centred " + + "care."); + } + + private static EntriesSection experience() { + return new EntriesSection("PROFESSIONAL EXPERIENCE", List.of( + CvEntry.builder("Senior Staff Nurse") + .subtitle("Manchester Royal Infirmary, Manchester, UK") + .date("2021" + EN_DASH + "Present") + .body(String.join(NEWLINE, + "Deliver direct nursing care for adult patients on a busy " + + "acute medical ward.", + "Coordinate care plans with doctors, therapists, and support " + + "staff to improve outcomes.", + "Supervise junior nurses and support student placements during " + + "clinical rotations.", + "Improved discharge coordination and patient communication " + + "across the ward.", + "Maintain precise documentation and ensure compliance with " + + "clinical standards.")) + .build(), + CvEntry.builder("Staff Nurse") + .subtitle("Salford General Hospital, Salford, UK") + .date("2018" + EN_DASH + "2021") + .body(String.join(NEWLINE, + "Provided nursing care across medical and surgical units.", + "Administered medications, monitored vital signs, and escalated " + + "deteriorating patients promptly.", + "Supported infection control procedures and safe discharge " + + "planning.", + "Built strong rapport with patients and families during " + + "treatment and recovery.")) + .build(), + CvEntry.builder("Community Nurse") + .subtitle("NorthCare Community Health, Greater Manchester, UK") + .date("2016" + EN_DASH + "2018") + .body(String.join(NEWLINE, + "Delivered home-based nursing care and patient education.", + "Managed wound care, medication support, and follow-up visits " + + "for vulnerable patients.", + "Worked closely with GPs, social services, and families to " + + "coordinate holistic care.")) + .build())); + } + + private static EntriesSection education() { + return new EntriesSection("EDUCATION", List.of( + CvEntry.builder("BSc (Hons) Adult Nursing") + .subtitle("University of Manchester") + .date("2013" + EN_DASH + "2016") + .build())); + } + + private static EntriesSection certifications() { + List entries = new ArrayList<>(); + for (String name : new String[] { + "NMC Registered Nurse", "Immediate Life Support (ILS)", + "Venepuncture and Cannulation Certification", "Safeguarding Adults Level 3"}) { + entries.add(CvEntry.builder(name).build()); + } + return new EntriesSection("CERTIFICATIONS", entries); + } + + private static EntriesSection facts() { + return new EntriesSection("ADDITIONAL INFORMATION", List.of( + CvEntry.builder("Languages:") + .body("English (Native), Polish (Conversational)") + .build(), + CvEntry.builder("Right to Work:").body("United Kingdom").build(), + CvEntry.builder("Availability:").body("1 month notice").build())); + } + + /** + * The closing line. Its title names the berth and is not drawn — only the + * body reaches the sheet. + */ + private static ParagraphSection tagline() { + return new ParagraphSection("TAGLINE", + "COMPASSIONATE CARE. CLINICAL EXCELLENCE. BETTER OUTCOMES."); + } +} diff --git a/examples/src/main/java/com/demcha/examples/support/TerracottaRailSampleData.java b/examples/src/main/java/com/demcha/examples/support/TerracottaRailSampleData.java new file mode 100644 index 000000000..b4f2d7885 --- /dev/null +++ b/examples/src/main/java/com/demcha/examples/support/TerracottaRailSampleData.java @@ -0,0 +1,207 @@ +package com.demcha.examples.support; + +import com.demcha.compose.document.templates.core.identity.Contact; +import com.demcha.compose.document.templates.core.identity.Link; +import com.demcha.compose.document.templates.cv.data.CvDocument; +import com.demcha.compose.document.templates.cv.data.CvEntry; +import com.demcha.compose.document.templates.cv.data.CvIdentity; +import com.demcha.compose.document.templates.cv.data.CvName; +import com.demcha.compose.document.templates.cv.data.CvSkill; +import com.demcha.compose.document.templates.cv.data.EntriesSection; +import com.demcha.compose.document.templates.cv.data.ParagraphSection; +import com.demcha.compose.document.templates.cv.data.SkillGroup; +import com.demcha.compose.document.templates.cv.data.SkillsSection; +import com.demcha.compose.document.templates.cv.data.Slot; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.OptionalDouble; + +/** + * Shared sample data for the Terracotta Rail CV example. + * + *

The sample is the preset's reference content: an architect's one-page + * CV with a monogram, four contact rows including a link, two bulleted + * lists, two credentials, three facts behind their marks, a three-paragraph + * summary, three roles on the rail, three marked projects and two + * degrees.

+ * + *

Kept in lockstep with the qa module's {@code TerracottaRailFixtures} — + * the two modules cannot share a source file, so a content change here + * belongs there too.

+ */ +public final class TerracottaRailSampleData { + + /** The break a fact stacks its values on. */ + private static final String NEWLINE = String.valueOf((char) 10); + + /** The dash this sheet writes a date range with. */ + private static final String EN_DASH = String.valueOf((char) 0x2013); + + private TerracottaRailSampleData() { + } + + /** The single-page reference CV. */ + public static CvDocument sample() { + List placements = new ArrayList<>(); + placements.add(new CvDocument.Placement(Slot.SIDEBAR, competencies())); + placements.add(new CvDocument.Placement(Slot.SIDEBAR, software())); + placements.add(new CvDocument.Placement(Slot.SIDEBAR, certifications())); + placements.add(new CvDocument.Placement(Slot.SIDEBAR, facts())); + placements.add(new CvDocument.Placement(Slot.MAIN, summary())); + placements.add(new CvDocument.Placement(Slot.MAIN, experience())); + placements.add(new CvDocument.Placement(Slot.MAIN, projects())); + placements.add(new CvDocument.Placement(Slot.MAIN, education())); + return new CvDocument(identity(), placements); + } + + private static CvIdentity identity() { + return new CvIdentity( + CvName.of("OLIVER", "BENNETT"), + "SENIOR ARCHITECT", + new Contact("+44 7700 900123", "oliver.bennett@email.com", + "Bristol, United Kingdom"), + List.of(new Link("LinkedIn", + "https://linkedin.com/in/oliverbennett-architect")), + Optional.empty()); + } + + private static SkillsSection competencies() { + return plainSkills("CORE COMPETENCIES", + "Architectural Design", "Concept Development", "Planning Applications", + "Technical Drawings", "Revit", "AutoCAD", "BIM Coordination", + "Design Presentations", "Project Delivery", "Stakeholder Management", + "Sustainable Design", "Building Regulations"); + } + + private static SkillsSection software() { + return plainSkills("SOFTWARE", + "Revit", "AutoCAD", "Rhino", "SketchUp", "Adobe InDesign", "Photoshop", + "Microsoft Office"); + } + + private static EntriesSection certifications() { + return new EntriesSection("CERTIFICATIONS", List.of( + CvEntry.builder("ARB Registered Architect").build(), + CvEntry.builder("RIBA Chartered Member").build())); + } + + private static EntriesSection facts() { + return new EntriesSection("ADDITIONAL INFORMATION", List.of( + CvEntry.builder("Languages:") + .icon("globe") + .body("English (Native)," + NEWLINE + "Spanish (Conversational)") + .build(), + CvEntry.builder("Right to Work:") + .icon("badge") + .body("United Kingdom") + .build(), + CvEntry.builder("Availability:") + .icon("clock") + .body("1 month notice") + .build())); + } + + private static ParagraphSection summary() { + return new ParagraphSection("PROFESSIONAL SUMMARY", String.join(NEWLINE, + "Senior Architect with over 8 years of experience delivering residential, " + + "mixed-use, and commercial projects across all RIBA work stages.", + "Proven expertise in design development, planning applications, and technical " + + "delivery with a strong focus on sustainability, quality, and user " + + "experience.", + "Collaborative communicator skilled in coordinating consultants, engaging " + + "stakeholders, and leading multidisciplinary teams to achieve " + + "successful outcomes.")); + } + + private static EntriesSection experience() { + return new EntriesSection("PROFESSIONAL EXPERIENCE", List.of( + CvEntry.builder("Senior Architect") + .subtitle("Northline Studio, Bristol, UK") + .date("2021" + EN_DASH + "Present") + .body(String.join(NEWLINE, + "Lead design packages from concept through to detailed design " + + "across mid-scale residential and mixed-use projects.", + "Coordinate structural, M&E, landscape and planning consultants " + + "to ensure integrated and buildable solutions.", + "Prepare and manage planning applications, design and access " + + "statements, and supporting documentation.", + "Present and communicate design proposals to clients and " + + "stakeholders, securing approvals and driving projects " + + "forward.")) + .build(), + CvEntry.builder("Architect") + .subtitle("Urban Form Partners, Bath, UK") + .date("2018" + EN_DASH + "2021") + .body(String.join(NEWLINE, + "Developed concept and technical designs for a range of " + + "residential, commercial, and education projects.", + "Prepared coordinated drawing sets, schedules, and " + + "specifications to support planning and construction.", + "Supported project delivery on site, resolving design queries " + + "and ensuring quality outcomes.", + "Coordinated with contractors and consultants to maintain " + + "programme, budget and design intent.")) + .build(), + CvEntry.builder("Part II Architectural Assistant") + .subtitle("Axis Design Workshop, Cardiff, UK") + .date("2016" + EN_DASH + "2018") + .body(String.join(NEWLINE, + "Supported senior architects across all RIBA stages on a " + + "variety of project types.", + "Produced CAD and Revit drawings, models, and visualisations to " + + "communicate design intent.", + "Undertook research, prepared presentation boards, and assisted " + + "with planning documentation.")) + .build())); + } + + private static EntriesSection projects() { + return new EntriesSection("SELECTED PROJECTS", List.of( + CvEntry.builder("Harbour Point") + .subtitle("Mixed-Use Regeneration") + .place("Bristol") + .icon("building") + .body("Mixed-use development delivering 142 homes, retail space and " + + "public realm improvements; secured planning consent and is " + + "now on site.") + .build(), + CvEntry.builder("The Assembly Hotel") + .subtitle("Boutique Hotel Refurbishment") + .place("Bath") + .icon("hotel") + .body("Sensitive refurbishment of listed building to create a " + + "36-bedroom boutique hotel; enhanced heritage features and " + + "guest experience.") + .build(), + CvEntry.builder("Elmwood Mews") + .subtitle("Residential Development") + .place("Clifton, Bristol") + .icon("house") + .body("Design of 28 high-quality homes within a sustainable masterplan; " + + "achieved high environmental standards and strong sales " + + "performance.") + .build())); + } + + private static EntriesSection education() { + return new EntriesSection("EDUCATION", List.of( + CvEntry.builder("MArch Architecture") + .subtitle("University of Sheffield") + .date("2014" + EN_DASH + "2016") + .build(), + CvEntry.builder("BA (Hons) Architecture") + .subtitle("University of the West of England") + .date("2011" + EN_DASH + "2014") + .build())); + } + + private static SkillsSection plainSkills(String title, String... names) { + List entries = new ArrayList<>(); + for (String name : names) { + entries.add(new CvSkill(name, OptionalDouble.empty())); + } + return new SkillsSection(title, List.of(new SkillGroup(title, entries))); + } +} diff --git a/examples/src/main/java/com/demcha/examples/support/VioletGridSampleData.java b/examples/src/main/java/com/demcha/examples/support/VioletGridSampleData.java new file mode 100644 index 000000000..3266599a1 --- /dev/null +++ b/examples/src/main/java/com/demcha/examples/support/VioletGridSampleData.java @@ -0,0 +1,224 @@ +package com.demcha.examples.support; + +import com.demcha.compose.document.templates.core.identity.Contact; +import com.demcha.compose.document.templates.core.identity.Link; +import com.demcha.compose.document.templates.cv.data.CvDocument; +import com.demcha.compose.document.templates.cv.data.CvEntry; +import com.demcha.compose.document.templates.cv.data.CvIdentity; +import com.demcha.compose.document.templates.cv.data.CvName; +import com.demcha.compose.document.templates.cv.data.CvSkill; +import com.demcha.compose.document.templates.cv.data.EntriesSection; +import com.demcha.compose.document.templates.cv.data.ParagraphSection; +import com.demcha.compose.document.templates.cv.data.SkillGroup; +import com.demcha.compose.document.templates.cv.data.SkillsSection; +import com.demcha.compose.document.templates.cv.data.Slot; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +/** + * Shared sample data for the Violet Grid CV example. + * + *

The sample is the preset's reference content: a one-page CV with five + * contact lines including two links, three opening lines, six marked skills, + * eleven tools, three roles, two projects, a degree, three rated languages + * and the closing quotation.

+ * + *

Kept in lockstep with the qa module's {@code VioletGridFixtures} — the + * two modules cannot share a source file, so a content change here belongs + * there too.

+ */ +public final class VioletGridSampleData { + + /** The break a body stacks its lines on. */ + private static final String NEWLINE = String.valueOf((char) 10); + + private VioletGridSampleData() { + } + + /** The single-page reference CV. */ + public static CvDocument sample() { + List placements = new ArrayList<>(); + placements.add(new CvDocument.Placement(Slot.MAIN, summary())); + placements.add(new CvDocument.Placement(Slot.MAIN, skills())); + placements.add(new CvDocument.Placement(Slot.MAIN, tools())); + placements.add(new CvDocument.Placement(Slot.MAIN, experience())); + placements.add(new CvDocument.Placement(Slot.MAIN, projects())); + placements.add(new CvDocument.Placement(Slot.FOOTER, education())); + placements.add(new CvDocument.Placement(Slot.FOOTER, languages())); + placements.add(new CvDocument.Placement(Slot.FOOTER, quote())); + return new CvDocument(identity(), placements); + } + + private static CvIdentity identity() { + return new CvIdentity( + CvName.of("SOFIA", "MARTINEZ"), + "UX / UI DESIGNER", + new Contact("+1 (415) 555-7842", + "sofia.martinez.design@gmail.com", + "San Francisco, CA, USA"), + List.of( + new Link("sofiamartinez.design", "https://sofiamartinez.design"), + new Link("LinkedIn", "https://www.linkedin.com/in/sofia-martinez-ux")), + Optional.empty()); + } + + /** The opening lines, set as written rather than wrapped. */ + private static ParagraphSection summary() { + return new ParagraphSection("SUMMARY", String.join(NEWLINE, + "User-centered designer with 5+ years of experience " + + "crafting intuitive digital experiences for web and " + + "mobile products.", + "I combine research, design thinking, and visual design " + + "to solve complex problems and deliver accessible, " + + "elegant solutions.", + "Passionate about collaborating with cross-functional " + + "teams to create products that delight users and drive " + + "business impact.")); + } + + /** The six-up grid: a mark, a label and a description each. */ + private static EntriesSection skills() { + return new EntriesSection("DESIGN SKILLS", List.of( + CvEntry.builder("UX RESEARCH") + .icon("ux-research") + .body("User interviews, surveys, personas, journey maps, " + + "competitive analysis") + .build(), + CvEntry.builder("INFORMATION ARCHITECTURE") + .icon("information-architecture") + .body("Sitemaps, user flows, card sorting, content strategy") + .build(), + CvEntry.builder("WIREFRAMING") + .icon("wireframing") + .body("Low to high-fidelity wireframes, layout design, " + + "interaction flows") + .build(), + CvEntry.builder("PROTOTYPING") + .icon("prototyping") + .body("Interactive prototypes, microinteractions, transitions") + .build(), + CvEntry.builder("USABILITY TESTING") + .icon("usability-testing") + .body("Test planning, usability tests, heuristic evaluation, " + + "insights & iteration") + .build(), + CvEntry.builder("DESIGN SYSTEMS") + .icon("design-systems") + .body("Component libraries, design tokens, pattern " + + "documentation") + .build())); + } + + /** The tools strip — names without levels. */ + private static SkillsSection tools() { + List entries = new ArrayList<>(); + for (String name : new String[] { + "Figma", "FigJam", "Adobe XD", "Sketch", "Miro", "Notion", "Jira", "Slack", "Maze", "Hotjar", "Airtable"}) { + entries.add(CvSkill.of(name)); + } + return new SkillsSection("TOOLS", + List.of(new SkillGroup("TOOLS", entries))); + } + + /** The roles held, each a headline over its bullets. */ + private static EntriesSection experience() { + return new EntriesSection("EXPERIENCE", List.of( + CvEntry.builder("Senior UX/UI Designer") + .subtitle("NovaFin (FinTech Startup)") + .place("San Francisco, CA") + .date("2022 - Present") + .body(String.join(NEWLINE, + "Led end-to-end design for a personal finance platform, " + + "from discovery to launch, improving task success rate by " + + "32%.", + "Conducted user research, built personas and journey " + + "maps, and translated insights into clear product " + + "opportunities.", + "Designed wireframes, prototypes, and high-fidelity " + + "interfaces for web and mobile using Figma.", + "Established and maintained a scalable design system, " + + "increasing design consistency and development speed.", + "Collaborated with product managers, engineers, and data " + + "analysts in an agile environment.")) + .build(), + CvEntry.builder("UX Designer") + .subtitle("BrightHealth (HealthTech)") + .place("San Francisco, CA") + .date("2020 - 2022") + .body(String.join(NEWLINE, + "Redesigned patient portal experience, reducing support " + + "tickets by 28% and improving user satisfaction.", + "Planned and facilitated usability tests and synthesized " + + "findings to drive iterative improvements.", + "Partnered with engineers to ensure feasible solutions " + + "and pixel-perfect implementation.", + "Created reusable components and style guides to support " + + "a cohesive cross-platform experience.")) + .build(), + CvEntry.builder("Junior UX/UI Designer") + .subtitle("Lumen Digital Agency") + .place("Austin, TX") + .date("2018 - 2020") + .body(String.join(NEWLINE, + "Supported discovery workshops, user flows, and " + + "wireframes for clients across e-commerce and SaaS.", + "Designed responsive websites and landing pages following " + + "best practices in accessibility and usability.", + "Collaborated with copywriters and developers to deliver " + + "high-quality, user-centered solutions.")) + .build())); + } + + /** The projects, each behind its tinted tile. */ + private static EntriesSection projects() { + return new EntriesSection("SELECTED PROJECTS", List.of( + CvEntry.builder("NovaFin Mobile App") + .subtitle("Personal Finance Management") + .date("2023") + .icon("project-wallet") + .body("Led UX/UI design for budgeting, goal tracking, and " + + "insights features. Conducted user interviews and " + + "usability tests that informed a simplified navigation " + + "and data visualization. Resulted in 32% improvement in " + + "task success rate and 22% increase in weekly active " + + "users.") + .build(), + CvEntry.builder("BrightHealth Patient Portal") + .subtitle("Healthcare Platform Redesign") + .date("2021") + .icon("project-health") + .body("Redesigned key patient flows including appointment " + + "scheduling, test results, and messaging. Created a new " + + "design system and component library used across web and " + + "mobile. Reduced support tickets by 28%.") + .build())); + } + + /** The degree, with the place and the years on its third line. */ + private static EntriesSection education() { + return new EntriesSection("EDUCATION", List.of( + CvEntry.builder("Bachelor of Fine Arts in Graphic Design") + .subtitle("California College of the Arts") + .place("San Francisco, CA") + .date("2014 - 2018") + .icon("graduation") + .build())); + } + + /** The languages, each carrying both the rating and the word for it. */ + private static SkillsSection languages() { + return new SkillsSection("LANGUAGES", List.of(new SkillGroup("LANGUAGES", List.of( + CvSkill.of("English", 1.0, "Native"), + CvSkill.of("Spanish", 1.0, "Native"), + CvSkill.of("Portuguese", 0.6, "Conversational"))))); + } + + /** The closing line. Its title names the berth and is not drawn. */ + private static ParagraphSection quote() { + return new ParagraphSection("QUOTE", + "I design with empathy, iterate with purpose, and " + + "collaborate to build products people love."); + } +} diff --git a/examples/src/main/java/com/demcha/examples/support/WeeklyScheduleRenderer.java b/examples/src/main/java/com/demcha/examples/support/WeeklyScheduleRenderer.java index 236c4a298..bd9910d3e 100644 --- a/examples/src/main/java/com/demcha/examples/support/WeeklyScheduleRenderer.java +++ b/examples/src/main/java/com/demcha/examples/support/WeeklyScheduleRenderer.java @@ -5,6 +5,7 @@ import com.demcha.compose.document.api.DocumentSession; import com.demcha.compose.document.dsl.SectionBuilder; import com.demcha.compose.document.node.TextAlign; +import com.demcha.compose.document.output.DocumentPageZone; import com.demcha.compose.document.style.DocumentColor; import com.demcha.compose.document.style.DocumentInsets; import com.demcha.compose.document.style.DocumentStroke; @@ -336,13 +337,33 @@ public static Layout landscape() { double dayColumn = 105.5; return new Layout( DocumentPageSize.of(PAGE_WIDTH, 472), - new DocumentInsets(14, SIDE_MARGIN, 14, SIDE_MARGIN), + new DocumentInsets(BOTTOM_MARGIN, SIDE_MARGIN, BOTTOM_MARGIN, SIDE_MARGIN), Math.floor((printable - DAYS_IN_WEEK * dayColumn) * 100) / 100, dayColumn); } public double subColWidth() { return dayColWidth / 4.0; } public int totalColumns() { return 1 + DAYS_IN_WEEK * 4; } + + /** What the footer has to lay itself out inside. */ + public double printableWidth() { + return pageSize.width() - margin.left() - margin.right(); + } + + /** + * Half the footer rule: the printable width less the seal and the gap + * on either side of it. + * + *

Equal weights already put the seal's slot on the centre + * line — that part needs no arithmetic. What this buys is that the + * rules fill the slots they are given: a shape narrower than its slot + * is drawn at the slot's left edge, so a fixed width leaves the drawn + * rule short on one side of the seal and short of the margin on the + * other, and the foot reads as lopsided however centred the seal is.

+ */ + public double footerRuleWidth() { + return (printableWidth() - SEAL_WIDTH - 2 * SEAL_GAP) / 2; + } } // ───────────────────────── Constants ───────────────────────────── @@ -354,6 +375,68 @@ public static Layout landscape() { /** Left and right margin; the columns divide what is left. */ private static final double SIDE_MARGIN = 8; + + /** Top and bottom margin; the footer zone has to clear the bottom one. */ + private static final double BOTTOM_MARGIN = 14; + + /** The seal that breaks the footer rule, and the air on either side of it. */ + private static final double SEAL_WIDTH = 44; + private static final double SEAL_GAP = 8; + + /** Times-Bold, as the seal is set: the metrics the seal's geometry reads. */ + private static final double TIMES_BOLD_ASCENT = 0.683; + private static final double TIMES_BOLD_DESCENT = 0.217; + private static final double TIMES_BOLD_CAP_HEIGHT = 0.676; + + /** The seal's letter, and the point of air the pill keeps above and below it. */ + private static final double SEAL_LETTER_SIZE = 13; + private static final double SEAL_PADDING_Y = 1; + + /** + * How far the seal's letter drops to sit level in its pill. + * + *

A line box is tall enough for a descender and a capital has none, so + * centring the box centres the unused space along with the letter and the + * letter rides high. Dropping the box by half that unused space — the cap + * height against what the ascent and descent leave — puts the ink on the + * pill's centre line. It scales with the letter, but not with the font: + * set the seal in something other than Times-Bold and these three metrics + * have to change with it.

+ */ + private static final double SEAL_LETTER_DROP = SEAL_LETTER_SIZE + * (TIMES_BOLD_CAP_HEIGHT - (TIMES_BOLD_ASCENT - TIMES_BOLD_DESCENT)) / 2; + + /** The pill: the letter's line box, plus its point of air top and bottom. */ + private static final double SEAL_HEIGHT = + SEAL_LETTER_SIZE * (TIMES_BOLD_ASCENT + TIMES_BOLD_DESCENT) + 2 * SEAL_PADDING_Y; + + /** The hairline the seal breaks, and the drop that lands it on the seal's centre line. */ + private static final double FOOTER_RULE_THICKNESS = 0.6; + private static final double FOOTER_RULE_DROP = (SEAL_HEIGHT - FOOTER_RULE_THICKNESS) / 2; + + /** + * The build line under the rule, set in Courier, and the air between the + * two. The depth reserved for it is the font's box rather than its metric + * descent, because it is ink, not the line box, that has to stay out of + * the margin, and a descender reaches past the line it is measured by. + */ + private static final double COURIER_ASCENT = 0.629; + private static final double COURIER_BOX_DESCENT = 0.250; + private static final double BUILD_LINE_SIZE = 7.5; + private static final double FOOTER_GAP_Y = 6; + + /** + * Height reserved at the foot of every page. + * + *

A footer zone is measured from the foot of the sheet, not + * from the bottom margin, and its content is laid out from the top of the + * band down. So the band has to be the furniture plus the margin it must + * not print into: at 34 — the furniture alone — the build line came within + * three millimetres of the paper edge, inside the dead zone of most office + * printers, and this board is printed.

+ */ + private static final double FOOTER_ZONE_HEIGHT = SEAL_HEIGHT + FOOTER_GAP_Y + + BUILD_LINE_SIZE * (COURIER_ASCENT + COURIER_BOX_DESCENT) + BOTTOM_MARGIN; private static final DayShift[] EMPTY_WEEK = { DayShift.NONE, DayShift.NONE, DayShift.NONE, DayShift.NONE, DayShift.NONE, DayShift.NONE, DayShift.NONE @@ -404,6 +487,11 @@ public static void renderTo(Path outputFile, Layout layout) throws Exception { Objects.requireNonNull(outputFile, "outputFile"); Objects.requireNonNull(brandName, "brandName"); + // The brand heads the board and its initial seals the foot, so a blank + // one is a board with nobody's name on it rather than a default. + if (brandName.isBlank()) { + throw new IllegalArgumentException("brandName must not be blank"); + } Objects.requireNonNull(weekStart, "weekStart"); Objects.requireNonNull(staff, "staff"); Objects.requireNonNull(week, "week"); @@ -431,6 +519,55 @@ public static void renderTo(Path outputFile, .margin(layout.margin()) .create()) { + // The rule, its seal and the build line are page furniture, not + // the last thing on the board: as a zone they are drawn against + // the bottom margin, so a week that fills the sheet and a week + // that half fills it both foot the same way. + document.chrome().zone(DocumentPageZone.footer(FOOTER_ZONE_HEIGHT, page -> new SectionBuilder() + .name("FooterZone") + .spacing(6) + .addRow("FooterRule", row -> row + .spacing(SEAL_GAP) + // Equal weights on the two rules are what put the + // seal in the middle; the shapes then have to fill + // the slots the weights hand them. + .weights(layout.footerRuleWidth(), SEAL_WIDTH, layout.footerRuleWidth()) + .addSection("LeftRule", section -> section + .padding(new DocumentInsets(FOOTER_RULE_DROP, 0, 0, 0)) + .addShape(shape -> shape.size(layout.footerRuleWidth(), FOOTER_RULE_THICKNESS) + .fillColor(theme.brandAccent()).margin(DocumentInsets.zero()))) + .addSection("Seal", section -> section + .stroke(DocumentStroke.of(theme.brandAccent(), 0.5)) + .cornerRadius(9) + .padding(new DocumentInsets(SEAL_PADDING_Y + SEAL_LETTER_DROP, 0, + SEAL_PADDING_Y - SEAL_LETTER_DROP, 0)) + .addParagraph(p -> p + .text(sealLetter(brandName)) + .align(TextAlign.CENTER) + .textStyle(DocumentTextStyle.builder() + .fontName(FontName.TIMES_ROMAN) + .decoration(DocumentTextDecoration.BOLD) + .size(SEAL_LETTER_SIZE) + .color(theme.brandAccent()) + .build()) + .margin(DocumentInsets.zero()))) + .addSection("RightRule", section -> section + .padding(new DocumentInsets(FOOTER_RULE_DROP, 0, 0, 0)) + .addShape(shape -> shape.size(layout.footerRuleWidth(), FOOTER_RULE_THICKNESS) + .fillColor(theme.brandAccent()).margin(DocumentInsets.zero())))) + // Left, against the same margin the board's first column + // starts on: it is a credit, not part of the device above + // it, and centring gave it a weight it should not carry. + .addParagraph(p -> p + .text("Composed with GraphCompose — examples/.../WeeklyScheduleRenderer.java") + .textStyle(DocumentTextStyle.builder() + .fontName(FontName.COURIER) + .size(7.5) + .color(theme.muted()) + .build()) + .margin(DocumentInsets.zero())) + .build())); + document.pageFlow() .name("WeeklyShiftSchedule") .spacing(3) @@ -509,47 +646,24 @@ public static void renderTo(Path outputFile, } }) - // Footer rule + seal. - .addRow("FooterRule", row -> row - .spacing(8) - .weights(1, 0.12, 1) - .addSection("LeftRule", section -> section - .padding(new DocumentInsets(10, 0, 0, 0)) - .addShape(shape -> shape.size(292, 0.6).fillColor(theme.brandAccent()).margin(DocumentInsets.zero()))) - .addSection("Seal", section -> section - .stroke(DocumentStroke.of(theme.brandAccent(), 0.5)) - .cornerRadius(9) - .padding(new DocumentInsets(1, 0, 1, 0)) - .addParagraph(p -> p - .text("S") - .align(TextAlign.CENTER) - .textStyle(DocumentTextStyle.builder() - .fontName(FontName.TIMES_ROMAN) - .decoration(DocumentTextDecoration.BOLD) - .size(13) - .color(theme.brandAccent()) - .build()) - .margin(DocumentInsets.zero()))) - .addSection("RightRule", section -> section - .padding(new DocumentInsets(10, 0, 0, 0)) - .addShape(shape -> shape.size(292, 0.6).fillColor(theme.brandAccent()).margin(DocumentInsets.zero())))) - - .addSection("BuildFooter", section -> section - .padding(new DocumentInsets(6, 0, 0, 0)) - .addParagraph(p -> p - .text("Composed with GraphCompose — examples/.../WeeklyScheduleRenderer.java") - .textStyle(DocumentTextStyle.builder() - .fontName(FontName.COURIER) - .size(7.5) - .color(theme.muted()) - .build()) - .margin(DocumentInsets.zero()))) .build(); document.buildPdf(); } } + /** + * The letter the footer seal carries: the board's own initial, so a + * schedule printed for one venue is not sealed with another's. + * + *

Read as a code point rather than a {@code char} so a brand opening on + * a character outside the basic plane is not sealed with half of it.

+ */ + private static String sealLetter(String brandName) { + String trimmed = brandName.strip(); + return String.valueOf(Character.toChars(trimmed.codePointAt(0))).toUpperCase(Locale.ROOT); + } + /** * Split a day note like {@code "Bank Holiday Monday / Clean Crushed * Ice Machine & Area"} into its slash-separated fragments so each diff --git a/examples/src/main/java/com/demcha/examples/support/WorkspaceInvoiceSampleData.java b/examples/src/main/java/com/demcha/examples/support/WorkspaceInvoiceSampleData.java new file mode 100644 index 000000000..9a569a11e --- /dev/null +++ b/examples/src/main/java/com/demcha/examples/support/WorkspaceInvoiceSampleData.java @@ -0,0 +1,141 @@ +package com.demcha.examples.support; + +import com.demcha.compose.document.templates.data.invoice.InvoiceBrand; +import com.demcha.compose.document.templates.data.invoice.InvoiceContactBlock; +import com.demcha.compose.document.templates.data.invoice.InvoiceMasthead; +import com.demcha.compose.document.templates.data.invoice.InvoiceNotesBlock; +import com.demcha.compose.document.templates.data.invoice.InvoicePaymentBlock; +import com.demcha.compose.document.templates.data.invoice.InvoiceRecipient; +import com.demcha.compose.document.templates.data.invoice.InvoiceServiceLines; +import com.demcha.compose.document.templates.data.invoice.InvoiceSummaryBlock; +import com.demcha.compose.document.templates.data.invoice.InvoiceTotalsBlock; +import com.demcha.compose.document.templates.data.invoice.StructuredInvoiceData; + +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.List; + +/** + * Shared sample data for the Workspace invoice example. + * + *

Kept in lockstep with the qa module's {@code WorkspaceInvoiceFixtures} — + * the two modules cannot share a source file, so a content change here belongs + * there too.

+ */ +public final class WorkspaceInvoiceSampleData { + + private WorkspaceInvoiceSampleData() { + } + + /** + * The sample invoice — six metadata rows, two addressed parties, four + * marked service lines, eight payment fields, two summed rows and a closing + * line with an address inside it. + * + * @return the document the example renders + */ + public static StructuredInvoiceData sample() { + return new StructuredInvoiceData( + brand(), supplier(), masthead(), billTo(), shipTo(), + new InvoiceSummaryBlock("", "", ""), + serviceLines(), totals(), payment(), notes(), "USD"); + } + + /** No logo: the wordmark is what a document without one falls back to. */ + private static InvoiceBrand brand() { + return new InvoiceBrand(null, "kestrel", "", "", "", ""); + } + + private static InvoiceContactBlock supplier() { + return new InvoiceContactBlock( + "Kestrel Collaboration, Inc.", + List.of("500 Howard Street, 6th Floor", "San Francisco, CA 94105", + "United States"), + "", "", "kestrel.example", + "", "", + "VAT ID:", "US 77-0560185"); + } + + private static InvoiceMasthead masthead() { + List entries = new ArrayList<>(); + entries.add(new InvoiceMasthead.Entry("Invoice Number:", "INV-2024-0003521", true)); + entries.add(new InvoiceMasthead.Entry("Invoice Date:", "27 May 2024", false)); + entries.add(new InvoiceMasthead.Entry("Billing Period:", "1 - 31 May 2024", false)); + entries.add(new InvoiceMasthead.Entry("Workspace ID:", "T04FQ9L2M", false)); + entries.add(new InvoiceMasthead.Entry("Payment Terms:", "Net 30", false)); + entries.add(new InvoiceMasthead.Entry("Due Date:", "26 June 2024", false)); + return new InvoiceMasthead("INVOICE", entries); + } + + /** The registration is the labelled pair printed under the address. */ + private static InvoiceRecipient billTo() { + return new InvoiceRecipient("BILL TO", "Bright Future Ltd.", "", + List.of("45 King Street", "Manchester M2 7AZ", "United Kingdom"), + "", "", "VAT ID:", "GB 987 6543 21"); + } + + private static InvoiceRecipient shipTo() { + return new InvoiceRecipient("SHIP TO", "Bright Future Ltd.", "", + List.of("Unit 4, Riverside Park", "Leeds LS1 4AP", "United Kingdom"), + "", "", "", ""); + } + + private static InvoiceServiceLines serviceLines() { + // The currency is not in the labels: the preset names it once per money + // column, so a document states the code and nothing else. + InvoiceServiceLines.Columns columns = new InvoiceServiceLines.Columns( + "", "DESCRIPTION", "PLAN / SERVICE", "QTY", "UNIT PRICE", "AMOUNT", ""); + List lines = new ArrayList<>(); + lines.add(line(1, "Kestrel Business+", "Workspace subscription", + "Business+ Annual", "50", "Users", "8.75", "437.50", "grid")); + lines.add(line(2, "Enterprise Key Management", "Customer-managed encryption keys", + "Add-on", "1", "", "100.00", "100.00", "search")); + lines.add(line(3, "Priority Support", "Named engineer and response target", + "Standard", "1", "", "50.00", "50.00", "shield")); + lines.add(line(4, "Workflow Automation", "Templates and scheduled runs", + "Included", "1", "", "0.00", "0.00", "grid")); + return new InvoiceServiceLines(columns, lines); + } + + private static InvoiceServiceLines.Line line(int number, String title, String description, + String plan, String quantity, String unit, + String unitPrice, String amount, String icon) { + return new InvoiceServiceLines.Line(number, title, description, plan, + new BigDecimal(quantity), unit, decimal(unitPrice), decimal(amount), "", icon); + } + + private static InvoiceTotalsBlock totals() { + List rows = new ArrayList<>(); + rows.add(new InvoiceTotalsBlock.Row("Subtotal", decimal("587.50"))); + rows.add(new InvoiceTotalsBlock.Row("Tax (0%)", decimal("0.00"))); + return new InvoiceTotalsBlock(rows, "TOTAL DUE", decimal("587.50")); + } + + private static InvoicePaymentBlock payment() { + List fields = new ArrayList<>(); + fields.add(new InvoicePaymentBlock.Field("Bank Name:", "Bay Union Bank, N.A.")); + fields.add(new InvoicePaymentBlock.Field("Account Name:", "Kestrel Collaboration, Inc.")); + fields.add(new InvoicePaymentBlock.Field("Account Number:", "000000123456789")); + fields.add(new InvoicePaymentBlock.Field("Routing (ABA):", "021000021")); + fields.add(new InvoicePaymentBlock.Field("SWIFT / BIC:", "BAYUUS33")); + fields.add(new InvoicePaymentBlock.Field("Currency:", "USD")); + fields.add(new InvoicePaymentBlock.Field("Reference:", "INV-2024-0003521")); + fields.add(new InvoicePaymentBlock.Field("Bank Address:", "1 Market Plaza, San Francisco")); + return new InvoicePaymentBlock("PAYMENT DETAILS", fields, + "Please include the invoice number in your payment reference.", + "PAYMENT DUE BY", + "26 June 2024", + "", ""); + } + + private static InvoiceNotesBlock notes() { + return new InvoiceNotesBlock("Thank you for using Kestrel.", + List.of("Questions about this invoice? Reach us at billing@kestrel.example " + + "and we will get back to you within one business day."), + "billing@kestrel.example", ""); + } + + private static BigDecimal decimal(String printed) { + return new BigDecimal(printed.replace(",", "")); + } +} diff --git a/examples/src/main/java/com/demcha/examples/templates/cv/v2/CharcoalGoldExample.java b/examples/src/main/java/com/demcha/examples/templates/cv/v2/CharcoalGoldExample.java new file mode 100644 index 000000000..db78ef2e2 --- /dev/null +++ b/examples/src/main/java/com/demcha/examples/templates/cv/v2/CharcoalGoldExample.java @@ -0,0 +1,57 @@ +package com.demcha.examples.templates.cv.v2; + +import com.demcha.compose.GraphCompose; +import com.demcha.compose.document.api.DocumentPageSize; +import com.demcha.compose.document.api.DocumentSession; +import com.demcha.compose.document.templates.api.DocumentTemplate; +import com.demcha.compose.document.templates.cv.data.CvDocument; +import com.demcha.compose.document.templates.cv.presets.CharcoalGold; +import com.demcha.examples.support.CharcoalGoldSampleData; +import com.demcha.examples.support.ExampleOutputPaths; + +import java.nio.file.Path; + +/** + * Renders the layered {@code cv.v2} Charcoal Gold preset against the + * project-manager sample. + * + *

Output: + * {@code examples/target/generated-pdfs/templates/cv/cv-charcoal-gold-v2.pdf}.

+ * + *

Unlike its ported siblings this preset leaves the page to the caller, + * so the session sets A4 with no margin: both columns run to the paper edge + * and each carries its own padding.

+ */ +public final class CharcoalGoldExample { + + private CharcoalGoldExample() { + } + + /** + * @return absolute path of the rendered PDF + * @throws Exception if rendering fails + */ + public static Path generate() throws Exception { + Path outputFile = ExampleOutputPaths.prepare( + "templates/cv", "cv-charcoal-gold-v2.pdf"); + CvDocument doc = CharcoalGoldSampleData.sample(); + DocumentTemplate template = CharcoalGold.create(); + + try (DocumentSession document = GraphCompose.document(outputFile) + .pageSize(DocumentPageSize.A4) + .margin(0f, 0f, 0f, 0f) + .create()) { + template.compose(document, doc); + document.buildPdf(); + } + return outputFile; + } + + /** + * @param args ignored + * @throws Exception if rendering fails + */ + public static void main(String[] args) throws Exception { + System.out.println("Generated: " + generate()); + } +} diff --git a/examples/src/main/java/com/demcha/examples/templates/cv/v2/MidnightNavyExample.java b/examples/src/main/java/com/demcha/examples/templates/cv/v2/MidnightNavyExample.java new file mode 100644 index 000000000..0c73823e1 --- /dev/null +++ b/examples/src/main/java/com/demcha/examples/templates/cv/v2/MidnightNavyExample.java @@ -0,0 +1,52 @@ +package com.demcha.examples.templates.cv.v2; + +import com.demcha.compose.GraphCompose; +import com.demcha.compose.document.api.DocumentSession; +import com.demcha.compose.document.templates.api.DocumentTemplate; +import com.demcha.compose.document.templates.cv.data.CvDocument; +import com.demcha.compose.document.templates.cv.presets.MidnightNavy; +import com.demcha.examples.support.ExampleOutputPaths; +import com.demcha.examples.support.MidnightNavySampleData; + +import java.nio.file.Path; + +/** + * Renders the layered {@code cv.v2} Midnight Navy preset against the design + * sample. + * + *

Output: + * {@code examples/target/generated-pdfs/templates/cv/cv-midnight-navy-v2.pdf}.

+ * + *

The preset owns its page geometry — every length is a share of the + * design's own grid, and the navy plate is a page background sized by the same + * ratio the body row splits on — so the session starts unconfigured.

+ */ +public final class MidnightNavyExample { + + private MidnightNavyExample() { + } + + /** + * @return absolute path of the rendered PDF + * @throws Exception if rendering fails + */ + public static Path generate() throws Exception { + Path outputFile = ExampleOutputPaths.prepare("templates/cv", "cv-midnight-navy-v2.pdf"); + CvDocument doc = MidnightNavySampleData.sample(); + DocumentTemplate template = MidnightNavy.create(); + + try (DocumentSession document = GraphCompose.document(outputFile).create()) { + template.compose(document, doc); + document.buildPdf(); + } + return outputFile; + } + + /** + * @param args ignored + * @throws Exception if rendering fails + */ + public static void main(String[] args) throws Exception { + System.out.println("Generated: " + generate()); + } +} diff --git a/examples/src/main/java/com/demcha/examples/templates/cv/v2/NavySidebarExample.java b/examples/src/main/java/com/demcha/examples/templates/cv/v2/NavySidebarExample.java new file mode 100644 index 000000000..25a43f09e --- /dev/null +++ b/examples/src/main/java/com/demcha/examples/templates/cv/v2/NavySidebarExample.java @@ -0,0 +1,52 @@ +package com.demcha.examples.templates.cv.v2; + +import com.demcha.compose.GraphCompose; +import com.demcha.compose.document.api.DocumentSession; +import com.demcha.compose.document.templates.api.DocumentTemplate; +import com.demcha.compose.document.templates.cv.data.CvDocument; +import com.demcha.compose.document.templates.cv.presets.NavySidebar; +import com.demcha.examples.support.ExampleOutputPaths; +import com.demcha.examples.support.NavySidebarSampleData; + +import java.nio.file.Path; + +/** + * Renders the layered {@code cv.v2} Navy Sidebar preset against the + * marketing-manager sample. + * + *

Output: + * {@code examples/target/generated-pdfs/templates/cv/cv-navy-sidebar-v2.pdf}.

+ * + *

The preset owns its page — A4 with no margin, the navy plate painted as + * a page background — so the session starts unconfigured.

+ */ +public final class NavySidebarExample { + + private NavySidebarExample() { + } + + /** + * @return absolute path of the rendered PDF + * @throws Exception if rendering fails + */ + public static Path generate() throws Exception { + Path outputFile = ExampleOutputPaths.prepare( + "templates/cv", "cv-navy-sidebar-v2.pdf"); + CvDocument doc = NavySidebarSampleData.sample(); + DocumentTemplate template = NavySidebar.create(); + + try (DocumentSession document = GraphCompose.document(outputFile).create()) { + template.compose(document, doc); + document.buildPdf(); + } + return outputFile; + } + + /** + * @param args ignored + * @throws Exception if rendering fails + */ + public static void main(String[] args) throws Exception { + System.out.println("Generated: " + generate()); + } +} diff --git a/examples/src/main/java/com/demcha/examples/templates/cv/v2/OrangeOpsExample.java b/examples/src/main/java/com/demcha/examples/templates/cv/v2/OrangeOpsExample.java new file mode 100644 index 000000000..3d5e9754b --- /dev/null +++ b/examples/src/main/java/com/demcha/examples/templates/cv/v2/OrangeOpsExample.java @@ -0,0 +1,62 @@ +package com.demcha.examples.templates.cv.v2; + +import com.demcha.compose.GraphCompose; +import com.demcha.compose.document.api.DocumentSession; +import com.demcha.compose.document.templates.api.DocumentTemplate; +import com.demcha.compose.document.templates.cv.data.CvDocument; +import com.demcha.compose.document.templates.cv.presets.OrangeOps; +import com.demcha.compose.font.FontFamilyDefinition; +import com.demcha.examples.support.ExampleOutputPaths; +import com.demcha.examples.support.OrangeOpsSampleData; + +import java.nio.file.Path; + +/** + * Renders the layered {@code cv.v2} Orange Ops preset against the design + * sample. + * + *

Output: + * {@code examples/target/generated-pdfs/templates/cv/cv-orange-ops-v2.pdf}.

+ * + *

The preset owns its page geometry — every length is a share of the + * design's own grid — so the session starts unconfigured. What it does need is + * the display family: Orange Ops sets its name and headings in Oswald, which + * neither the templates artifact nor {@code graph-compose-fonts} carries, so + * registering it is the caller's job and this example is the caller. The two + * faces are the example module's own resources.

+ */ +public final class OrangeOpsExample { + + private OrangeOpsExample() { + } + + /** + * @return absolute path of the rendered PDF + * @throws Exception if rendering fails + */ + public static Path generate() throws Exception { + Path outputFile = ExampleOutputPaths.prepare("templates/cv", "cv-orange-ops-v2.pdf"); + CvDocument doc = OrangeOpsSampleData.sample(); + DocumentTemplate template = OrangeOps.create(); + + try (DocumentSession document = GraphCompose.document(outputFile).create()) { + document.registerFontFamily( + FontFamilyDefinition.classpath(OrangeOps.DISPLAY_FONT, + "/fonts/oswald/Oswald-Regular.ttf") + .wordFamily("Oswald") + .boldResource("/fonts/oswald/Oswald-SemiBold.ttf") + .build()); + template.compose(document, doc); + document.buildPdf(); + } + return outputFile; + } + + /** + * @param args ignored + * @throws Exception if rendering fails + */ + public static void main(String[] args) throws Exception { + System.out.println("Generated: " + generate()); + } +} diff --git a/examples/src/main/java/com/demcha/examples/templates/cv/v2/ProfessionalSidebarExample.java b/examples/src/main/java/com/demcha/examples/templates/cv/v2/ProfessionalSidebarExample.java new file mode 100644 index 000000000..570a3fc4e --- /dev/null +++ b/examples/src/main/java/com/demcha/examples/templates/cv/v2/ProfessionalSidebarExample.java @@ -0,0 +1,52 @@ +package com.demcha.examples.templates.cv.v2; + +import com.demcha.compose.GraphCompose; +import com.demcha.compose.document.api.DocumentSession; +import com.demcha.compose.document.templates.api.DocumentTemplate; +import com.demcha.compose.document.templates.cv.data.CvDocument; +import com.demcha.compose.document.templates.cv.presets.ProfessionalSidebar; +import com.demcha.examples.support.ExampleOutputPaths; +import com.demcha.examples.support.ProfessionalSidebarSampleData; + +import java.nio.file.Path; + +/** + * Renders the layered {@code cv.v2} Professional Sidebar preset against the + * backend-engineer sample. + * + *

Output: + * {@code examples/target/generated-pdfs/templates/cv/cv-professional-sidebar-v2.pdf}.

+ * + *

The preset owns its page geometry — a 491.6 x 737.28pt sheet with no + * margin — so the session starts unconfigured.

+ */ +public final class ProfessionalSidebarExample { + + private ProfessionalSidebarExample() { + } + + /** + * @return absolute path of the rendered PDF + * @throws Exception if rendering fails + */ + public static Path generate() throws Exception { + Path outputFile = ExampleOutputPaths.prepare( + "templates/cv", "cv-professional-sidebar-v2.pdf"); + CvDocument doc = ProfessionalSidebarSampleData.sample(); + DocumentTemplate template = ProfessionalSidebar.create(); + + try (DocumentSession document = GraphCompose.document(outputFile).create()) { + template.compose(document, doc); + document.buildPdf(); + } + return outputFile; + } + + /** + * @param args ignored + * @throws Exception if rendering fails + */ + public static void main(String[] args) throws Exception { + System.out.println("Generated: " + generate()); + } +} diff --git a/examples/src/main/java/com/demcha/examples/templates/cv/v2/SerifHeadlineExample.java b/examples/src/main/java/com/demcha/examples/templates/cv/v2/SerifHeadlineExample.java new file mode 100644 index 000000000..da82b37a4 --- /dev/null +++ b/examples/src/main/java/com/demcha/examples/templates/cv/v2/SerifHeadlineExample.java @@ -0,0 +1,52 @@ +package com.demcha.examples.templates.cv.v2; + +import com.demcha.compose.GraphCompose; +import com.demcha.compose.document.api.DocumentSession; +import com.demcha.compose.document.templates.api.DocumentTemplate; +import com.demcha.compose.document.templates.cv.data.CvDocument; +import com.demcha.compose.document.templates.cv.presets.SerifHeadline; +import com.demcha.examples.support.ExampleOutputPaths; +import com.demcha.examples.support.SerifHeadlineSampleData; + +import java.nio.file.Path; + +/** + * Renders the layered {@code cv.v2} Serif Headline preset against the + * software-engineer sample. + * + *

Output: + * {@code examples/target/generated-pdfs/templates/cv/cv-serif-headline-v2.pdf}.

+ * + *

The preset owns its page — A4, with the margin the design's own grid + * defines — so the session starts unconfigured.

+ */ +public final class SerifHeadlineExample { + + private SerifHeadlineExample() { + } + + /** + * @return absolute path of the rendered PDF + * @throws Exception if rendering fails + */ + public static Path generate() throws Exception { + Path outputFile = ExampleOutputPaths.prepare( + "templates/cv", "cv-serif-headline-v2.pdf"); + CvDocument doc = SerifHeadlineSampleData.sample(); + DocumentTemplate template = SerifHeadline.create(); + + try (DocumentSession document = GraphCompose.document(outputFile).create()) { + template.compose(document, doc); + document.buildPdf(); + } + return outputFile; + } + + /** + * @param args ignored + * @throws Exception if rendering fails + */ + public static void main(String[] args) throws Exception { + System.out.println("Generated: " + generate()); + } +} diff --git a/examples/src/main/java/com/demcha/examples/templates/cv/v2/SlateOrangeExample.java b/examples/src/main/java/com/demcha/examples/templates/cv/v2/SlateOrangeExample.java new file mode 100644 index 000000000..698d32580 --- /dev/null +++ b/examples/src/main/java/com/demcha/examples/templates/cv/v2/SlateOrangeExample.java @@ -0,0 +1,51 @@ +package com.demcha.examples.templates.cv.v2; + +import com.demcha.compose.GraphCompose; +import com.demcha.compose.document.api.DocumentSession; +import com.demcha.compose.document.templates.api.DocumentTemplate; +import com.demcha.compose.document.templates.cv.data.CvDocument; +import com.demcha.compose.document.templates.cv.presets.SlateOrange; +import com.demcha.examples.support.ExampleOutputPaths; +import com.demcha.examples.support.SlateOrangeSampleData; + +import java.nio.file.Path; + +/** + * Renders the layered {@code cv.v2} Slate Orange preset against the + * operations sample. + * + *

Output: + * {@code examples/target/generated-pdfs/templates/cv/cv-slate-orange-v2.pdf}.

+ * + *

The preset owns its page geometry — the sheet is drawn to the paper edge + * and every column pads itself — so the session starts unconfigured.

+ */ +public final class SlateOrangeExample { + + private SlateOrangeExample() { + } + + /** + * @return absolute path of the rendered PDF + * @throws Exception if rendering fails + */ + public static Path generate() throws Exception { + Path outputFile = ExampleOutputPaths.prepare("templates/cv", "cv-slate-orange-v2.pdf"); + CvDocument doc = SlateOrangeSampleData.sample(); + DocumentTemplate template = SlateOrange.create(); + + try (DocumentSession document = GraphCompose.document(outputFile).create()) { + template.compose(document, doc); + document.buildPdf(); + } + return outputFile; + } + + /** + * @param args ignored + * @throws Exception if rendering fails + */ + public static void main(String[] args) throws Exception { + System.out.println("Generated: " + generate()); + } +} diff --git a/examples/src/main/java/com/demcha/examples/templates/cv/v2/TealPulseExample.java b/examples/src/main/java/com/demcha/examples/templates/cv/v2/TealPulseExample.java new file mode 100644 index 000000000..35243ac00 --- /dev/null +++ b/examples/src/main/java/com/demcha/examples/templates/cv/v2/TealPulseExample.java @@ -0,0 +1,51 @@ +package com.demcha.examples.templates.cv.v2; + +import com.demcha.compose.GraphCompose; +import com.demcha.compose.document.api.DocumentSession; +import com.demcha.compose.document.templates.api.DocumentTemplate; +import com.demcha.compose.document.templates.cv.data.CvDocument; +import com.demcha.compose.document.templates.cv.presets.TealPulse; +import com.demcha.examples.support.ExampleOutputPaths; +import com.demcha.examples.support.TealPulseSampleData; + +import java.nio.file.Path; + +/** + * Renders the layered {@code cv.v2} Teal Pulse preset against the nursing + * sample. + * + *

Output: + * {@code examples/target/generated-pdfs/templates/cv/cv-teal-pulse-v2.pdf}.

+ * + *

The preset owns its page geometry — the design is drawn on a raster + * whose proportion is not A4's — so the session starts unconfigured.

+ */ +public final class TealPulseExample { + + private TealPulseExample() { + } + + /** + * @return absolute path of the rendered PDF + * @throws Exception if rendering fails + */ + public static Path generate() throws Exception { + Path outputFile = ExampleOutputPaths.prepare("templates/cv", "cv-teal-pulse-v2.pdf"); + CvDocument doc = TealPulseSampleData.sample(); + DocumentTemplate template = TealPulse.create(); + + try (DocumentSession document = GraphCompose.document(outputFile).create()) { + template.compose(document, doc); + document.buildPdf(); + } + return outputFile; + } + + /** + * @param args ignored + * @throws Exception if rendering fails + */ + public static void main(String[] args) throws Exception { + System.out.println("Generated: " + generate()); + } +} diff --git a/examples/src/main/java/com/demcha/examples/templates/cv/v2/TerracottaRailExample.java b/examples/src/main/java/com/demcha/examples/templates/cv/v2/TerracottaRailExample.java new file mode 100644 index 000000000..6bce9e01d --- /dev/null +++ b/examples/src/main/java/com/demcha/examples/templates/cv/v2/TerracottaRailExample.java @@ -0,0 +1,57 @@ +package com.demcha.examples.templates.cv.v2; + +import com.demcha.compose.GraphCompose; +import com.demcha.compose.document.api.DocumentPageSize; +import com.demcha.compose.document.api.DocumentSession; +import com.demcha.compose.document.templates.api.DocumentTemplate; +import com.demcha.compose.document.templates.cv.data.CvDocument; +import com.demcha.compose.document.templates.cv.presets.TerracottaRail; +import com.demcha.examples.support.ExampleOutputPaths; +import com.demcha.examples.support.TerracottaRailSampleData; + +import java.nio.file.Path; + +/** + * Renders the layered {@code cv.v2} Terracotta Rail preset against the + * architect sample. + * + *

Output: + * {@code examples/target/generated-pdfs/templates/cv/cv-terracotta-rail-v2.pdf}.

+ * + *

The preset leaves the page to the caller, so the session sets A4 with + * no margin: both columns run to the paper edge and each carries its own + * padding.

+ */ +public final class TerracottaRailExample { + + private TerracottaRailExample() { + } + + /** + * @return absolute path of the rendered PDF + * @throws Exception if rendering fails + */ + public static Path generate() throws Exception { + Path outputFile = ExampleOutputPaths.prepare( + "templates/cv", "cv-terracotta-rail-v2.pdf"); + CvDocument doc = TerracottaRailSampleData.sample(); + DocumentTemplate template = TerracottaRail.create(); + + try (DocumentSession document = GraphCompose.document(outputFile) + .pageSize(DocumentPageSize.A4) + .margin(0f, 0f, 0f, 0f) + .create()) { + template.compose(document, doc); + document.buildPdf(); + } + return outputFile; + } + + /** + * @param args ignored + * @throws Exception if rendering fails + */ + public static void main(String[] args) throws Exception { + System.out.println("Generated: " + generate()); + } +} diff --git a/examples/src/main/java/com/demcha/examples/templates/cv/v2/VioletGridExample.java b/examples/src/main/java/com/demcha/examples/templates/cv/v2/VioletGridExample.java new file mode 100644 index 000000000..646ab9a06 --- /dev/null +++ b/examples/src/main/java/com/demcha/examples/templates/cv/v2/VioletGridExample.java @@ -0,0 +1,51 @@ +package com.demcha.examples.templates.cv.v2; + +import com.demcha.compose.GraphCompose; +import com.demcha.compose.document.api.DocumentSession; +import com.demcha.compose.document.templates.api.DocumentTemplate; +import com.demcha.compose.document.templates.cv.data.CvDocument; +import com.demcha.compose.document.templates.cv.presets.VioletGrid; +import com.demcha.examples.support.ExampleOutputPaths; +import com.demcha.examples.support.VioletGridSampleData; + +import java.nio.file.Path; + +/** + * Renders the layered {@code cv.v2} Violet Grid preset against the design + * sample. + * + *

Output: + * {@code examples/target/generated-pdfs/templates/cv/cv-violet-grid-v2.pdf}.

+ * + *

The preset owns its page geometry — every length is a share of the + * design's own grid — so the session starts unconfigured.

+ */ +public final class VioletGridExample { + + private VioletGridExample() { + } + + /** + * @return absolute path of the rendered PDF + * @throws Exception if rendering fails + */ + public static Path generate() throws Exception { + Path outputFile = ExampleOutputPaths.prepare("templates/cv", "cv-violet-grid-v2.pdf"); + CvDocument doc = VioletGridSampleData.sample(); + DocumentTemplate template = VioletGrid.create(); + + try (DocumentSession document = GraphCompose.document(outputFile).create()) { + template.compose(document, doc); + document.buildPdf(); + } + return outputFile; + } + + /** + * @param args ignored + * @throws Exception if rendering fails + */ + public static void main(String[] args) throws Exception { + System.out.println("Generated: " + generate()); + } +} diff --git a/examples/src/main/java/com/demcha/examples/templates/invoice/v2/ClassicInvoiceV2Example.java b/examples/src/main/java/com/demcha/examples/templates/invoice/v2/ClassicInvoiceV2Example.java new file mode 100644 index 000000000..19b4e9a23 --- /dev/null +++ b/examples/src/main/java/com/demcha/examples/templates/invoice/v2/ClassicInvoiceV2Example.java @@ -0,0 +1,61 @@ +package com.demcha.examples.templates.invoice; + +import com.demcha.compose.GraphCompose; +import com.demcha.compose.document.api.DocumentPageSize; +import com.demcha.compose.document.api.DocumentSession; +import com.demcha.compose.document.templates.api.DocumentTemplate; +import com.demcha.compose.document.templates.core.theme.BrandTheme; +import com.demcha.compose.document.templates.data.invoice.InvoiceDocumentSpec; +import com.demcha.compose.document.templates.invoice.presets.ClassicInvoice; +import com.demcha.examples.support.ExampleDataFactory; +import com.demcha.examples.support.ExampleOutputPaths; + +import java.nio.file.Path; + +/** + * Renders the layered {@code invoice.v2} Classic Invoice preset against + * the shared {@code InvoiceDocumentSpec} sample data using the default + * {@code BrandTheme.invoiceModern()} theme. + * + *

Output: + * {@code examples/target/generated-pdfs/templates/invoice/invoice-classic-v2.pdf}.

+ * + *

The preset pads the page flow itself, so the session margin stays at + * {@code ClassicInvoice.RECOMMENDED_MARGIN} (zero) and the page keeps its + * plain white background — the letterhead look of the preset.

+ */ +public final class ClassicInvoiceV2Example { + + private ClassicInvoiceV2Example() { + } + + /** + * @return absolute path of the rendered PDF + * @throws Exception if rendering fails + */ + public static Path generate() throws Exception { + Path outputFile = ExampleOutputPaths.prepare( + "templates/invoice", "invoice-classic-v2.pdf"); + InvoiceDocumentSpec spec = ExampleDataFactory.sampleInvoice(); + DocumentTemplate template = + ClassicInvoice.create(BrandTheme.invoiceModern()); + + float m = (float) ClassicInvoice.RECOMMENDED_MARGIN; + try (DocumentSession document = GraphCompose.document(outputFile) + .pageSize(DocumentPageSize.A4) + .margin(m, m, m, m) + .create()) { + template.compose(document, spec); + document.buildPdf(); + } + return outputFile; + } + + /** + * @param args ignored + * @throws Exception if rendering fails + */ + public static void main(String[] args) throws Exception { + System.out.println("Generated: " + generate()); + } +} diff --git a/examples/src/main/java/com/demcha/examples/templates/invoice/v2/ConsultingInvoiceV2Example.java b/examples/src/main/java/com/demcha/examples/templates/invoice/v2/ConsultingInvoiceV2Example.java new file mode 100644 index 000000000..277c31424 --- /dev/null +++ b/examples/src/main/java/com/demcha/examples/templates/invoice/v2/ConsultingInvoiceV2Example.java @@ -0,0 +1,54 @@ +package com.demcha.examples.templates.invoice; + +import com.demcha.compose.GraphCompose; +import com.demcha.compose.document.api.DocumentSession; +import com.demcha.compose.document.templates.api.DocumentTemplate; +import com.demcha.compose.document.templates.data.invoice.StructuredInvoiceDocumentSpec; +import com.demcha.compose.document.templates.invoice.presets.ConsultingInvoice; +import com.demcha.examples.support.ConsultingInvoiceSampleData; +import com.demcha.examples.support.ExampleOutputPaths; + +import java.nio.file.Path; + +/** + * Renders the layered {@code invoice.v2} Consulting Invoice preset against + * the shared structured invoice sample data. + * + *

Output: + * {@code examples/target/generated-pdfs/templates/invoice/invoice-consulting-v2.pdf}.

+ * + *

The preset owns its page geometry — size, margins, the page fills and + * the footer band — so the session starts unconfigured; see + * {@code ConsultingInvoice.RECOMMENDED_MARGIN}. The brand logo is supplied + * through the data, the way a caller supplies its own.

+ */ +public final class ConsultingInvoiceV2Example { + + private ConsultingInvoiceV2Example() { + } + + /** + * @return absolute path of the rendered PDF + * @throws Exception if rendering fails + */ + public static Path generate() throws Exception { + Path outputFile = ExampleOutputPaths.prepare( + "templates/invoice", "invoice-consulting-v2.pdf"); + StructuredInvoiceDocumentSpec spec = ConsultingInvoiceSampleData.sample(); + DocumentTemplate template = ConsultingInvoice.create(); + + try (DocumentSession document = GraphCompose.document(outputFile).create()) { + template.compose(document, spec); + document.buildPdf(); + } + return outputFile; + } + + /** + * @param args ignored + * @throws Exception if rendering fails + */ + public static void main(String[] args) throws Exception { + System.out.println("Generated: " + generate()); + } +} diff --git a/examples/src/main/java/com/demcha/examples/templates/invoice/v2/LumaStudioInvoiceV2Example.java b/examples/src/main/java/com/demcha/examples/templates/invoice/v2/LumaStudioInvoiceV2Example.java new file mode 100644 index 000000000..1b8b84336 --- /dev/null +++ b/examples/src/main/java/com/demcha/examples/templates/invoice/v2/LumaStudioInvoiceV2Example.java @@ -0,0 +1,54 @@ +package com.demcha.examples.templates.invoice; + +import com.demcha.compose.GraphCompose; +import com.demcha.compose.document.api.DocumentSession; +import com.demcha.compose.document.templates.api.DocumentTemplate; +import com.demcha.compose.document.templates.data.invoice.StructuredInvoiceDocumentSpec; +import com.demcha.compose.document.templates.invoice.presets.LumaStudioInvoice; +import com.demcha.examples.support.ExampleOutputPaths; +import com.demcha.examples.support.LumaStudioInvoiceSampleData; + +import java.nio.file.Path; + +/** + * Renders the layered {@code invoice.v2} Luma Studio Invoice preset against + * the shared structured invoice sample data. + * + *

Output: + * {@code examples/target/generated-pdfs/templates/invoice/invoice-luma-studio-v2.pdf}.

+ * + *

The preset owns its page geometry — size, margins, the paper tint, the + * cream sidebar column and the sign-off band — so the session starts + * unconfigured. The lockup is drawn from the brand's monogram and wordmark, + * and the amounts take their mark from the data's currency code.

+ */ +public final class LumaStudioInvoiceV2Example { + + private LumaStudioInvoiceV2Example() { + } + + /** + * @return absolute path of the rendered PDF + * @throws Exception if rendering fails + */ + public static Path generate() throws Exception { + Path outputFile = ExampleOutputPaths.prepare( + "templates/invoice", "invoice-luma-studio-v2.pdf"); + StructuredInvoiceDocumentSpec spec = LumaStudioInvoiceSampleData.sample(); + DocumentTemplate template = LumaStudioInvoice.create(); + + try (DocumentSession document = GraphCompose.document(outputFile).create()) { + template.compose(document, spec); + document.buildPdf(); + } + return outputFile; + } + + /** + * @param args ignored + * @throws Exception if rendering fails + */ + public static void main(String[] args) throws Exception { + System.out.println("Generated: " + generate()); + } +} diff --git a/examples/src/main/java/com/demcha/examples/templates/invoice/v2/PaymentsInvoiceV2Example.java b/examples/src/main/java/com/demcha/examples/templates/invoice/v2/PaymentsInvoiceV2Example.java new file mode 100644 index 000000000..278ac8ad1 --- /dev/null +++ b/examples/src/main/java/com/demcha/examples/templates/invoice/v2/PaymentsInvoiceV2Example.java @@ -0,0 +1,54 @@ +package com.demcha.examples.templates.invoice; + +import com.demcha.compose.GraphCompose; +import com.demcha.compose.document.api.DocumentSession; +import com.demcha.compose.document.templates.api.DocumentTemplate; +import com.demcha.compose.document.templates.data.invoice.StructuredInvoiceData; +import com.demcha.compose.document.templates.invoice.presets.PaymentsInvoice; +import com.demcha.examples.support.ExampleOutputPaths; +import com.demcha.examples.support.PaymentsInvoiceSampleData; + +import java.nio.file.Path; + +/** + * Renders the layered {@code invoice.v2} Payments preset against the design + * sample. + * + *

Output: + * {@code examples/target/generated-pdfs/templates/invoice/invoice-payments-v2.pdf}.

+ * + *

The preset owns its page geometry and its pagination — the table's header + * repeats and a continuation page reserves a deeper bottom margin — so the + * session starts unconfigured. The sample brings no logo, so the lockup box + * beside the title is filled with the brand's name as a wordmark.

+ */ +public final class PaymentsInvoiceV2Example { + + private PaymentsInvoiceV2Example() { + } + + /** + * @return absolute path of the rendered PDF + * @throws Exception if rendering fails + */ + public static Path generate() throws Exception { + Path outputFile = ExampleOutputPaths.prepare("templates/invoice", + "invoice-payments-v2.pdf"); + StructuredInvoiceData data = PaymentsInvoiceSampleData.sample(); + DocumentTemplate template = PaymentsInvoice.create(); + + try (DocumentSession document = GraphCompose.document(outputFile).create()) { + template.compose(document, data); + document.buildPdf(); + } + return outputFile; + } + + /** + * @param args ignored + * @throws Exception if rendering fails + */ + public static void main(String[] args) throws Exception { + System.out.println("Generated: " + generate()); + } +} diff --git a/examples/src/main/java/com/demcha/examples/templates/invoice/v2/WorkspaceInvoiceV2Example.java b/examples/src/main/java/com/demcha/examples/templates/invoice/v2/WorkspaceInvoiceV2Example.java new file mode 100644 index 000000000..2a18cfb5d --- /dev/null +++ b/examples/src/main/java/com/demcha/examples/templates/invoice/v2/WorkspaceInvoiceV2Example.java @@ -0,0 +1,54 @@ +package com.demcha.examples.templates.invoice; + +import com.demcha.compose.GraphCompose; +import com.demcha.compose.document.api.DocumentSession; +import com.demcha.compose.document.templates.api.DocumentTemplate; +import com.demcha.compose.document.templates.data.invoice.StructuredInvoiceData; +import com.demcha.compose.document.templates.invoice.presets.WorkspaceInvoice; +import com.demcha.examples.support.ExampleOutputPaths; +import com.demcha.examples.support.WorkspaceInvoiceSampleData; + +import java.nio.file.Path; + +/** + * Renders the layered {@code invoice.v2} Workspace preset against the design + * sample. + * + *

Output: + * {@code examples/target/generated-pdfs/templates/invoice/invoice-workspace-v2.pdf}.

+ * + *

The preset owns its page geometry and its pagination — the table's header + * repeats and a continuation page reserves a deeper bottom margin — so the + * session starts unconfigured. The sample brings no logo, so the lockup box + * beside the title is filled with the brand's name as a wordmark.

+ */ +public final class WorkspaceInvoiceV2Example { + + private WorkspaceInvoiceV2Example() { + } + + /** + * @return absolute path of the rendered PDF + * @throws Exception if rendering fails + */ + public static Path generate() throws Exception { + Path outputFile = ExampleOutputPaths.prepare("templates/invoice", + "invoice-workspace-v2.pdf"); + StructuredInvoiceData data = WorkspaceInvoiceSampleData.sample(); + DocumentTemplate template = WorkspaceInvoice.create(); + + try (DocumentSession document = GraphCompose.document(outputFile).create()) { + template.compose(document, data); + document.buildPdf(); + } + return outputFile; + } + + /** + * @param args ignored + * @throws Exception if rendering fails + */ + public static void main(String[] args) throws Exception { + System.out.println("Generated: " + generate()); + } +} diff --git a/examples/src/main/java/com/demcha/examples/templates/proposal/v2/EditorialProposalV2Example.java b/examples/src/main/java/com/demcha/examples/templates/proposal/v2/EditorialProposalV2Example.java new file mode 100644 index 000000000..ae4d35bec --- /dev/null +++ b/examples/src/main/java/com/demcha/examples/templates/proposal/v2/EditorialProposalV2Example.java @@ -0,0 +1,53 @@ +package com.demcha.examples.templates.proposal; + +import com.demcha.compose.GraphCompose; +import com.demcha.compose.document.api.DocumentSession; +import com.demcha.compose.document.templates.api.DocumentTemplate; +import com.demcha.compose.document.templates.data.proposal.StructuredProposalDocumentSpec; +import com.demcha.compose.document.templates.proposal.presets.EditorialProposal; +import com.demcha.examples.support.EditorialProposalSampleData; +import com.demcha.examples.support.ExampleOutputPaths; + +import java.nio.file.Path; + +/** + * Renders the layered {@code proposal.v2} Editorial Proposal preset against + * the structured proposal sample data. + * + *

Output: + * {@code examples/target/generated-pdfs/templates/proposal/proposal-editorial-v2.pdf}.

+ * + *

The same document shape the Northline example renders, in the serif + * editorial look: the preset owns its page geometry, so the session starts + * unconfigured.

+ */ +public final class EditorialProposalV2Example { + + private EditorialProposalV2Example() { + } + + /** + * @return absolute path of the rendered PDF + * @throws Exception if rendering fails + */ + public static Path generate() throws Exception { + Path outputFile = ExampleOutputPaths.prepare( + "templates/proposal", "proposal-editorial-v2.pdf"); + StructuredProposalDocumentSpec spec = EditorialProposalSampleData.sample(); + DocumentTemplate template = EditorialProposal.create(); + + try (DocumentSession document = GraphCompose.document(outputFile).create()) { + template.compose(document, spec); + document.buildPdf(); + } + return outputFile; + } + + /** + * @param args ignored + * @throws Exception if rendering fails + */ + public static void main(String[] args) throws Exception { + System.out.println("Generated: " + generate()); + } +} diff --git a/examples/src/main/java/com/demcha/examples/templates/proposal/v2/NorthlineProposalV2Example.java b/examples/src/main/java/com/demcha/examples/templates/proposal/v2/NorthlineProposalV2Example.java new file mode 100644 index 000000000..0d91e2365 --- /dev/null +++ b/examples/src/main/java/com/demcha/examples/templates/proposal/v2/NorthlineProposalV2Example.java @@ -0,0 +1,53 @@ +package com.demcha.examples.templates.proposal; + +import com.demcha.compose.GraphCompose; +import com.demcha.compose.document.api.DocumentSession; +import com.demcha.compose.document.templates.api.DocumentTemplate; +import com.demcha.compose.document.templates.data.proposal.StructuredProposalDocumentSpec; +import com.demcha.compose.document.templates.proposal.presets.NorthlineProposal; +import com.demcha.examples.support.ExampleOutputPaths; +import com.demcha.examples.support.StructuredProposalSampleData; + +import java.nio.file.Path; + +/** + * Renders the layered {@code proposal.v2} Northline Proposal preset + * against the shared structured proposal sample data. + * + *

Output: + * {@code examples/target/generated-pdfs/templates/proposal/proposal-northline-v2.pdf}.

+ * + *

The preset owns its page geometry — size, margins, the footer band + * and the page background — so the session starts unconfigured; see + * {@code NorthlineProposal.RECOMMENDED_MARGIN}.

+ */ +public final class NorthlineProposalV2Example { + + private NorthlineProposalV2Example() { + } + + /** + * @return absolute path of the rendered PDF + * @throws Exception if rendering fails + */ + public static Path generate() throws Exception { + Path outputFile = ExampleOutputPaths.prepare( + "templates/proposal", "proposal-northline-v2.pdf"); + StructuredProposalDocumentSpec spec = StructuredProposalSampleData.sample(); + DocumentTemplate template = NorthlineProposal.create(); + + try (DocumentSession document = GraphCompose.document(outputFile).create()) { + template.compose(document, spec); + document.buildPdf(); + } + return outputFile; + } + + /** + * @param args ignored + * @throws Exception if rendering fails + */ + public static void main(String[] args) throws Exception { + System.out.println("Generated: " + generate()); + } +} diff --git a/examples/src/main/java/com/demcha/examples/templates/receipt/ModernReceiptExample.java b/examples/src/main/java/com/demcha/examples/templates/receipt/ModernReceiptExample.java new file mode 100644 index 000000000..e9dbe6d73 --- /dev/null +++ b/examples/src/main/java/com/demcha/examples/templates/receipt/ModernReceiptExample.java @@ -0,0 +1,79 @@ +package com.demcha.examples.templates.receipt; + +import com.demcha.compose.GraphCompose; +import com.demcha.compose.document.api.DocumentPageSize; +import com.demcha.compose.document.api.DocumentSession; +import com.demcha.compose.document.style.DocumentColor; +import com.demcha.compose.document.templates.api.DocumentTemplate; +import com.demcha.compose.document.templates.core.identity.SvgGlyph; +import com.demcha.compose.document.templates.core.theme.BrandTheme; +import com.demcha.compose.document.templates.data.receipt.ReceiptDocumentSpec; +import com.demcha.compose.document.templates.receipt.presets.ModernReceipt; +import com.demcha.examples.support.ExampleDataFactory; +import com.demcha.examples.support.ExampleOutputPaths; + +import java.nio.file.Path; + +/** + * Renders the layered {@code receipt} Modern Receipt preset — a settled + * direct-debit collection from a fictional bank. + * + *

Output: + * {@code examples/target/generated-pdfs/templates/receipt/receipt-modern.pdf}.

+ * + *

The branding is the part worth reading: the theme + * ({@link BrandTheme#receiptModern()}) carries no brand colour at all, and + * the issuer's identity arrives through {@code ModernReceipt.Options} as a + * recolourable SVG mark plus one accent. Point those two at a different + * institution and the same preset renders that institution's + * confirmation.

+ */ +public final class ModernReceiptExample { + + /** Classpath location of the fictional issuer's mark. */ + private static final String MARK = "/brand/northwind-pay-mark.svg"; + + /** The fictional issuer's brand accent. */ + private static final DocumentColor ACCENT = DocumentColor.rgb(23, 92, 211); + + private ModernReceiptExample() { + } + + /** + * @return absolute path of the rendered PDF + * @throws Exception if rendering fails + */ + public static Path generate() throws Exception { + Path outputFile = ExampleOutputPaths.prepare("templates/receipt", "receipt-modern.pdf"); + ReceiptDocumentSpec spec = ExampleDataFactory.sampleReceipt(); + BrandTheme theme = BrandTheme.receiptModern(); + + // A symbol mark, not a wordmark: it renders inline beside the issuer's + // name, so it is sized to the name's cap height rather than to a + // wordmark's full width. + ModernReceipt.Options options = ModernReceipt.Options + .branded(SvgGlyph.fromResource(MARK), ACCENT) + .withLogoWidth(14) + .withLogoColor(ACCENT); + DocumentTemplate template = ModernReceipt.create(theme, options); + + float m = (float) ModernReceipt.RECOMMENDED_MARGIN; + try (DocumentSession document = GraphCompose.document(outputFile) + .pageSize(DocumentPageSize.A4) + .pageBackground(theme.palette().mainFill()) + .margin(m, m, m, m) + .create()) { + template.compose(document, spec); + document.buildPdf(); + } + return outputFile; + } + + /** + * @param args ignored + * @throws Exception if rendering fails + */ + public static void main(String[] args) throws Exception { + System.out.println("Generated: " + generate()); + } +} diff --git a/examples/src/main/java/com/demcha/examples/templates/schedule/WeeklyScheduleFileExample.java b/examples/src/main/java/com/demcha/examples/templates/schedule/WeeklyScheduleFileExample.java index 834f33377..e3eb621da 100644 --- a/examples/src/main/java/com/demcha/examples/templates/schedule/WeeklyScheduleFileExample.java +++ b/examples/src/main/java/com/demcha/examples/templates/schedule/WeeklyScheduleFileExample.java @@ -63,8 +63,8 @@ public final class WeeklyScheduleFileExample { private static final List WEEK = List.of( new DayPlan("Bank Holiday Monday / Clean Crushed Ice Machine & Area", 163, 35), new DayPlan("Pianist 18:30 / Clean Crushed Ice Machine & Area", 21, 63), - new DayPlan("TEAM DAY OUT / Motown GF / Pianist 18:30", 52, 74), - new DayPlan("Ex Hire Terrace Dinner 13PAX / Pianist 18:30 / MGM meeting 3:30pm", 35, 75), + new DayPlan("TEAM DAY OUT / Soul GF / Pianist 18:30", 52, 74), + new DayPlan("Ex Hire Terrace Dinner 13PAX / Pianist 18:30 / Partner meeting 3:30pm", 35, 75), new DayPlan("Pianist 19:00", 82, 78), new DayPlan("Ex Hire FF Lunch 44PAX / Pianist 19:00", 131, 149), new DayPlan("Masterclass 4PAX (2x2) / Line Check / Clean Cubed Ice Machine & Area", 130, 36) diff --git a/examples/src/main/resources/brand/northwind-pay-mark.svg b/examples/src/main/resources/brand/northwind-pay-mark.svg new file mode 100644 index 000000000..ba0d9a157 --- /dev/null +++ b/examples/src/main/resources/brand/northwind-pay-mark.svg @@ -0,0 +1,10 @@ + + + + diff --git a/examples/src/main/resources/consulting-invoice-logo.png b/examples/src/main/resources/consulting-invoice-logo.png new file mode 100644 index 000000000..e1a37e806 Binary files /dev/null and b/examples/src/main/resources/consulting-invoice-logo.png differ diff --git a/examples/src/main/resources/cv-portrait-placeholder.png b/examples/src/main/resources/cv-portrait-placeholder.png new file mode 100644 index 000000000..8c58ba4d8 Binary files /dev/null and b/examples/src/main/resources/cv-portrait-placeholder.png differ diff --git a/examples/src/main/resources/fonts/oswald/OFL.txt b/examples/src/main/resources/fonts/oswald/OFL.txt new file mode 100644 index 000000000..9d0219e98 --- /dev/null +++ b/examples/src/main/resources/fonts/oswald/OFL.txt @@ -0,0 +1,93 @@ +Copyright 2016 The Oswald Project Authors (https://github.com/googlefonts/OswaldFont) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +https://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/examples/src/main/resources/fonts/oswald/Oswald-Regular.ttf b/examples/src/main/resources/fonts/oswald/Oswald-Regular.ttf new file mode 100644 index 000000000..4a4373d1a Binary files /dev/null and b/examples/src/main/resources/fonts/oswald/Oswald-Regular.ttf differ diff --git a/examples/src/main/resources/fonts/oswald/Oswald-SemiBold.ttf b/examples/src/main/resources/fonts/oswald/Oswald-SemiBold.ttf new file mode 100644 index 000000000..26aa8f272 Binary files /dev/null and b/examples/src/main/resources/fonts/oswald/Oswald-SemiBold.ttf differ diff --git a/examples/src/test/java/com/demcha/examples/templates/cv/SidebarContactRowsFitTest.java b/examples/src/test/java/com/demcha/examples/templates/cv/SidebarContactRowsFitTest.java new file mode 100644 index 000000000..cb70a57c0 --- /dev/null +++ b/examples/src/test/java/com/demcha/examples/templates/cv/SidebarContactRowsFitTest.java @@ -0,0 +1,84 @@ +package com.demcha.examples.templates.cv; + +import com.demcha.compose.GraphCompose; +import com.demcha.compose.document.api.DocumentSession; +import com.demcha.compose.document.snapshot.LayoutNodeSnapshot; +import com.demcha.compose.document.templates.api.DocumentTemplate; +import com.demcha.compose.document.templates.cv.data.CvDocument; +import com.demcha.compose.document.templates.cv.presets.NavySidebar; +import com.demcha.compose.document.templates.cv.presets.ProfessionalSidebar; +import com.demcha.examples.support.NavySidebarSampleData; +import com.demcha.examples.support.ProfessionalSidebarSampleData; +import org.junit.jupiter.api.Test; + +import java.util.List; +import java.util.function.Supplier; +import java.util.stream.Collectors; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Holds the sidebar CV samples to the width of the column they are drawn in. + * + *

A contact channel in both of these presets is one paragraph — the mark + * and the value share a line — so a value wider than the sidebar's text + * column wraps, and the mark is left alone on the first line. Nothing in the + * promotion gates catches it: they measure the published template's own + * fixture, whose addresses fit, while the sample data here is written for + * the repository and can outgrow the column without a single test going + * red. That is exactly how it shipped once, spotted in the rendered preview + * rather than by a build.

+ * + *

So the guard is on the samples, and it is the shape of the row rather + * than the length of the string: a single-line channel is as tall as its + * mark — 8.8 to 11pt across the two designs — and a wrapped one is close to + * twice that. The bound sits between.

+ */ +class SidebarContactRowsFitTest { + + /** Comfortably above the tallest single-line row, well under a wrapped one. */ + private static final double SINGLE_LINE_LIMIT = 14.0; + + @Test + void professionalSidebarChannelsEachFitOneLine() { + assertChannelsFitOneLine("Professional Sidebar", + ProfessionalSidebar::create, ProfessionalSidebarSampleData.sample()); + } + + @Test + void navySidebarChannelsEachFitOneLine() { + assertChannelsFitOneLine("Navy Sidebar", + NavySidebar::create, NavySidebarSampleData.sample()); + } + + private static void assertChannelsFitOneLine(String preset, + Supplier> factory, + CvDocument doc) { + List channels; + try (DocumentSession session = GraphCompose.document().create()) { + factory.get().compose(session, doc); + channels = session.layoutSnapshot().nodes().stream() + .filter(node -> node.entityName() != null + && node.entityName().startsWith("Contact_")) + .toList(); + } + + assertThat(channels) + .describedAs("%s draws its contact channels as Contact_* nodes; finding none " + + "means the guard is looking at the wrong name, not that the sample " + + "is clean", preset) + .isNotEmpty(); + + List wrapped = channels.stream() + .filter(node -> node.placementHeight() > SINGLE_LINE_LIMIT) + .map(node -> node.entityName() + " (" + node.placementHeight() + "pt)") + .collect(Collectors.toList()); + + assertThat(wrapped) + .describedAs("%s: a contact value in the sample is wider than the sidebar " + + "column, so its row wrapped and left the mark alone on the first " + + "line. Shorten the value in the sample data — the column is not the " + + "thing to widen", preset) + .isEmpty(); + } +} diff --git a/knowledge/api/authoring.json b/knowledge/api/authoring.json index 10a85a758..9a84fbaa8 100644 --- a/knowledge/api/authoring.json +++ b/knowledge/api/authoring.json @@ -23,9 +23,9 @@ ], "counts": { "types": 237, - "methods": 2107, - "constants": 237, - "generated": 1126 + "methods": 2123, + "constants": 236, + "generated": 1129 }, "packages": [ { @@ -9221,6 +9221,38 @@ } ] }, + { + "kind": "method", + "name": "addItem", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "ListBuilder", + "params": [ + { + "type": "Consumer", + "name": "content" + } + ] + }, + { + "kind": "method", + "name": "addItem", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "ListBuilder", + "params": [ + { + "type": "Consumer", + "name": "content" + }, + { + "type": "Consumer", + "name": "body" + } + ] + }, { "kind": "method", "name": "markerFor", @@ -9267,6 +9299,20 @@ } ] }, + { + "kind": "method", + "name": "marker", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "ListBuilder", + "params": [ + { + "type": "Consumer", + "name": "marker" + } + ] + }, { "kind": "method", "name": "bullet", @@ -15106,6 +15152,20 @@ } ] }, + { + "kind": "method", + "name": "leadingGap", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "TimelineBuilder", + "params": [ + { + "type": "double", + "name": "gap" + } + ] + }, { "kind": "method", "name": "markerColumnWeight", @@ -15603,23 +15663,37 @@ }, { "kind": "method", - "name": "extent", + "name": "from", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "TimelineRailBuilder", + "params": [ + { + "type": "TimelineRailEnd", + "name": "start" + } + ] + }, + { + "kind": "method", + "name": "to", "static": false, "origin": "source", "typeParameters": null, "returns": "TimelineRailBuilder", "params": [ { - "type": "TimelineRailExtent", - "name": "extent" + "type": "TimelineRailEnd", + "name": "end" } ] } ] }, { - "name": "TimelineRailExtent", - "binaryName": "com.demcha.compose.document.dsl.TimelineRailExtent", + "name": "TimelineRailEnd", + "binaryName": "com.demcha.compose.document.dsl.TimelineRailEnd", "kind": "enum", "modifiers": [ "final" @@ -15628,24 +15702,17 @@ "members": [ { "kind": "constant", - "name": "MARKER_TO_MARKER", + "name": "ENTRY_BOUND", "static": true, "origin": "generated", - "type": "TimelineRailExtent" + "type": "TimelineRailEnd" }, { "kind": "constant", - "name": "ENTRY_BOUNDS", + "name": "MARKER", "static": true, "origin": "generated", - "type": "TimelineRailExtent" - }, - { - "kind": "constant", - "name": "TIMELINE_BOUNDS", - "static": true, - "origin": "generated", - "type": "TimelineRailExtent" + "type": "TimelineRailEnd" } ] }, @@ -18677,7 +18744,36 @@ "kind": "interface", "modifiers": [], "artifact": "graph-compose-core", - "members": [] + "members": [ + { + "kind": "method", + "name": "plainText", + "static": true, + "origin": "generated", + "typeParameters": null, + "returns": "String", + "params": [ + { + "type": "List", + "name": null + } + ] + }, + { + "kind": "method", + "name": "textRuns", + "static": true, + "origin": "generated", + "typeParameters": null, + "returns": "List", + "params": [ + { + "type": "List", + "name": null + } + ] + } + ] }, { "name": "InlineShapeRun", @@ -20332,6 +20428,10 @@ "type": "String", "name": null }, + { + "type": "List", + "name": null + }, { "type": "ListMarker", "name": null @@ -20342,6 +20442,69 @@ } ] }, + { + "kind": "constructor", + "name": "ListItem", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": null, + "params": [ + { + "type": "String", + "name": "label" + }, + { + "type": "ListMarker", + "name": "marker" + }, + { + "type": "List", + "name": "children" + } + ] + }, + { + "kind": "method", + "name": "ofRuns", + "static": true, + "origin": "source", + "typeParameters": null, + "returns": "ListItem", + "params": [ + { + "type": "List", + "name": "runs" + } + ] + }, + { + "kind": "method", + "name": "ofRuns", + "static": true, + "origin": "source", + "typeParameters": null, + "returns": "ListItem", + "params": [ + { + "type": "String", + "name": "label" + }, + { + "type": "List", + "name": "runs" + } + ] + }, + { + "kind": "method", + "name": "isRich", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "boolean", + "params": [] + }, { "kind": "method", "name": "of", @@ -20392,6 +20555,15 @@ "returns": "String", "params": [] }, + { + "kind": "method", + "name": "runs", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "List", + "params": [] + }, { "kind": "method", "name": "marker", @@ -20432,9 +20604,50 @@ { "type": "String", "name": null + }, + { + "type": "List", + "name": null + } + ] + }, + { + "kind": "constructor", + "name": "ListMarker", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": null, + "params": [ + { + "type": "String", + "name": "value" + } + ] + }, + { + "kind": "method", + "name": "ofRuns", + "static": true, + "origin": "source", + "typeParameters": null, + "returns": "ListMarker", + "params": [ + { + "type": "List", + "name": "runs" } ] }, + { + "kind": "method", + "name": "isRich", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "boolean", + "params": [] + }, { "kind": "method", "name": "bullet", @@ -20534,6 +20747,15 @@ "typeParameters": null, "returns": "String", "params": [] + }, + { + "kind": "method", + "name": "runs", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "List", + "params": [] } ] }, diff --git a/knowledge/api/authoring.md b/knowledge/api/authoring.md index e531a52b1..5efea33dc 100644 --- a/knowledge/api/authoring.md +++ b/knowledge/api/authoring.md @@ -28,7 +28,7 @@ note: "Generated from the pinned artifact's class files. Authoritative closed se **GraphCompose version:** 2.4.0-SNAPSHOT -Types: 237 · methods: 2107 · constants: 237 · compiler-generated members: 1126 +Types: 237 · methods: 2123 · constants: 236 · compiler-generated members: 1129 ## com.demcha.compose @@ -751,9 +751,12 @@ Types: 237 · methods: 2107 · constants: 237 · compiler-generated members: 112 - `ListBuilder items(List items)` - `ListBuilder addItem(String item)` - `ListBuilder addItem(String label, Consumer body)` +- `ListBuilder addItem(Consumer content)` +- `ListBuilder addItem(Consumer content, Consumer body)` - `ListBuilder markerFor(int depth, ListMarker marker)` - `ListBuilder marker(ListMarker marker)` - `ListBuilder marker(String marker)` +- `ListBuilder marker(Consumer marker)` - `ListBuilder bullet()` - `ListBuilder dash()` - `ListBuilder noMarker()` @@ -1128,6 +1131,7 @@ Types: 237 · methods: 2107 · constants: 237 · compiler-generated members: 112 - `TimelineBuilder markerOnRail()` - `TimelineBuilder gutter(double gutter)` - `TimelineBuilder markerGap(double gap)` +- `TimelineBuilder leadingGap(double gap)` - `TimelineBuilder markerColumnWeight(double weight)` - `TimelineBuilder axisWidth(double points)` - `TimelineBuilder leadingColumn(DocumentRowColumn column)` @@ -1164,10 +1168,11 @@ Types: 237 · methods: 2107 · constants: 237 · compiler-generated members: 112 ### TimelineRailBuilder (class) - `TimelineRailBuilder stroke(DocumentStroke stroke)` -- `TimelineRailBuilder extent(TimelineRailExtent extent)` +- `TimelineRailBuilder from(TimelineRailEnd start)` +- `TimelineRailBuilder to(TimelineRailEnd end)` -### TimelineRailExtent (enum) -- constants: `MARKER_TO_MARKER`, `ENTRY_BOUNDS`, `TIMELINE_BOUNDS` +### TimelineRailEnd (enum) +- constants: `ENTRY_BOUND`, `MARKER` ### TocBuilder (class) - `new TocBuilder()` @@ -1402,6 +1407,8 @@ Types: 237 · methods: 2107 · constants: 237 · compiler-generated members: 112 - `DocumentLinkTarget linkTarget()` ### InlineRun (interface) +- `String plainText(List)` +- `List textRuns(List)` ### InlineShapeRun (record) - `new InlineShapeRun(List, InlineImageAlignment, double, DocumentLinkTarget)` @@ -1498,16 +1505,24 @@ Types: 237 · methods: 2107 · constants: 237 · compiler-generated members: 112 - `boolean keepWithNext()` ### ListItem (record) -- `new ListItem(String, ListMarker, List)` +- `new ListItem(String, List, ListMarker, List)` +- `new ListItem(String label, ListMarker marker, List children)` +- `ListItem ofRuns(List runs)` +- `ListItem ofRuns(String label, List runs)` +- `boolean isRich()` - `ListItem of(String label)` - `ListItem of(String label, List children)` - `boolean isLeaf()` - `String label()` +- `List runs()` - `ListMarker marker()` - `List children()` ### ListMarker (record) -- `new ListMarker(String)` +- `new ListMarker(String, List)` +- `new ListMarker(String value)` +- `ListMarker ofRuns(List runs)` +- `boolean isRich()` - `ListMarker bullet()` - `ListMarker dash()` - `ListMarker none()` @@ -1517,6 +1532,7 @@ Types: 237 · methods: 2107 · constants: 237 · compiler-generated members: 112 - `boolean isVisible()` - `String prefix()` - `String value()` +- `List runs()` ### ListNode (record) - `new ListNode(String, List, List, ListMarker, DocumentTextStyle, TextAlign, double, double, String, boolean, DocumentInsets, DocumentInsets, boolean, double)` diff --git a/knowledge/api/templates.json b/knowledge/api/templates.json index 90f43ab37..ede1e699d 100644 --- a/knowledge/api/templates.json +++ b/knowledge/api/templates.json @@ -22,10 +22,10 @@ "graph-compose-testing:sources" ], "counts": { - "types": 161, - "methods": 954, - "constants": 118, - "generated": 403 + "types": 266, + "methods": 1475, + "constants": 201, + "generated": 807 }, "packages": [ { @@ -427,6 +427,101 @@ } ] }, + { + "name": "ContactUri", + "binaryName": "com.demcha.compose.document.templates.core.identity.ContactUri", + "kind": "class", + "modifiers": [ + "final" + ], + "artifact": "graph-compose-templates", + "members": [ + { + "kind": "method", + "name": "tel", + "static": true, + "origin": "source", + "typeParameters": null, + "returns": "String", + "params": [ + { + "type": "String", + "name": "phone" + } + ] + }, + { + "kind": "method", + "name": "telLink", + "static": true, + "origin": "source", + "typeParameters": null, + "returns": "DocumentLinkOptions", + "params": [ + { + "type": "String", + "name": "phone" + } + ] + }, + { + "kind": "method", + "name": "mailLink", + "static": true, + "origin": "source", + "typeParameters": null, + "returns": "DocumentLinkOptions", + "params": [ + { + "type": "String", + "name": "email" + } + ] + }, + { + "kind": "method", + "name": "web", + "static": true, + "origin": "source", + "typeParameters": null, + "returns": "String", + "params": [ + { + "type": "String", + "name": "website" + } + ] + }, + { + "kind": "method", + "name": "webLink", + "static": true, + "origin": "source", + "typeParameters": null, + "returns": "DocumentLinkOptions", + "params": [ + { + "type": "String", + "name": "website" + } + ] + }, + { + "kind": "method", + "name": "channelLink", + "static": true, + "origin": "source", + "typeParameters": null, + "returns": "DocumentLinkOptions", + "params": [ + { + "type": "String", + "name": "contact" + } + ] + } + ] + }, { "name": "Headline", "binaryName": "com.demcha.compose.document.templates.core.identity.Headline", @@ -1124,6 +1219,20 @@ } ] }, + { + "kind": "method", + "name": "fromFile", + "static": true, + "origin": "source", + "typeParameters": null, + "returns": "SvgGlyph", + "params": [ + { + "type": "Path", + "name": "file" + } + ] + }, { "kind": "method", "name": "outline", @@ -1739,6 +1848,15 @@ "returns": "BrandTheme", "params": [] }, + { + "kind": "method", + "name": "receiptModern", + "static": true, + "origin": "source", + "typeParameters": null, + "returns": "BrandTheme", + "params": [] + }, { "kind": "method", "name": "headlineStyle", @@ -2145,6 +2263,15 @@ "returns": "Palette", "params": [] }, + { + "kind": "method", + "name": "receiptModern", + "static": true, + "origin": "source", + "typeParameters": null, + "returns": "Palette", + "params": [] + }, { "kind": "method", "name": "ink", @@ -2473,6 +2600,15 @@ "returns": "Spacing", "params": [] }, + { + "kind": "method", + "name": "receiptModern", + "static": true, + "origin": "source", + "typeParameters": null, + "returns": "Spacing", + "params": [] + }, { "kind": "method", "name": "pageFlowSpacing", @@ -2813,6 +2949,15 @@ "returns": "Typography", "params": [] }, + { + "kind": "method", + "name": "receiptModern", + "static": true, + "origin": "source", + "typeParameters": null, + "returns": "Typography", + "params": [] + }, { "kind": "method", "name": "headlineFont", @@ -6753,146 +6898,128 @@ "type": "String", "name": null }, + { + "type": "String", + "name": null + }, + { + "type": "String", + "name": null + }, + { + "type": "String", + "name": null + }, { "type": "String", "name": null } ] }, - { - "kind": "method", - "name": "title", - "static": false, - "origin": "generated", - "typeParameters": null, - "returns": "String", - "params": [] - }, - { - "kind": "method", - "name": "subtitle", - "static": false, - "origin": "generated", - "typeParameters": null, - "returns": "String", - "params": [] - }, - { - "kind": "method", - "name": "date", - "static": false, - "origin": "generated", - "typeParameters": null, - "returns": "String", - "params": [] - }, - { - "kind": "method", - "name": "body", - "static": false, - "origin": "generated", - "typeParameters": null, - "returns": "String", - "params": [] - } - ] - }, - { - "name": "CvIdentity", - "binaryName": "com.demcha.compose.document.templates.cv.data.CvIdentity", - "kind": "record", - "modifiers": [ - "final" - ], - "artifact": "graph-compose-templates", - "members": [ { "kind": "constructor", - "name": "CvIdentity", + "name": "CvEntry", "static": false, - "origin": "generated", + "origin": "source", "typeParameters": null, "returns": null, "params": [ { - "type": "CvName", - "name": null + "type": "String", + "name": "title" }, { "type": "String", - "name": null + "name": "subtitle" }, { - "type": "Contact", - "name": null + "type": "String", + "name": "date" }, { - "type": "List", - "name": null + "type": "String", + "name": "body" + }, + { + "type": "String", + "name": "place" + }, + { + "type": "String", + "name": "icon" } ] }, { "kind": "constructor", - "name": "CvIdentity", + "name": "CvEntry", "static": false, "origin": "source", "typeParameters": null, "returns": null, "params": [ { - "type": "CvName", - "name": "name" + "type": "String", + "name": "title" }, { - "type": "Contact", - "name": "contact" + "type": "String", + "name": "subtitle" }, { - "type": "List", - "name": "links" + "type": "String", + "name": "date" + }, + { + "type": "String", + "name": "body" } ] }, { "kind": "method", - "name": "displayName", - "static": false, + "name": "builder", + "static": true, "origin": "source", "typeParameters": null, - "returns": "String", - "params": [] + "returns": "CvEntry.Builder", + "params": [ + { + "type": "String", + "name": "title" + } + ] }, { "kind": "method", - "name": "tagline", + "name": "title", "static": false, - "origin": "source", + "origin": "generated", "typeParameters": null, "returns": "String", "params": [] }, { "kind": "method", - "name": "builder", - "static": true, - "origin": "source", + "name": "subtitle", + "static": false, + "origin": "generated", "typeParameters": null, - "returns": "CvIdentity.Builder", + "returns": "String", "params": [] }, { "kind": "method", - "name": "name", + "name": "date", "static": false, "origin": "generated", "typeParameters": null, - "returns": "CvName", + "returns": "String", "params": [] }, { "kind": "method", - "name": "jobTitle", + "name": "body", "static": false, "origin": "generated", "typeParameters": null, @@ -6901,27 +7028,36 @@ }, { "kind": "method", - "name": "contact", + "name": "place", "static": false, "origin": "generated", "typeParameters": null, - "returns": "Contact", + "returns": "String", "params": [] }, { "kind": "method", - "name": "links", + "name": "icon", "static": false, "origin": "generated", "typeParameters": null, - "returns": "List", + "returns": "String", + "params": [] + }, + { + "kind": "method", + "name": "link", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "String", "params": [] } ] }, { - "name": "CvIdentity.Builder", - "binaryName": "com.demcha.compose.document.templates.cv.data.CvIdentity$Builder", + "name": "CvEntry.Builder", + "binaryName": "com.demcha.compose.document.templates.cv.data.CvEntry$Builder", "kind": "class", "modifiers": [ "final" @@ -6930,65 +7066,39 @@ "members": [ { "kind": "method", - "name": "name", - "static": false, - "origin": "source", - "typeParameters": null, - "returns": "CvIdentity.Builder", - "params": [ - { - "type": "CvName", - "name": "value" - } - ] - }, - { - "kind": "method", - "name": "name", + "name": "subtitle", "static": false, "origin": "source", "typeParameters": null, - "returns": "CvIdentity.Builder", + "returns": "CvEntry.Builder", "params": [ { "type": "String", - "name": "first" - }, - { - "type": "String", - "name": "last" + "name": "value" } ] }, { "kind": "method", - "name": "name", + "name": "date", "static": false, "origin": "source", "typeParameters": null, - "returns": "CvIdentity.Builder", + "returns": "CvEntry.Builder", "params": [ { "type": "String", - "name": "first" - }, - { - "type": "String", - "name": "middle" - }, - { - "type": "String", - "name": "last" + "name": "value" } ] }, { "kind": "method", - "name": "jobTitle", + "name": "body", "static": false, "origin": "source", "typeParameters": null, - "returns": "CvIdentity.Builder", + "returns": "CvEntry.Builder", "params": [ { "type": "String", @@ -6998,51 +7108,43 @@ }, { "kind": "method", - "name": "contact", + "name": "body", "static": false, "origin": "source", "typeParameters": null, - "returns": "CvIdentity.Builder", + "returns": "CvEntry.Builder", "params": [ { - "type": "Contact", - "name": "value" + "type": "List", + "name": "lines" } ] }, { "kind": "method", - "name": "contact", + "name": "place", "static": false, "origin": "source", "typeParameters": null, - "returns": "CvIdentity.Builder", + "returns": "CvEntry.Builder", "params": [ { "type": "String", - "name": "phone" - }, - { - "type": "String", - "name": "email" - }, - { - "type": "String", - "name": "address" + "name": "value" } ] }, { "kind": "method", - "name": "link", + "name": "icon", "static": false, "origin": "source", "typeParameters": null, - "returns": "CvIdentity.Builder", + "returns": "CvEntry.Builder", "params": [ { - "type": "Link", - "name": "link" + "type": "String", + "name": "value" } ] }, @@ -7052,15 +7154,11 @@ "static": false, "origin": "source", "typeParameters": null, - "returns": "CvIdentity.Builder", + "returns": "CvEntry.Builder", "params": [ { "type": "String", - "name": "label" - }, - { - "type": "String", - "name": "url" + "name": "value" } ] }, @@ -7070,14 +7168,14 @@ "static": false, "origin": "source", "typeParameters": null, - "returns": "CvIdentity", + "returns": "CvEntry", "params": [] } ] }, { - "name": "CvName", - "binaryName": "com.demcha.compose.document.templates.cv.data.CvName", + "name": "CvIdentity", + "binaryName": "com.demcha.compose.document.templates.cv.data.CvIdentity", "kind": "record", "modifiers": [ "final" @@ -7086,14 +7184,14 @@ "members": [ { "kind": "constructor", - "name": "CvName", + "name": "CvIdentity", "static": false, "origin": "generated", "typeParameters": null, "returns": null, "params": [ { - "type": "String", + "type": "CvName", "name": null }, { @@ -7101,41 +7199,70 @@ "name": null }, { - "type": "String", + "type": "Contact", + "name": null + }, + { + "type": "List", + "name": null + }, + { + "type": "Optional", "name": null } ] }, { - "kind": "method", - "name": "of", - "static": true, + "kind": "constructor", + "name": "CvIdentity", + "static": false, "origin": "source", "typeParameters": null, - "returns": "CvName", + "returns": null, "params": [ { - "type": "String", - "name": "first" + "type": "CvName", + "name": "name" }, { "type": "String", - "name": "last" + "name": "jobTitle" + }, + { + "type": "Contact", + "name": "contact" + }, + { + "type": "List", + "name": "links" } ] }, { - "kind": "method", - "name": "middleName", + "kind": "constructor", + "name": "CvIdentity", "static": false, "origin": "source", "typeParameters": null, - "returns": "Optional", - "params": [] + "returns": null, + "params": [ + { + "type": "CvName", + "name": "name" + }, + { + "type": "Contact", + "name": "contact" + }, + { + "type": "List", + "name": "links" + } + ] }, { "kind": "method", - "name": "full", + "name": "displayName", "static": false, "origin": "source", "typeParameters": null, @@ -7144,348 +7271,242 @@ }, { "kind": "method", - "name": "first", + "name": "tagline", "static": false, - "origin": "generated", + "origin": "source", "typeParameters": null, "returns": "String", "params": [] }, { "kind": "method", - "name": "middle", - "static": false, - "origin": "generated", + "name": "builder", + "static": true, + "origin": "source", "typeParameters": null, - "returns": "String", + "returns": "CvIdentity.Builder", "params": [] }, { "kind": "method", - "name": "last", + "name": "name", "static": false, "origin": "generated", "typeParameters": null, - "returns": "String", + "returns": "CvName", "params": [] - } - ] - }, - { - "name": "CvRow", - "binaryName": "com.demcha.compose.document.templates.cv.data.CvRow", - "kind": "record", - "modifiers": [ - "final" - ], - "artifact": "graph-compose-templates", - "members": [ + }, { - "kind": "constructor", - "name": "CvRow", + "kind": "method", + "name": "jobTitle", "static": false, "origin": "generated", "typeParameters": null, - "returns": null, - "params": [ - { - "type": "String", - "name": null - }, - { - "type": "String", - "name": null - } - ] + "returns": "String", + "params": [] }, { "kind": "method", - "name": "label", + "name": "contact", "static": false, "origin": "generated", "typeParameters": null, - "returns": "String", + "returns": "Contact", "params": [] }, { "kind": "method", - "name": "body", + "name": "links", "static": false, "origin": "generated", "typeParameters": null, - "returns": "String", + "returns": "List", "params": [] - } - ] - }, - { - "name": "CvSection", - "binaryName": "com.demcha.compose.document.templates.cv.data.CvSection", - "kind": "interface", - "modifiers": [], - "artifact": "graph-compose-templates", - "members": [ + }, { "kind": "method", - "name": "title", + "name": "portrait", "static": false, "origin": "generated", "typeParameters": null, - "returns": "String", + "returns": "Optional", "params": [] } ] }, { - "name": "CvSkill", - "binaryName": "com.demcha.compose.document.templates.cv.data.CvSkill", - "kind": "record", + "name": "CvIdentity.Builder", + "binaryName": "com.demcha.compose.document.templates.cv.data.CvIdentity$Builder", + "kind": "class", "modifiers": [ "final" ], "artifact": "graph-compose-templates", "members": [ { - "kind": "constructor", - "name": "CvSkill", + "kind": "method", + "name": "name", "static": false, - "origin": "generated", + "origin": "source", "typeParameters": null, - "returns": null, + "returns": "CvIdentity.Builder", "params": [ { - "type": "String", - "name": null - }, - { - "type": "OptionalDouble", - "name": null + "type": "CvName", + "name": "value" } ] }, { "kind": "method", - "name": "of", - "static": true, + "name": "name", + "static": false, "origin": "source", "typeParameters": null, - "returns": "CvSkill", + "returns": "CvIdentity.Builder", "params": [ { "type": "String", - "name": "name" + "name": "first" + }, + { + "type": "String", + "name": "last" } ] }, { "kind": "method", - "name": "of", - "static": true, + "name": "name", + "static": false, "origin": "source", "typeParameters": null, - "returns": "CvSkill", + "returns": "CvIdentity.Builder", "params": [ { "type": "String", - "name": "name" + "name": "first" }, { - "type": "double", - "name": "level" + "type": "String", + "name": "middle" + }, + { + "type": "String", + "name": "last" } ] }, { "kind": "method", - "name": "name", - "static": false, - "origin": "generated", - "typeParameters": null, - "returns": "String", - "params": [] - }, - { - "kind": "method", - "name": "level", - "static": false, - "origin": "generated", - "typeParameters": null, - "returns": "OptionalDouble", - "params": [] - } - ] - }, - { - "name": "EntriesSection", - "binaryName": "com.demcha.compose.document.templates.cv.data.EntriesSection", - "kind": "record", - "modifiers": [ - "final" - ], - "artifact": "graph-compose-templates", - "members": [ - { - "kind": "constructor", - "name": "EntriesSection", + "name": "jobTitle", "static": false, - "origin": "generated", + "origin": "source", "typeParameters": null, - "returns": null, + "returns": "CvIdentity.Builder", "params": [ { "type": "String", - "name": null - }, - { - "type": "List", - "name": null + "name": "value" } ] }, { "kind": "method", - "name": "builder", - "static": true, + "name": "contact", + "static": false, "origin": "source", "typeParameters": null, - "returns": "EntriesSection.Builder", + "returns": "CvIdentity.Builder", "params": [ { - "type": "String", - "name": "title" + "type": "Contact", + "name": "value" } ] }, { "kind": "method", - "name": "title", - "static": false, - "origin": "generated", - "typeParameters": null, - "returns": "String", - "params": [] - }, - { - "kind": "method", - "name": "entries", - "static": false, - "origin": "generated", - "typeParameters": null, - "returns": "List", - "params": [] - } - ] - }, - { - "name": "EntriesSection.Builder", - "binaryName": "com.demcha.compose.document.templates.cv.data.EntriesSection$Builder", - "kind": "class", - "modifiers": [ - "final" - ], - "artifact": "graph-compose-templates", - "members": [ - { - "kind": "method", - "name": "entry", + "name": "contact", "static": false, "origin": "source", "typeParameters": null, - "returns": "EntriesSection.Builder", + "returns": "CvIdentity.Builder", "params": [ { "type": "String", - "name": "title" - }, - { - "type": "String", - "name": "subtitle" + "name": "phone" }, { "type": "String", - "name": "date" + "name": "email" }, { "type": "String", - "name": "body" + "name": "address" } ] }, { "kind": "method", - "name": "entry", + "name": "link", "static": false, "origin": "source", "typeParameters": null, - "returns": "EntriesSection.Builder", + "returns": "CvIdentity.Builder", "params": [ { - "type": "CvEntry", - "name": "entry" + "type": "Link", + "name": "link" } ] }, { "kind": "method", - "name": "build", + "name": "link", "static": false, "origin": "source", "typeParameters": null, - "returns": "EntriesSection", - "params": [] - } - ] - }, - { - "name": "ParagraphSection", - "binaryName": "com.demcha.compose.document.templates.cv.data.ParagraphSection", - "kind": "record", - "modifiers": [ - "final" - ], - "artifact": "graph-compose-templates", - "members": [ - { - "kind": "constructor", - "name": "ParagraphSection", - "static": false, - "origin": "generated", - "typeParameters": null, - "returns": null, + "returns": "CvIdentity.Builder", "params": [ { "type": "String", - "name": null + "name": "label" }, { "type": "String", - "name": null + "name": "url" } ] }, { "kind": "method", - "name": "title", + "name": "portrait", "static": false, - "origin": "generated", + "origin": "source", "typeParameters": null, - "returns": "String", - "params": [] + "returns": "CvIdentity.Builder", + "params": [ + { + "type": "DocumentImageData", + "name": "value" + } + ] }, { "kind": "method", - "name": "body", + "name": "build", "static": false, - "origin": "generated", + "origin": "source", "typeParameters": null, - "returns": "String", + "returns": "CvIdentity", "params": [] } ] }, { - "name": "RowsSection", - "binaryName": "com.demcha.compose.document.templates.cv.data.RowsSection", + "name": "CvName", + "binaryName": "com.demcha.compose.document.templates.cv.data.CvName", "kind": "record", "modifiers": [ "final" @@ -7494,7 +7515,7 @@ "members": [ { "kind": "constructor", - "name": "RowsSection", + "name": "CvName", "static": false, "origin": "generated", "typeParameters": null, @@ -7505,36 +7526,54 @@ "name": null }, { - "type": "List", + "type": "String", "name": null }, { - "type": "RowStyle", + "type": "String", "name": null } ] }, { "kind": "method", - "name": "builder", + "name": "of", "static": true, "origin": "source", "typeParameters": null, - "returns": "RowsSection.Builder", + "returns": "CvName", "params": [ { "type": "String", - "name": "title" + "name": "first" }, { - "type": "RowStyle", - "name": "style" + "type": "String", + "name": "last" } ] }, { "kind": "method", - "name": "title", + "name": "middleName", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "Optional", + "params": [] + }, + { + "kind": "method", + "name": "full", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "String", + "params": [] + }, + { + "kind": "method", + "name": "first", "static": false, "origin": "generated", "typeParameters": null, @@ -7543,111 +7582,92 @@ }, { "kind": "method", - "name": "rows", + "name": "middle", "static": false, "origin": "generated", "typeParameters": null, - "returns": "List", + "returns": "String", "params": [] }, { "kind": "method", - "name": "style", + "name": "last", "static": false, "origin": "generated", "typeParameters": null, - "returns": "RowStyle", + "returns": "String", "params": [] } ] }, { - "name": "RowsSection.Builder", - "binaryName": "com.demcha.compose.document.templates.cv.data.RowsSection$Builder", - "kind": "class", + "name": "CvRow", + "binaryName": "com.demcha.compose.document.templates.cv.data.CvRow", + "kind": "record", "modifiers": [ "final" ], "artifact": "graph-compose-templates", "members": [ { - "kind": "method", - "name": "row", + "kind": "constructor", + "name": "CvRow", "static": false, - "origin": "source", + "origin": "generated", "typeParameters": null, - "returns": "RowsSection.Builder", + "returns": null, "params": [ { "type": "String", - "name": "label" + "name": null }, { "type": "String", - "name": "body" + "name": null } ] }, { "kind": "method", - "name": "row", + "name": "label", "static": false, - "origin": "source", + "origin": "generated", "typeParameters": null, - "returns": "RowsSection.Builder", - "params": [ - { - "type": "CvRow", - "name": "row" - } - ] + "returns": "String", + "params": [] }, { "kind": "method", - "name": "build", + "name": "body", "static": false, - "origin": "source", + "origin": "generated", "typeParameters": null, - "returns": "RowsSection", + "returns": "String", "params": [] } ] }, { - "name": "RowStyle", - "binaryName": "com.demcha.compose.document.templates.cv.data.RowStyle", - "kind": "enum", - "modifiers": [ - "final" - ], + "name": "CvSection", + "binaryName": "com.demcha.compose.document.templates.cv.data.CvSection", + "kind": "interface", + "modifiers": [], "artifact": "graph-compose-templates", "members": [ { - "kind": "constant", - "name": "PLAIN", - "static": true, - "origin": "generated", - "type": "RowStyle" - }, - { - "kind": "constant", - "name": "BULLETED", - "static": true, - "origin": "generated", - "type": "RowStyle" - }, - { - "kind": "constant", - "name": "BULLETED_STACKED", - "static": true, + "kind": "method", + "name": "title", + "static": false, "origin": "generated", - "type": "RowStyle" + "typeParameters": null, + "returns": "String", + "params": [] } ] }, { - "name": "SkillGroup", - "binaryName": "com.demcha.compose.document.templates.cv.data.SkillGroup", + "name": "CvSkill", + "binaryName": "com.demcha.compose.document.templates.cv.data.CvSkill", "kind": "record", "modifiers": [ "final" @@ -7656,7 +7676,7 @@ "members": [ { "kind": "constructor", - "name": "SkillGroup", + "name": "CvSkill", "static": false, "origin": "generated", "typeParameters": null, @@ -7667,88 +7687,119 @@ "name": null }, { - "type": "List", + "type": "OptionalDouble", + "name": null + }, + { + "type": "String", "name": null } ] }, + { + "kind": "constructor", + "name": "CvSkill", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": null, + "params": [ + { + "type": "String", + "name": "name" + }, + { + "type": "OptionalDouble", + "name": "level" + } + ] + }, { "kind": "method", "name": "of", "static": true, "origin": "source", "typeParameters": null, - "returns": "SkillGroup", + "returns": "CvSkill", "params": [ { "type": "String", - "name": "category" - }, - { - "type": "String...", - "name": "skills" + "name": "name" } ] }, { "kind": "method", - "name": "ofNames", + "name": "of", "static": true, "origin": "source", "typeParameters": null, - "returns": "SkillGroup", + "returns": "CvSkill", "params": [ { "type": "String", - "name": "category" + "name": "name" }, { - "type": "List", - "name": "skills" + "type": "double", + "name": "level" } ] }, { "kind": "method", - "name": "skills", - "static": false, + "name": "of", + "static": true, "origin": "source", "typeParameters": null, - "returns": "List", - "params": [] + "returns": "CvSkill", + "params": [ + { + "type": "String", + "name": "name" + }, + { + "type": "double", + "name": "level" + }, + { + "type": "String", + "name": "note" + } + ] }, { "kind": "method", - "name": "skillsInline", + "name": "name", "static": false, - "origin": "source", + "origin": "generated", "typeParameters": null, "returns": "String", "params": [] }, { "kind": "method", - "name": "category", + "name": "level", "static": false, "origin": "generated", "typeParameters": null, - "returns": "String", + "returns": "OptionalDouble", "params": [] }, { "kind": "method", - "name": "entries", + "name": "note", "static": false, "origin": "generated", "typeParameters": null, - "returns": "List", + "returns": "String", "params": [] } ] }, { - "name": "SkillsSection", - "binaryName": "com.demcha.compose.document.templates.cv.data.SkillsSection", + "name": "EntriesSection", + "binaryName": "com.demcha.compose.document.templates.cv.data.EntriesSection", "kind": "record", "modifiers": [ "final" @@ -7757,7 +7808,7 @@ "members": [ { "kind": "constructor", - "name": "SkillsSection", + "name": "EntriesSection", "static": false, "origin": "generated", "typeParameters": null, @@ -7768,7 +7819,7 @@ "name": null }, { - "type": "List", + "type": "List", "name": null } ] @@ -7779,29 +7830,11 @@ "static": true, "origin": "source", "typeParameters": null, - "returns": "SkillsSection.Builder", - "params": [ - { - "type": "String", - "name": "title" - } - ] - }, - { - "kind": "method", - "name": "of", - "static": true, - "origin": "source", - "typeParameters": null, - "returns": "SkillsSection", + "returns": "EntriesSection.Builder", "params": [ { "type": "String", "name": "title" - }, - { - "type": "SkillGroup...", - "name": "groups" } ] }, @@ -7816,18 +7849,18 @@ }, { "kind": "method", - "name": "groups", + "name": "entries", "static": false, "origin": "generated", "typeParameters": null, - "returns": "List", + "returns": "List", "params": [] } ] }, { - "name": "SkillsSection.Builder", - "binaryName": "com.demcha.compose.document.templates.cv.data.SkillsSection$Builder", + "name": "EntriesSection.Builder", + "binaryName": "com.demcha.compose.document.templates.cv.data.EntriesSection$Builder", "kind": "class", "modifiers": [ "final" @@ -7836,69 +7869,41 @@ "members": [ { "kind": "method", - "name": "group", - "static": false, - "origin": "source", - "typeParameters": null, - "returns": "SkillsSection.Builder", - "params": [ - { - "type": "SkillGroup", - "name": "group" - } - ] - }, - { - "kind": "method", - "name": "group", + "name": "entry", "static": false, "origin": "source", "typeParameters": null, - "returns": "SkillsSection.Builder", + "returns": "EntriesSection.Builder", "params": [ { "type": "String", - "name": "category" + "name": "title" }, { - "type": "List", - "name": "skills" - } - ] - }, - { - "kind": "method", - "name": "group", - "static": false, - "origin": "source", - "typeParameters": null, - "returns": "SkillsSection.Builder", - "params": [ + "type": "String", + "name": "subtitle" + }, { "type": "String", - "name": "category" + "name": "date" }, { - "type": "String...", - "name": "skills" + "type": "String", + "name": "body" } ] }, { "kind": "method", - "name": "leveledGroup", + "name": "entry", "static": false, "origin": "source", "typeParameters": null, - "returns": "SkillsSection.Builder", + "returns": "EntriesSection.Builder", "params": [ { - "type": "String", - "name": "category" - }, - { - "type": "List", - "name": "entries" + "type": "CvEntry", + "name": "entry" } ] }, @@ -7908,162 +7913,192 @@ "static": false, "origin": "source", "typeParameters": null, - "returns": "SkillsSection", + "returns": "EntriesSection", "params": [] } ] }, { - "name": "Slot", - "binaryName": "com.demcha.compose.document.templates.cv.data.Slot", - "kind": "enum", + "name": "ParagraphSection", + "binaryName": "com.demcha.compose.document.templates.cv.data.ParagraphSection", + "kind": "record", "modifiers": [ "final" ], "artifact": "graph-compose-templates", "members": [ { - "kind": "constant", - "name": "MAIN", - "static": true, + "kind": "constructor", + "name": "ParagraphSection", + "static": false, "origin": "generated", - "type": "Slot" + "typeParameters": null, + "returns": null, + "params": [ + { + "type": "String", + "name": null + }, + { + "type": "String", + "name": null + } + ] }, { - "kind": "constant", - "name": "SIDEBAR", - "static": true, + "kind": "method", + "name": "title", + "static": false, "origin": "generated", - "type": "Slot" + "typeParameters": null, + "returns": "String", + "params": [] }, { - "kind": "constant", - "name": "FOOTER", - "static": true, + "kind": "method", + "name": "body", + "static": false, "origin": "generated", - "type": "Slot" + "typeParameters": null, + "returns": "String", + "params": [] } ] - } - ] - }, - { - "name": "com.demcha.compose.document.templates.cv.presets", - "types": [ + }, { - "name": "BlueBanner", - "binaryName": "com.demcha.compose.document.templates.cv.presets.BlueBanner", - "kind": "class", + "name": "RowsSection", + "binaryName": "com.demcha.compose.document.templates.cv.data.RowsSection", + "kind": "record", "modifiers": [ "final" ], "artifact": "graph-compose-templates", "members": [ { - "kind": "constant", - "name": "ID", - "static": true, + "kind": "constructor", + "name": "RowsSection", + "static": false, "origin": "generated", - "type": "String" + "typeParameters": null, + "returns": null, + "params": [ + { + "type": "String", + "name": null + }, + { + "type": "List", + "name": null + }, + { + "type": "RowStyle", + "name": null + } + ] }, { - "kind": "constant", - "name": "DISPLAY_NAME", + "kind": "method", + "name": "builder", "static": true, - "origin": "generated", - "type": "String" + "origin": "source", + "typeParameters": null, + "returns": "RowsSection.Builder", + "params": [ + { + "type": "String", + "name": "title" + }, + { + "type": "RowStyle", + "name": "style" + } + ] }, { - "kind": "constant", - "name": "RECOMMENDED_MARGIN", - "static": true, + "kind": "method", + "name": "title", + "static": false, "origin": "generated", - "type": "double" + "typeParameters": null, + "returns": "String", + "params": [] }, { "kind": "method", - "name": "create", - "static": true, - "origin": "source", + "name": "rows", + "static": false, + "origin": "generated", "typeParameters": null, - "returns": "DocumentTemplate", + "returns": "List", "params": [] }, { "kind": "method", - "name": "create", - "static": true, - "origin": "source", + "name": "style", + "static": false, + "origin": "generated", "typeParameters": null, - "returns": "DocumentTemplate", - "params": [ - { - "type": "BrandTheme", - "name": "theme" - } - ] + "returns": "RowStyle", + "params": [] } ] }, { - "name": "BoxedSections", - "binaryName": "com.demcha.compose.document.templates.cv.presets.BoxedSections", + "name": "RowsSection.Builder", + "binaryName": "com.demcha.compose.document.templates.cv.data.RowsSection$Builder", "kind": "class", "modifiers": [ "final" ], "artifact": "graph-compose-templates", "members": [ - { - "kind": "constant", - "name": "ID", - "static": true, - "origin": "generated", - "type": "String" - }, - { - "kind": "constant", - "name": "DISPLAY_NAME", - "static": true, - "origin": "generated", - "type": "String" - }, - { - "kind": "constant", - "name": "RECOMMENDED_MARGIN", - "static": true, - "origin": "generated", - "type": "double" - }, { "kind": "method", - "name": "create", - "static": true, + "name": "row", + "static": false, "origin": "source", "typeParameters": null, - "returns": "DocumentTemplate", - "params": [] + "returns": "RowsSection.Builder", + "params": [ + { + "type": "String", + "name": "label" + }, + { + "type": "String", + "name": "body" + } + ] }, { "kind": "method", - "name": "create", - "static": true, + "name": "row", + "static": false, "origin": "source", "typeParameters": null, - "returns": "DocumentTemplate", + "returns": "RowsSection.Builder", "params": [ { - "type": "BrandTheme", - "name": "theme" + "type": "CvRow", + "name": "row" } ] + }, + { + "kind": "method", + "name": "build", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "RowsSection", + "params": [] } ] }, { - "name": "CenteredHeadline", - "binaryName": "com.demcha.compose.document.templates.cv.presets.CenteredHeadline", - "kind": "class", + "name": "RowStyle", + "binaryName": "com.demcha.compose.document.templates.cv.data.RowStyle", + "kind": "enum", "modifiers": [ "final" ], @@ -8071,163 +8106,210 @@ "members": [ { "kind": "constant", - "name": "ID", + "name": "PLAIN", "static": true, "origin": "generated", - "type": "String" + "type": "RowStyle" }, { "kind": "constant", - "name": "DISPLAY_NAME", + "name": "BULLETED", "static": true, "origin": "generated", - "type": "String" + "type": "RowStyle" }, { "kind": "constant", - "name": "RECOMMENDED_MARGIN", + "name": "BULLETED_STACKED", "static": true, "origin": "generated", - "type": "double" - }, - { - "kind": "method", - "name": "create", - "static": true, - "origin": "source", - "typeParameters": null, - "returns": "DocumentTemplate", - "params": [] - }, - { - "kind": "method", - "name": "create", - "static": true, - "origin": "source", - "typeParameters": null, - "returns": "DocumentTemplate", - "params": [ - { - "type": "BrandTheme", - "name": "theme" - } - ] + "type": "RowStyle" } ] }, { - "name": "ClassicSerif", - "binaryName": "com.demcha.compose.document.templates.cv.presets.ClassicSerif", - "kind": "class", + "name": "SkillGroup", + "binaryName": "com.demcha.compose.document.templates.cv.data.SkillGroup", + "kind": "record", "modifiers": [ "final" ], "artifact": "graph-compose-templates", "members": [ { - "kind": "constant", - "name": "ID", - "static": true, - "origin": "generated", - "type": "String" - }, - { - "kind": "constant", - "name": "DISPLAY_NAME", - "static": true, - "origin": "generated", - "type": "String" - }, - { - "kind": "constant", - "name": "RECOMMENDED_MARGIN", - "static": true, + "kind": "constructor", + "name": "SkillGroup", + "static": false, "origin": "generated", - "type": "double" + "typeParameters": null, + "returns": null, + "params": [ + { + "type": "String", + "name": null + }, + { + "type": "List", + "name": null + } + ] }, { "kind": "method", - "name": "create", + "name": "of", "static": true, "origin": "source", "typeParameters": null, - "returns": "DocumentTemplate", - "params": [] + "returns": "SkillGroup", + "params": [ + { + "type": "String", + "name": "category" + }, + { + "type": "String...", + "name": "skills" + } + ] }, { "kind": "method", - "name": "create", + "name": "ofNames", "static": true, "origin": "source", "typeParameters": null, - "returns": "DocumentTemplate", + "returns": "SkillGroup", "params": [ { - "type": "BrandTheme", - "name": "theme" + "type": "String", + "name": "category" + }, + { + "type": "List", + "name": "skills" } ] - } - ] - }, - { - "name": "CompactMono", - "binaryName": "com.demcha.compose.document.templates.cv.presets.CompactMono", - "kind": "class", - "modifiers": [ - "final" - ], - "artifact": "graph-compose-templates", - "members": [ + }, { - "kind": "constant", - "name": "ID", - "static": true, - "origin": "generated", - "type": "String" + "kind": "method", + "name": "skills", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "List", + "params": [] }, { - "kind": "constant", - "name": "DISPLAY_NAME", - "static": true, + "kind": "method", + "name": "skillsInline", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "String", + "params": [] + }, + { + "kind": "method", + "name": "category", + "static": false, "origin": "generated", - "type": "String" + "typeParameters": null, + "returns": "String", + "params": [] }, { - "kind": "constant", - "name": "RECOMMENDED_MARGIN", - "static": true, + "kind": "method", + "name": "entries", + "static": false, "origin": "generated", - "type": "double" + "typeParameters": null, + "returns": "List", + "params": [] + } + ] + }, + { + "name": "SkillsSection", + "binaryName": "com.demcha.compose.document.templates.cv.data.SkillsSection", + "kind": "record", + "modifiers": [ + "final" + ], + "artifact": "graph-compose-templates", + "members": [ + { + "kind": "constructor", + "name": "SkillsSection", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": null, + "params": [ + { + "type": "String", + "name": null + }, + { + "type": "List", + "name": null + } + ] }, { "kind": "method", - "name": "create", + "name": "builder", "static": true, "origin": "source", "typeParameters": null, - "returns": "DocumentTemplate", - "params": [] + "returns": "SkillsSection.Builder", + "params": [ + { + "type": "String", + "name": "title" + } + ] }, { "kind": "method", - "name": "create", + "name": "of", "static": true, "origin": "source", "typeParameters": null, - "returns": "DocumentTemplate", + "returns": "SkillsSection", "params": [ { - "type": "BrandTheme", - "name": "theme" + "type": "String", + "name": "title" + }, + { + "type": "SkillGroup...", + "name": "groups" } ] + }, + { + "kind": "method", + "name": "title", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "String", + "params": [] + }, + { + "kind": "method", + "name": "groups", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "List", + "params": [] } ] }, { - "name": "EditorialBlue", - "binaryName": "com.demcha.compose.document.templates.cv.presets.EditorialBlue", + "name": "SkillsSection.Builder", + "binaryName": "com.demcha.compose.document.templates.cv.data.SkillsSection$Builder", "kind": "class", "modifiers": [ "final" @@ -8235,54 +8317,124 @@ "artifact": "graph-compose-templates", "members": [ { - "kind": "constant", - "name": "ID", - "static": true, - "origin": "generated", - "type": "String" - }, - { - "kind": "constant", - "name": "DISPLAY_NAME", - "static": true, - "origin": "generated", - "type": "String" + "kind": "method", + "name": "group", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "SkillsSection.Builder", + "params": [ + { + "type": "SkillGroup", + "name": "group" + } + ] }, { - "kind": "constant", - "name": "RECOMMENDED_MARGIN", - "static": true, - "origin": "generated", - "type": "double" + "kind": "method", + "name": "group", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "SkillsSection.Builder", + "params": [ + { + "type": "String", + "name": "category" + }, + { + "type": "List", + "name": "skills" + } + ] }, { "kind": "method", - "name": "create", - "static": true, + "name": "group", + "static": false, "origin": "source", "typeParameters": null, - "returns": "DocumentTemplate", - "params": [] + "returns": "SkillsSection.Builder", + "params": [ + { + "type": "String", + "name": "category" + }, + { + "type": "String...", + "name": "skills" + } + ] }, { "kind": "method", - "name": "create", - "static": true, + "name": "leveledGroup", + "static": false, "origin": "source", "typeParameters": null, - "returns": "DocumentTemplate", + "returns": "SkillsSection.Builder", "params": [ { - "type": "BrandTheme", - "name": "theme" + "type": "String", + "name": "category" + }, + { + "type": "List", + "name": "entries" } ] + }, + { + "kind": "method", + "name": "build", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "SkillsSection", + "params": [] } ] }, { - "name": "EngineeringResume", - "binaryName": "com.demcha.compose.document.templates.cv.presets.EngineeringResume", + "name": "Slot", + "binaryName": "com.demcha.compose.document.templates.cv.data.Slot", + "kind": "enum", + "modifiers": [ + "final" + ], + "artifact": "graph-compose-templates", + "members": [ + { + "kind": "constant", + "name": "MAIN", + "static": true, + "origin": "generated", + "type": "Slot" + }, + { + "kind": "constant", + "name": "SIDEBAR", + "static": true, + "origin": "generated", + "type": "Slot" + }, + { + "kind": "constant", + "name": "FOOTER", + "static": true, + "origin": "generated", + "type": "Slot" + } + ] + } + ] + }, + { + "name": "com.demcha.compose.document.templates.cv.presets", + "types": [ + { + "name": "BlueBanner", + "binaryName": "com.demcha.compose.document.templates.cv.presets.BlueBanner", "kind": "class", "modifiers": [ "final" @@ -8336,8 +8488,8 @@ ] }, { - "name": "Executive", - "binaryName": "com.demcha.compose.document.templates.cv.presets.Executive", + "name": "BoxedSections", + "binaryName": "com.demcha.compose.document.templates.cv.presets.BoxedSections", "kind": "class", "modifiers": [ "final" @@ -8391,8 +8543,8 @@ ] }, { - "name": "MinimalUnderlined", - "binaryName": "com.demcha.compose.document.templates.cv.presets.MinimalUnderlined", + "name": "CenteredHeadline", + "binaryName": "com.demcha.compose.document.templates.cv.presets.CenteredHeadline", "kind": "class", "modifiers": [ "final" @@ -8446,8 +8598,8 @@ ] }, { - "name": "MintEditorial", - "binaryName": "com.demcha.compose.document.templates.cv.presets.MintEditorial", + "name": "CharcoalGold", + "binaryName": "com.demcha.compose.document.templates.cv.presets.CharcoalGold", "kind": "class", "modifiers": [ "final" @@ -8483,6 +8635,38 @@ "typeParameters": null, "returns": "DocumentTemplate", "params": [] + } + ] + }, + { + "name": "ClassicSerif", + "binaryName": "com.demcha.compose.document.templates.cv.presets.ClassicSerif", + "kind": "class", + "modifiers": [ + "final" + ], + "artifact": "graph-compose-templates", + "members": [ + { + "kind": "constant", + "name": "ID", + "static": true, + "origin": "generated", + "type": "String" + }, + { + "kind": "constant", + "name": "DISPLAY_NAME", + "static": true, + "origin": "generated", + "type": "String" + }, + { + "kind": "constant", + "name": "RECOMMENDED_MARGIN", + "static": true, + "origin": "generated", + "type": "double" }, { "kind": "method", @@ -8491,26 +8675,7 @@ "origin": "source", "typeParameters": null, "returns": "DocumentTemplate", - "params": [ - { - "type": "BrandTheme", - "name": "theme" - } - ] - }, - { - "kind": "method", - "name": "create", - "static": true, - "origin": "source", - "typeParameters": null, - "returns": "DocumentTemplate", - "params": [ - { - "type": "MintEditorial.Options", - "name": "options" - } - ] + "params": [] }, { "kind": "method", @@ -8523,109 +8688,69 @@ { "type": "BrandTheme", "name": "theme" - }, - { - "type": "MintEditorial.Options", - "name": "options" } ] } ] }, { - "name": "MintEditorial.Options", - "binaryName": "com.demcha.compose.document.templates.cv.presets.MintEditorial$Options", - "kind": "record", + "name": "CompactMono", + "binaryName": "com.demcha.compose.document.templates.cv.presets.CompactMono", + "kind": "class", "modifiers": [ "final" ], "artifact": "graph-compose-templates", "members": [ { - "kind": "constructor", - "name": "Options", - "static": false, - "origin": "generated", - "typeParameters": null, - "returns": null, - "params": [ - { - "type": "DocumentColor", - "name": null - }, - { - "type": "DocumentColor", - "name": null - }, - { - "type": "DocumentColor", - "name": null - }, - { - "type": "DocumentColor", - "name": null - } - ] - }, - { - "kind": "method", - "name": "defaults", + "kind": "constant", + "name": "ID", "static": true, - "origin": "source", - "typeParameters": null, - "returns": "MintEditorial.Options", - "params": [] + "origin": "generated", + "type": "String" }, { - "kind": "method", - "name": "builder", + "kind": "constant", + "name": "DISPLAY_NAME", "static": true, - "origin": "source", - "typeParameters": null, - "returns": "MintEditorial.Options.Builder", - "params": [] - }, - { - "kind": "method", - "name": "accentColor", - "static": false, "origin": "generated", - "typeParameters": null, - "returns": "DocumentColor", - "params": [] + "type": "String" }, { - "kind": "method", - "name": "ruleColor", - "static": false, + "kind": "constant", + "name": "RECOMMENDED_MARGIN", + "static": true, "origin": "generated", - "typeParameters": null, - "returns": "DocumentColor", - "params": [] + "type": "double" }, { "kind": "method", - "name": "nameColor", - "static": false, - "origin": "generated", + "name": "create", + "static": true, + "origin": "source", "typeParameters": null, - "returns": "DocumentColor", + "returns": "DocumentTemplate", "params": [] }, { "kind": "method", - "name": "headerBandColor", - "static": false, - "origin": "generated", + "name": "create", + "static": true, + "origin": "source", "typeParameters": null, - "returns": "DocumentColor", - "params": [] + "returns": "DocumentTemplate", + "params": [ + { + "type": "BrandTheme", + "name": "theme" + } + ] } ] }, { - "name": "MintEditorial.Options.Builder", - "binaryName": "com.demcha.compose.document.templates.cv.presets.MintEditorial$Options$Builder", + "name": "EditorialBlue", + "binaryName": "com.demcha.compose.document.templates.cv.presets.EditorialBlue", "kind": "class", "modifiers": [ "final" @@ -8633,75 +8758,54 @@ "artifact": "graph-compose-templates", "members": [ { - "kind": "method", - "name": "accentColor", - "static": false, - "origin": "source", - "typeParameters": null, - "returns": "MintEditorial.Options.Builder", - "params": [ - { - "type": "DocumentColor", - "name": "value" - } - ] + "kind": "constant", + "name": "ID", + "static": true, + "origin": "generated", + "type": "String" }, { - "kind": "method", - "name": "ruleColor", - "static": false, - "origin": "source", - "typeParameters": null, - "returns": "MintEditorial.Options.Builder", - "params": [ - { - "type": "DocumentColor", - "name": "value" - } - ] + "kind": "constant", + "name": "DISPLAY_NAME", + "static": true, + "origin": "generated", + "type": "String" + }, + { + "kind": "constant", + "name": "RECOMMENDED_MARGIN", + "static": true, + "origin": "generated", + "type": "double" }, { "kind": "method", - "name": "nameColor", - "static": false, + "name": "create", + "static": true, "origin": "source", "typeParameters": null, - "returns": "MintEditorial.Options.Builder", - "params": [ - { - "type": "DocumentColor", - "name": "value" - } - ] + "returns": "DocumentTemplate", + "params": [] }, { "kind": "method", - "name": "headerBandColor", - "static": false, + "name": "create", + "static": true, "origin": "source", "typeParameters": null, - "returns": "MintEditorial.Options.Builder", + "returns": "DocumentTemplate", "params": [ { - "type": "DocumentColor", - "name": "value" + "type": "BrandTheme", + "name": "theme" } ] - }, - { - "kind": "method", - "name": "build", - "static": false, - "origin": "source", - "typeParameters": null, - "returns": "MintEditorial.Options", - "params": [] } ] }, { - "name": "ModernProfessional", - "binaryName": "com.demcha.compose.document.templates.cv.presets.ModernProfessional", + "name": "EngineeringResume", + "binaryName": "com.demcha.compose.document.templates.cv.presets.EngineeringResume", "kind": "class", "modifiers": [ "final" @@ -8755,8 +8859,8 @@ ] }, { - "name": "MonogramSidebar", - "binaryName": "com.demcha.compose.document.templates.cv.presets.MonogramSidebar", + "name": "Executive", + "binaryName": "com.demcha.compose.document.templates.cv.presets.Executive", "kind": "class", "modifiers": [ "final" @@ -8806,121 +8910,46 @@ "name": "theme" } ] - }, - { - "kind": "method", - "name": "create", - "static": true, - "origin": "source", - "typeParameters": null, - "returns": "DocumentTemplate", - "params": [ - { - "type": "BrandTheme", - "name": "theme" - }, - { - "type": "MonogramSidebar.Options", - "name": "options" - } - ] } ] }, { - "name": "MonogramSidebar.Options", - "binaryName": "com.demcha.compose.document.templates.cv.presets.MonogramSidebar$Options", - "kind": "record", + "name": "MidnightNavy", + "binaryName": "com.demcha.compose.document.templates.cv.presets.MidnightNavy", + "kind": "class", "modifiers": [ "final" ], "artifact": "graph-compose-templates", "members": [ { - "kind": "constructor", - "name": "Options", - "static": false, + "kind": "constant", + "name": "ID", + "static": true, "origin": "generated", - "typeParameters": null, - "returns": null, - "params": [ - { - "type": "DocumentColor", - "name": null - }, - { - "type": "DocumentColor", - "name": null - }, - { - "type": "DocumentColor", - "name": null - }, - { - "type": "DocumentColor", - "name": null - } - ] + "type": "String" }, { - "kind": "method", - "name": "defaults", + "kind": "constant", + "name": "DISPLAY_NAME", "static": true, - "origin": "source", - "typeParameters": null, - "returns": "MonogramSidebar.Options", - "params": [] + "origin": "generated", + "type": "String" }, { "kind": "method", - "name": "builder", + "name": "create", "static": true, "origin": "source", "typeParameters": null, - "returns": "MonogramSidebar.Options.Builder", - "params": [] - }, - { - "kind": "method", - "name": "sidebarFillColor", - "static": false, - "origin": "generated", - "typeParameters": null, - "returns": "DocumentColor", - "params": [] - }, - { - "kind": "method", - "name": "mainFillColor", - "static": false, - "origin": "generated", - "typeParameters": null, - "returns": "DocumentColor", - "params": [] - }, - { - "kind": "method", - "name": "accentColor", - "static": false, - "origin": "generated", - "typeParameters": null, - "returns": "DocumentColor", - "params": [] - }, - { - "kind": "method", - "name": "monogramRingColor", - "static": false, - "origin": "generated", - "typeParameters": null, - "returns": "DocumentColor", + "returns": "DocumentTemplate", "params": [] } ] }, { - "name": "MonogramSidebar.Options.Builder", - "binaryName": "com.demcha.compose.document.templates.cv.presets.MonogramSidebar$Options$Builder", + "name": "MinimalUnderlined", + "binaryName": "com.demcha.compose.document.templates.cv.presets.MinimalUnderlined", "kind": "class", "modifiers": [ "final" @@ -8928,75 +8957,54 @@ "artifact": "graph-compose-templates", "members": [ { - "kind": "method", - "name": "sidebarFillColor", - "static": false, - "origin": "source", - "typeParameters": null, - "returns": "MonogramSidebar.Options.Builder", - "params": [ - { - "type": "DocumentColor", - "name": "value" - } - ] + "kind": "constant", + "name": "ID", + "static": true, + "origin": "generated", + "type": "String" }, { - "kind": "method", - "name": "mainFillColor", - "static": false, - "origin": "source", - "typeParameters": null, - "returns": "MonogramSidebar.Options.Builder", - "params": [ - { - "type": "DocumentColor", - "name": "value" - } - ] + "kind": "constant", + "name": "DISPLAY_NAME", + "static": true, + "origin": "generated", + "type": "String" + }, + { + "kind": "constant", + "name": "RECOMMENDED_MARGIN", + "static": true, + "origin": "generated", + "type": "double" }, { "kind": "method", - "name": "accentColor", - "static": false, + "name": "create", + "static": true, "origin": "source", "typeParameters": null, - "returns": "MonogramSidebar.Options.Builder", - "params": [ - { - "type": "DocumentColor", - "name": "value" - } - ] + "returns": "DocumentTemplate", + "params": [] }, { "kind": "method", - "name": "monogramRingColor", - "static": false, + "name": "create", + "static": true, "origin": "source", "typeParameters": null, - "returns": "MonogramSidebar.Options.Builder", + "returns": "DocumentTemplate", "params": [ { - "type": "DocumentColor", - "name": "value" + "type": "BrandTheme", + "name": "theme" } ] - }, - { - "kind": "method", - "name": "build", - "static": false, - "origin": "source", - "typeParameters": null, - "returns": "MonogramSidebar.Options", - "params": [] } ] }, { - "name": "NordicClean", - "binaryName": "com.demcha.compose.document.templates.cv.presets.NordicClean", + "name": "MintEditorial", + "binaryName": "com.demcha.compose.document.templates.cv.presets.MintEditorial", "kind": "class", "modifiers": [ "final" @@ -9047,6 +9055,20 @@ } ] }, + { + "kind": "method", + "name": "create", + "static": true, + "origin": "source", + "typeParameters": null, + "returns": "DocumentTemplate", + "params": [ + { + "type": "MintEditorial.Options", + "name": "options" + } + ] + }, { "kind": "method", "name": "create", @@ -9060,7 +9082,7 @@ "name": "theme" }, { - "type": "NordicClean.Options", + "type": "MintEditorial.Options", "name": "options" } ] @@ -9068,8 +9090,8 @@ ] }, { - "name": "NordicClean.Options", - "binaryName": "com.demcha.compose.document.templates.cv.presets.NordicClean$Options", + "name": "MintEditorial.Options", + "binaryName": "com.demcha.compose.document.templates.cv.presets.MintEditorial$Options", "kind": "record", "modifiers": [ "final" @@ -9085,7 +9107,7 @@ "returns": null, "params": [ { - "type": "NordicClean.RailSide", + "type": "DocumentColor", "name": null }, { @@ -9108,7 +9130,7 @@ "static": true, "origin": "source", "typeParameters": null, - "returns": "NordicClean.Options", + "returns": "MintEditorial.Options", "params": [] }, { @@ -9117,21 +9139,21 @@ "static": true, "origin": "source", "typeParameters": null, - "returns": "NordicClean.Options.Builder", + "returns": "MintEditorial.Options.Builder", "params": [] }, { "kind": "method", - "name": "railSide", + "name": "accentColor", "static": false, "origin": "generated", "typeParameters": null, - "returns": "NordicClean.RailSide", + "returns": "DocumentColor", "params": [] }, { "kind": "method", - "name": "accentColor", + "name": "ruleColor", "static": false, "origin": "generated", "typeParameters": null, @@ -9140,7 +9162,7 @@ }, { "kind": "method", - "name": "railFillColor", + "name": "nameColor", "static": false, "origin": "generated", "typeParameters": null, @@ -9149,7 +9171,7 @@ }, { "kind": "method", - "name": "profileFillColor", + "name": "headerBandColor", "static": false, "origin": "generated", "typeParameters": null, @@ -9159,8 +9181,8 @@ ] }, { - "name": "NordicClean.Options.Builder", - "binaryName": "com.demcha.compose.document.templates.cv.presets.NordicClean$Options$Builder", + "name": "MintEditorial.Options.Builder", + "binaryName": "com.demcha.compose.document.templates.cv.presets.MintEditorial$Options$Builder", "kind": "class", "modifiers": [ "final" @@ -9169,25 +9191,25 @@ "members": [ { "kind": "method", - "name": "railSide", + "name": "accentColor", "static": false, "origin": "source", "typeParameters": null, - "returns": "NordicClean.Options.Builder", + "returns": "MintEditorial.Options.Builder", "params": [ { - "type": "NordicClean.RailSide", + "type": "DocumentColor", "name": "value" } ] }, { "kind": "method", - "name": "accentColor", + "name": "ruleColor", "static": false, "origin": "source", "typeParameters": null, - "returns": "NordicClean.Options.Builder", + "returns": "MintEditorial.Options.Builder", "params": [ { "type": "DocumentColor", @@ -9197,11 +9219,11 @@ }, { "kind": "method", - "name": "railFillColor", + "name": "nameColor", "static": false, "origin": "source", "typeParameters": null, - "returns": "NordicClean.Options.Builder", + "returns": "MintEditorial.Options.Builder", "params": [ { "type": "DocumentColor", @@ -9211,11 +9233,11 @@ }, { "kind": "method", - "name": "profileFillColor", + "name": "headerBandColor", "static": false, "origin": "source", "typeParameters": null, - "returns": "NordicClean.Options.Builder", + "returns": "MintEditorial.Options.Builder", "params": [ { "type": "DocumentColor", @@ -9229,39 +9251,14 @@ "static": false, "origin": "source", "typeParameters": null, - "returns": "NordicClean.Options", + "returns": "MintEditorial.Options", "params": [] } ] }, { - "name": "NordicClean.RailSide", - "binaryName": "com.demcha.compose.document.templates.cv.presets.NordicClean$RailSide", - "kind": "enum", - "modifiers": [ - "final" - ], - "artifact": "graph-compose-templates", - "members": [ - { - "kind": "constant", - "name": "LEFT", - "static": true, - "origin": "generated", - "type": "NordicClean.RailSide" - }, - { - "kind": "constant", - "name": "RIGHT", - "static": true, - "origin": "generated", - "type": "NordicClean.RailSide" - } - ] - }, - { - "name": "Panel", - "binaryName": "com.demcha.compose.document.templates.cv.presets.Panel", + "name": "ModernProfessional", + "binaryName": "com.demcha.compose.document.templates.cv.presets.ModernProfessional", "kind": "class", "modifiers": [ "final" @@ -9315,8 +9312,8 @@ ] }, { - "name": "SidebarPortrait", - "binaryName": "com.demcha.compose.document.templates.cv.presets.SidebarPortrait", + "name": "MonogramSidebar", + "binaryName": "com.demcha.compose.document.templates.cv.presets.MonogramSidebar", "kind": "class", "modifiers": [ "final" @@ -9380,7 +9377,7 @@ "name": "theme" }, { - "type": "SidebarPortrait.Options", + "type": "MonogramSidebar.Options", "name": "options" } ] @@ -9388,8 +9385,8 @@ ] }, { - "name": "SidebarPortrait.Options", - "binaryName": "com.demcha.compose.document.templates.cv.presets.SidebarPortrait$Options", + "name": "MonogramSidebar.Options", + "binaryName": "com.demcha.compose.document.templates.cv.presets.MonogramSidebar$Options", "kind": "record", "modifiers": [ "final" @@ -9412,6 +9409,10 @@ "type": "DocumentColor", "name": null }, + { + "type": "DocumentColor", + "name": null + }, { "type": "DocumentColor", "name": null @@ -9424,7 +9425,7 @@ "static": true, "origin": "source", "typeParameters": null, - "returns": "SidebarPortrait.Options", + "returns": "MonogramSidebar.Options", "params": [] }, { @@ -9433,7 +9434,7 @@ "static": true, "origin": "source", "typeParameters": null, - "returns": "SidebarPortrait.Options.Builder", + "returns": "MonogramSidebar.Options.Builder", "params": [] }, { @@ -9462,12 +9463,21 @@ "typeParameters": null, "returns": "DocumentColor", "params": [] + }, + { + "kind": "method", + "name": "monogramRingColor", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "DocumentColor", + "params": [] } ] }, { - "name": "SidebarPortrait.Options.Builder", - "binaryName": "com.demcha.compose.document.templates.cv.presets.SidebarPortrait$Options$Builder", + "name": "MonogramSidebar.Options.Builder", + "binaryName": "com.demcha.compose.document.templates.cv.presets.MonogramSidebar$Options$Builder", "kind": "class", "modifiers": [ "final" @@ -9480,7 +9490,7 @@ "static": false, "origin": "source", "typeParameters": null, - "returns": "SidebarPortrait.Options.Builder", + "returns": "MonogramSidebar.Options.Builder", "params": [ { "type": "DocumentColor", @@ -9494,7 +9504,7 @@ "static": false, "origin": "source", "typeParameters": null, - "returns": "SidebarPortrait.Options.Builder", + "returns": "MonogramSidebar.Options.Builder", "params": [ { "type": "DocumentColor", @@ -9508,7 +9518,21 @@ "static": false, "origin": "source", "typeParameters": null, - "returns": "SidebarPortrait.Options.Builder", + "returns": "MonogramSidebar.Options.Builder", + "params": [ + { + "type": "DocumentColor", + "name": "value" + } + ] + }, + { + "kind": "method", + "name": "monogramRingColor", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "MonogramSidebar.Options.Builder", "params": [ { "type": "DocumentColor", @@ -9522,14 +9546,14 @@ "static": false, "origin": "source", "typeParameters": null, - "returns": "SidebarPortrait.Options", + "returns": "MonogramSidebar.Options", "params": [] } ] }, { - "name": "TimelineMinimal", - "binaryName": "com.demcha.compose.document.templates.cv.presets.TimelineMinimal", + "name": "NavySidebar", + "binaryName": "com.demcha.compose.document.templates.cv.presets.NavySidebar", "kind": "class", "modifiers": [ "final" @@ -9565,31 +9589,12 @@ "typeParameters": null, "returns": "DocumentTemplate", "params": [] - }, - { - "kind": "method", - "name": "create", - "static": true, - "origin": "source", - "typeParameters": null, - "returns": "DocumentTemplate", - "params": [ - { - "type": "BrandTheme", - "name": "theme" - } - ] } ] - } - ] - }, - { - "name": "com.demcha.compose.document.templates.cv.widgets", - "types": [ + }, { - "name": "FlowSectionHeader", - "binaryName": "com.demcha.compose.document.templates.cv.widgets.FlowSectionHeader", + "name": "NordicClean", + "binaryName": "com.demcha.compose.document.templates.cv.presets.NordicClean", "kind": "class", "modifiers": [ "final" @@ -9597,361 +9602,73 @@ "artifact": "graph-compose-templates", "members": [ { - "kind": "method", - "name": "banner", + "kind": "constant", + "name": "ID", "static": true, - "origin": "source", - "typeParameters": null, - "returns": "void", - "params": [ - { - "type": "PageFlowBuilder", - "name": "flow" - }, - { - "type": "String", - "name": "name" - }, - { - "type": "String", - "name": "title" - }, - { - "type": "double", - "name": "ruleWidth" - }, - { - "type": "BrandTheme", - "name": "theme" - }, - { - "type": "DocumentTextStyle", - "name": "titleStyle" - }, - { - "type": "DocumentInsets", - "name": "topRuleMargin" - }, - { - "type": "DocumentInsets", - "name": "bottomRuleMargin" - } - ] + "origin": "generated", + "type": "String" + }, + { + "kind": "constant", + "name": "DISPLAY_NAME", + "static": true, + "origin": "generated", + "type": "String" + }, + { + "kind": "constant", + "name": "RECOMMENDED_MARGIN", + "static": true, + "origin": "generated", + "type": "double" }, { "kind": "method", - "name": "banner", + "name": "create", "static": true, "origin": "source", "typeParameters": null, - "returns": "void", - "params": [ - { - "type": "PageFlowBuilder", - "name": "flow" - }, - { - "type": "String", - "name": "name" - }, - { - "type": "String", - "name": "title" - }, - { - "type": "double", - "name": "ruleWidth" - }, - { - "type": "BrandTheme", - "name": "theme" - }, - { - "type": "DocumentTextStyle", - "name": "titleStyle" - }, - { - "type": "DocumentColor", - "name": "ruleColor" - }, - { - "type": "DocumentInsets", - "name": "topRuleMargin" - }, - { - "type": "DocumentInsets", - "name": "bottomRuleMargin" - } - ] + "returns": "DocumentTemplate", + "params": [] }, { "kind": "method", - "name": "label", + "name": "create", "static": true, "origin": "source", "typeParameters": null, - "returns": "void", + "returns": "DocumentTemplate", "params": [ - { - "type": "PageFlowBuilder", - "name": "flow" - }, - { - "type": "String", - "name": "name" - }, - { - "type": "String", - "name": "title" - }, - { - "type": "double", - "name": "ruleWidth" - }, { "type": "BrandTheme", "name": "theme" - }, - { - "type": "DocumentTextStyle", - "name": "titleStyle" - }, - { - "type": "DocumentInsets", - "name": "topRuleMargin" - }, - { - "type": "DocumentInsets", - "name": "titlePadding" - }, - { - "type": "DocumentInsets", - "name": "bottomRuleMargin" - }, - { - "type": "boolean", - "name": "withTopRule" } ] }, { "kind": "method", - "name": "label", + "name": "create", "static": true, "origin": "source", "typeParameters": null, - "returns": "void", + "returns": "DocumentTemplate", "params": [ - { - "type": "PageFlowBuilder", - "name": "flow" - }, - { - "type": "String", - "name": "name" - }, - { - "type": "String", - "name": "title" - }, - { - "type": "double", - "name": "ruleWidth" - }, { "type": "BrandTheme", "name": "theme" }, { - "type": "DocumentTextStyle", - "name": "titleStyle" - }, - { - "type": "DocumentColor", - "name": "ruleColor" - }, - { - "type": "DocumentInsets", - "name": "topRuleMargin" - }, - { - "type": "DocumentInsets", - "name": "titlePadding" - }, - { - "type": "DocumentInsets", - "name": "bottomRuleMargin" - }, - { - "type": "boolean", - "name": "withTopRule" + "type": "NordicClean.Options", + "name": "options" } ] } ] }, { - "name": "IconTextRow", - "binaryName": "com.demcha.compose.document.templates.cv.widgets.IconTextRow", - "kind": "class", - "modifiers": [ - "final" - ], - "artifact": "graph-compose-templates", - "members": [ - { - "kind": "method", - "name": "render", - "static": true, - "origin": "source", - "typeParameters": null, - "returns": "void", - "params": [ - { - "type": "SectionBuilder", - "name": "host" - }, - { - "type": "SvgGlyph", - "name": "glyph" - }, - { - "type": "DocumentColor", - "name": "glyphColor" - }, - { - "type": "double", - "name": "iconSize" - }, - { - "type": "String", - "name": "text" - }, - { - "type": "DocumentTextStyle", - "name": "style" - }, - { - "type": "DocumentLinkOptions", - "name": "link" - }, - { - "type": "DocumentInsets", - "name": "margin" - } - ] - }, - { - "kind": "method", - "name": "render", - "static": true, - "origin": "source", - "typeParameters": null, - "returns": "void", - "params": [ - { - "type": "SectionBuilder", - "name": "host" - }, - { - "type": "DocumentImageData", - "name": "icon" - }, - { - "type": "double", - "name": "iconSize" - }, - { - "type": "String", - "name": "text" - }, - { - "type": "DocumentTextStyle", - "name": "style" - }, - { - "type": "DocumentLinkOptions", - "name": "link" - }, - { - "type": "DocumentInsets", - "name": "margin" - } - ] - } - ] - }, - { - "name": "ProfileBand", - "binaryName": "com.demcha.compose.document.templates.cv.widgets.ProfileBand", - "kind": "class", - "modifiers": [ - "final" - ], - "artifact": "graph-compose-templates", - "members": [ - { - "kind": "method", - "name": "render", - "static": true, - "origin": "source", - "typeParameters": null, - "returns": "void", - "params": [ - { - "type": "PageFlowBuilder", - "name": "flow" - }, - { - "type": "String", - "name": "name" - }, - { - "type": "String", - "name": "title" - }, - { - "type": "String", - "name": "body" - }, - { - "type": "ProfileBand.Style", - "name": "style" - } - ] - }, - { - "kind": "method", - "name": "render", - "static": true, - "origin": "source", - "typeParameters": null, - "returns": "void", - "params": [ - { - "type": "SectionBuilder", - "name": "host" - }, - { - "type": "String", - "name": "title" - }, - { - "type": "String", - "name": "body" - }, - { - "type": "ProfileBand.Style", - "name": "style" - } - ] - } - ] - }, - { - "name": "ProfileBand.Style", - "binaryName": "com.demcha.compose.document.templates.cv.widgets.ProfileBand$Style", - "kind": "record", + "name": "NordicClean.Options", + "binaryName": "com.demcha.compose.document.templates.cv.presets.NordicClean$Options", + "kind": "record", "modifiers": [ "final" ], @@ -9959,75 +9676,27 @@ "members": [ { "kind": "constructor", - "name": "Style", + "name": "Options", "static": false, "origin": "generated", "typeParameters": null, "returns": null, "params": [ { - "type": "double", - "name": null - }, - { - "type": "DocumentInsets", - "name": null - }, - { - "type": "DocumentColor", - "name": null - }, - { - "type": "DocumentCornerRadius", + "type": "NordicClean.RailSide", "name": null }, { "type": "DocumentColor", "name": null }, - { - "type": "double", - "name": null - }, { "type": "DocumentColor", "name": null }, - { - "type": "double", - "name": null - }, { "type": "DocumentColor", "name": null - }, - { - "type": "double", - "name": null - }, - { - "type": "DocumentTextStyle", - "name": null - }, - { - "type": "TextAlign", - "name": null - }, - { - "type": "boolean", - "name": null - }, - { - "type": "DocumentTextStyle", - "name": null - }, - { - "type": "TextAlign", - "name": null - }, - { - "type": "double", - "name": null } ] }, @@ -10037,7 +9706,7 @@ "static": true, "origin": "source", "typeParameters": null, - "returns": "ProfileBand.Style", + "returns": "NordicClean.Options", "params": [] }, { @@ -10046,30 +9715,30 @@ "static": true, "origin": "source", "typeParameters": null, - "returns": "ProfileBand.Style.Builder", + "returns": "NordicClean.Options.Builder", "params": [] }, { "kind": "method", - "name": "spacing", + "name": "railSide", "static": false, "origin": "generated", "typeParameters": null, - "returns": "double", + "returns": "NordicClean.RailSide", "params": [] }, { "kind": "method", - "name": "padding", + "name": "accentColor", "static": false, "origin": "generated", "typeParameters": null, - "returns": "DocumentInsets", + "returns": "DocumentColor", "params": [] }, { "kind": "method", - "name": "fillColor", + "name": "railFillColor", "static": false, "origin": "generated", "typeParameters": null, @@ -10078,126 +9747,160 @@ }, { "kind": "method", - "name": "cornerRadius", + "name": "profileFillColor", "static": false, "origin": "generated", "typeParameters": null, - "returns": "DocumentCornerRadius", + "returns": "DocumentColor", "params": [] - }, + } + ] + }, + { + "name": "NordicClean.Options.Builder", + "binaryName": "com.demcha.compose.document.templates.cv.presets.NordicClean$Options$Builder", + "kind": "class", + "modifiers": [ + "final" + ], + "artifact": "graph-compose-templates", + "members": [ { "kind": "method", - "name": "accentLeftColor", + "name": "railSide", "static": false, - "origin": "generated", + "origin": "source", "typeParameters": null, - "returns": "DocumentColor", - "params": [] + "returns": "NordicClean.Options.Builder", + "params": [ + { + "type": "NordicClean.RailSide", + "name": "value" + } + ] }, { "kind": "method", - "name": "accentLeftWidth", + "name": "accentColor", "static": false, - "origin": "generated", + "origin": "source", "typeParameters": null, - "returns": "double", - "params": [] - }, - { - "kind": "method", - "name": "accentTopColor", - "static": false, - "origin": "generated", - "typeParameters": null, - "returns": "DocumentColor", - "params": [] + "returns": "NordicClean.Options.Builder", + "params": [ + { + "type": "DocumentColor", + "name": "value" + } + ] }, { "kind": "method", - "name": "accentTopWidth", + "name": "railFillColor", "static": false, - "origin": "generated", + "origin": "source", "typeParameters": null, - "returns": "double", - "params": [] + "returns": "NordicClean.Options.Builder", + "params": [ + { + "type": "DocumentColor", + "name": "value" + } + ] }, { "kind": "method", - "name": "accentBottomColor", + "name": "profileFillColor", "static": false, - "origin": "generated", + "origin": "source", "typeParameters": null, - "returns": "DocumentColor", - "params": [] + "returns": "NordicClean.Options.Builder", + "params": [ + { + "type": "DocumentColor", + "name": "value" + } + ] }, { "kind": "method", - "name": "accentBottomWidth", + "name": "build", "static": false, - "origin": "generated", + "origin": "source", "typeParameters": null, - "returns": "double", + "returns": "NordicClean.Options", "params": [] - }, + } + ] + }, + { + "name": "NordicClean.RailSide", + "binaryName": "com.demcha.compose.document.templates.cv.presets.NordicClean$RailSide", + "kind": "enum", + "modifiers": [ + "final" + ], + "artifact": "graph-compose-templates", + "members": [ { - "kind": "method", - "name": "titleStyle", - "static": false, + "kind": "constant", + "name": "LEFT", + "static": true, "origin": "generated", - "typeParameters": null, - "returns": "DocumentTextStyle", - "params": [] + "type": "NordicClean.RailSide" }, { - "kind": "method", - "name": "titleAlign", - "static": false, + "kind": "constant", + "name": "RIGHT", + "static": true, "origin": "generated", - "typeParameters": null, - "returns": "TextAlign", - "params": [] - }, + "type": "NordicClean.RailSide" + } + ] + }, + { + "name": "OrangeOps", + "binaryName": "com.demcha.compose.document.templates.cv.presets.OrangeOps", + "kind": "class", + "modifiers": [ + "final" + ], + "artifact": "graph-compose-templates", + "members": [ { - "kind": "method", - "name": "transformTitle", - "static": false, + "kind": "constant", + "name": "ID", + "static": true, "origin": "generated", - "typeParameters": null, - "returns": "boolean", - "params": [] + "type": "String" }, { - "kind": "method", - "name": "bodyStyle", - "static": false, + "kind": "constant", + "name": "DISPLAY_NAME", + "static": true, "origin": "generated", - "typeParameters": null, - "returns": "DocumentTextStyle", - "params": [] + "type": "String" }, { - "kind": "method", - "name": "bodyAlign", - "static": false, + "kind": "constant", + "name": "DISPLAY_FONT", + "static": true, "origin": "generated", - "typeParameters": null, - "returns": "TextAlign", - "params": [] + "type": "FontName" }, { "kind": "method", - "name": "bodyLineSpacing", - "static": false, - "origin": "generated", + "name": "create", + "static": true, + "origin": "source", "typeParameters": null, - "returns": "double", + "returns": "DocumentTemplate", "params": [] } ] }, { - "name": "ProfileBand.Style.Builder", - "binaryName": "com.demcha.compose.document.templates.cv.widgets.ProfileBand$Style$Builder", + "name": "Panel", + "binaryName": "com.demcha.compose.document.templates.cv.presets.Panel", "kind": "class", "modifiers": [ "final" @@ -10205,195 +9908,331 @@ "artifact": "graph-compose-templates", "members": [ { - "kind": "method", - "name": "spacing", - "static": false, - "origin": "source", - "typeParameters": null, - "returns": "ProfileBand.Style.Builder", - "params": [ - { - "type": "double", - "name": "value" - } - ] + "kind": "constant", + "name": "ID", + "static": true, + "origin": "generated", + "type": "String" }, { - "kind": "method", - "name": "padding", - "static": false, - "origin": "source", - "typeParameters": null, - "returns": "ProfileBand.Style.Builder", - "params": [ - { - "type": "DocumentInsets", - "name": "value" - } - ] + "kind": "constant", + "name": "DISPLAY_NAME", + "static": true, + "origin": "generated", + "type": "String" }, { - "kind": "method", - "name": "fillColor", - "static": false, - "origin": "source", - "typeParameters": null, - "returns": "ProfileBand.Style.Builder", - "params": [ - { - "type": "DocumentColor", - "name": "value" - } - ] + "kind": "constant", + "name": "RECOMMENDED_MARGIN", + "static": true, + "origin": "generated", + "type": "double" }, { "kind": "method", - "name": "cornerRadius", - "static": false, + "name": "create", + "static": true, "origin": "source", "typeParameters": null, - "returns": "ProfileBand.Style.Builder", - "params": [ - { - "type": "DocumentCornerRadius", - "name": "value" - } - ] + "returns": "DocumentTemplate", + "params": [] }, { "kind": "method", - "name": "accentLeft", - "static": false, + "name": "create", + "static": true, "origin": "source", "typeParameters": null, - "returns": "ProfileBand.Style.Builder", + "returns": "DocumentTemplate", "params": [ { - "type": "DocumentColor", - "name": "color" - }, - { - "type": "double", - "name": "width" + "type": "BrandTheme", + "name": "theme" } ] + } + ] + }, + { + "name": "ProfessionalSidebar", + "binaryName": "com.demcha.compose.document.templates.cv.presets.ProfessionalSidebar", + "kind": "class", + "modifiers": [ + "final" + ], + "artifact": "graph-compose-templates", + "members": [ + { + "kind": "constant", + "name": "ID", + "static": true, + "origin": "generated", + "type": "String" }, { - "kind": "method", - "name": "accentTop", - "static": false, - "origin": "source", - "typeParameters": null, - "returns": "ProfileBand.Style.Builder", - "params": [ - { - "type": "DocumentColor", - "name": "color" - }, - { - "type": "double", - "name": "width" - } - ] + "kind": "constant", + "name": "DISPLAY_NAME", + "static": true, + "origin": "generated", + "type": "String" + }, + { + "kind": "constant", + "name": "RECOMMENDED_MARGIN", + "static": true, + "origin": "generated", + "type": "double" }, { "kind": "method", - "name": "accentBottom", - "static": false, + "name": "create", + "static": true, "origin": "source", "typeParameters": null, - "returns": "ProfileBand.Style.Builder", - "params": [ - { - "type": "DocumentColor", - "name": "color" - }, - { - "type": "double", - "name": "width" - } - ] + "returns": "DocumentTemplate", + "params": [] + } + ] + }, + { + "name": "SerifHeadline", + "binaryName": "com.demcha.compose.document.templates.cv.presets.SerifHeadline", + "kind": "class", + "modifiers": [ + "final" + ], + "artifact": "graph-compose-templates", + "members": [ + { + "kind": "constant", + "name": "ID", + "static": true, + "origin": "generated", + "type": "String" + }, + { + "kind": "constant", + "name": "DISPLAY_NAME", + "static": true, + "origin": "generated", + "type": "String" + }, + { + "kind": "constant", + "name": "RECOMMENDED_MARGIN", + "static": true, + "origin": "generated", + "type": "double" }, { "kind": "method", - "name": "titleStyle", - "static": false, + "name": "create", + "static": true, "origin": "source", "typeParameters": null, - "returns": "ProfileBand.Style.Builder", + "returns": "DocumentTemplate", + "params": [] + } + ] + }, + { + "name": "SidebarPortrait", + "binaryName": "com.demcha.compose.document.templates.cv.presets.SidebarPortrait", + "kind": "class", + "modifiers": [ + "final" + ], + "artifact": "graph-compose-templates", + "members": [ + { + "kind": "constant", + "name": "ID", + "static": true, + "origin": "generated", + "type": "String" + }, + { + "kind": "constant", + "name": "DISPLAY_NAME", + "static": true, + "origin": "generated", + "type": "String" + }, + { + "kind": "constant", + "name": "RECOMMENDED_MARGIN", + "static": true, + "origin": "generated", + "type": "double" + }, + { + "kind": "method", + "name": "create", + "static": true, + "origin": "source", + "typeParameters": null, + "returns": "DocumentTemplate", + "params": [] + }, + { + "kind": "method", + "name": "create", + "static": true, + "origin": "source", + "typeParameters": null, + "returns": "DocumentTemplate", "params": [ { - "type": "DocumentTextStyle", - "name": "value" + "type": "BrandTheme", + "name": "theme" } ] }, { "kind": "method", - "name": "titleAlign", - "static": false, + "name": "create", + "static": true, "origin": "source", "typeParameters": null, - "returns": "ProfileBand.Style.Builder", + "returns": "DocumentTemplate", "params": [ { - "type": "TextAlign", - "name": "value" + "type": "BrandTheme", + "name": "theme" + }, + { + "type": "SidebarPortrait.Options", + "name": "options" } ] - }, + } + ] + }, + { + "name": "SidebarPortrait.Options", + "binaryName": "com.demcha.compose.document.templates.cv.presets.SidebarPortrait$Options", + "kind": "record", + "modifiers": [ + "final" + ], + "artifact": "graph-compose-templates", + "members": [ { - "kind": "method", - "name": "transformTitle", + "kind": "constructor", + "name": "Options", "static": false, - "origin": "source", + "origin": "generated", "typeParameters": null, - "returns": "ProfileBand.Style.Builder", + "returns": null, "params": [ { - "type": "boolean", - "name": "value" + "type": "DocumentColor", + "name": null + }, + { + "type": "DocumentColor", + "name": null + }, + { + "type": "DocumentColor", + "name": null } ] }, { "kind": "method", - "name": "bodyStyle", + "name": "defaults", + "static": true, + "origin": "source", + "typeParameters": null, + "returns": "SidebarPortrait.Options", + "params": [] + }, + { + "kind": "method", + "name": "builder", + "static": true, + "origin": "source", + "typeParameters": null, + "returns": "SidebarPortrait.Options.Builder", + "params": [] + }, + { + "kind": "method", + "name": "sidebarFillColor", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "DocumentColor", + "params": [] + }, + { + "kind": "method", + "name": "mainFillColor", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "DocumentColor", + "params": [] + }, + { + "kind": "method", + "name": "accentColor", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "DocumentColor", + "params": [] + } + ] + }, + { + "name": "SidebarPortrait.Options.Builder", + "binaryName": "com.demcha.compose.document.templates.cv.presets.SidebarPortrait$Options$Builder", + "kind": "class", + "modifiers": [ + "final" + ], + "artifact": "graph-compose-templates", + "members": [ + { + "kind": "method", + "name": "sidebarFillColor", "static": false, "origin": "source", "typeParameters": null, - "returns": "ProfileBand.Style.Builder", + "returns": "SidebarPortrait.Options.Builder", "params": [ { - "type": "DocumentTextStyle", + "type": "DocumentColor", "name": "value" } ] }, { "kind": "method", - "name": "bodyAlign", + "name": "mainFillColor", "static": false, "origin": "source", "typeParameters": null, - "returns": "ProfileBand.Style.Builder", + "returns": "SidebarPortrait.Options.Builder", "params": [ { - "type": "TextAlign", + "type": "DocumentColor", "name": "value" } ] }, { "kind": "method", - "name": "bodyLineSpacing", + "name": "accentColor", "static": false, "origin": "source", "typeParameters": null, - "returns": "ProfileBand.Style.Builder", + "returns": "SidebarPortrait.Options.Builder", "params": [ { - "type": "double", + "type": "DocumentColor", "name": "value" } ] @@ -10404,157 +10243,246 @@ "static": false, "origin": "source", "typeParameters": null, - "returns": "ProfileBand.Style", + "returns": "SidebarPortrait.Options", "params": [] } ] }, { - "name": "SectionHeader", - "binaryName": "com.demcha.compose.document.templates.cv.widgets.SectionHeader", + "name": "SlateOrange", + "binaryName": "com.demcha.compose.document.templates.cv.presets.SlateOrange", "kind": "class", "modifiers": [ "final" ], "artifact": "graph-compose-templates", "members": [ + { + "kind": "constant", + "name": "ID", + "static": true, + "origin": "generated", + "type": "String" + }, + { + "kind": "constant", + "name": "DISPLAY_NAME", + "static": true, + "origin": "generated", + "type": "String" + }, { "kind": "method", - "name": "banner", + "name": "create", "static": true, "origin": "source", "typeParameters": null, - "returns": "void", - "params": [ - { - "type": "SectionBuilder", - "name": "host" - }, - { - "type": "String", - "name": "title" - }, - { - "type": "BrandTheme", - "name": "theme" - } - ] + "returns": "DocumentTemplate", + "params": [] + } + ] + }, + { + "name": "TealPulse", + "binaryName": "com.demcha.compose.document.templates.cv.presets.TealPulse", + "kind": "class", + "modifiers": [ + "final" + ], + "artifact": "graph-compose-templates", + "members": [ + { + "kind": "constant", + "name": "ID", + "static": true, + "origin": "generated", + "type": "String" + }, + { + "kind": "constant", + "name": "DISPLAY_NAME", + "static": true, + "origin": "generated", + "type": "String" }, { "kind": "method", - "name": "fullWidthBanner", + "name": "create", "static": true, "origin": "source", "typeParameters": null, - "returns": "void", - "params": [ - { - "type": "SectionBuilder", - "name": "host" - }, - { - "type": "String", - "name": "title" - }, - { - "type": "BrandTheme", - "name": "theme" - } - ] + "returns": "DocumentTemplate", + "params": [] + } + ] + }, + { + "name": "TerracottaRail", + "binaryName": "com.demcha.compose.document.templates.cv.presets.TerracottaRail", + "kind": "class", + "modifiers": [ + "final" + ], + "artifact": "graph-compose-templates", + "members": [ + { + "kind": "constant", + "name": "ID", + "static": true, + "origin": "generated", + "type": "String" + }, + { + "kind": "constant", + "name": "DISPLAY_NAME", + "static": true, + "origin": "generated", + "type": "String" + }, + { + "kind": "constant", + "name": "RECOMMENDED_MARGIN", + "static": true, + "origin": "generated", + "type": "double" }, { "kind": "method", - "name": "fullWidthBanner", + "name": "create", "static": true, "origin": "source", "typeParameters": null, - "returns": "void", - "params": [ - { - "type": "SectionBuilder", - "name": "host" - }, - { - "type": "String", - "name": "title" - }, - { - "type": "BrandTheme", - "name": "theme" - }, - { - "type": "DocumentTextStyle", - "name": "titleStyleOverride" - } - ] + "returns": "DocumentTemplate", + "params": [] + } + ] + }, + { + "name": "TimelineMinimal", + "binaryName": "com.demcha.compose.document.templates.cv.presets.TimelineMinimal", + "kind": "class", + "modifiers": [ + "final" + ], + "artifact": "graph-compose-templates", + "members": [ + { + "kind": "constant", + "name": "ID", + "static": true, + "origin": "generated", + "type": "String" + }, + { + "kind": "constant", + "name": "DISPLAY_NAME", + "static": true, + "origin": "generated", + "type": "String" + }, + { + "kind": "constant", + "name": "RECOMMENDED_MARGIN", + "static": true, + "origin": "generated", + "type": "double" }, { "kind": "method", - "name": "underlined", + "name": "create", "static": true, "origin": "source", "typeParameters": null, - "returns": "void", - "params": [ - { - "type": "SectionBuilder", - "name": "host" - }, - { - "type": "String", - "name": "title" - }, - { - "type": "BrandTheme", - "name": "theme" - } - ] + "returns": "DocumentTemplate", + "params": [] }, { "kind": "method", - "name": "flat", + "name": "create", "static": true, "origin": "source", "typeParameters": null, - "returns": "void", + "returns": "DocumentTemplate", "params": [ - { - "type": "SectionBuilder", - "name": "host" - }, - { - "type": "String", - "name": "title" - }, - { - "type": "DocumentColor", - "name": "color" - }, { "type": "BrandTheme", "name": "theme" } ] + } + ] + }, + { + "name": "VioletGrid", + "binaryName": "com.demcha.compose.document.templates.cv.presets.VioletGrid", + "kind": "class", + "modifiers": [ + "final" + ], + "artifact": "graph-compose-templates", + "members": [ + { + "kind": "constant", + "name": "ID", + "static": true, + "origin": "generated", + "type": "String" + }, + { + "kind": "constant", + "name": "DISPLAY_NAME", + "static": true, + "origin": "generated", + "type": "String" }, { "kind": "method", - "name": "flatSpacedCaps", + "name": "create", + "static": true, + "origin": "source", + "typeParameters": null, + "returns": "DocumentTemplate", + "params": [] + } + ] + } + ] + }, + { + "name": "com.demcha.compose.document.templates.cv.widgets", + "types": [ + { + "name": "FlowSectionHeader", + "binaryName": "com.demcha.compose.document.templates.cv.widgets.FlowSectionHeader", + "kind": "class", + "modifiers": [ + "final" + ], + "artifact": "graph-compose-templates", + "members": [ + { + "kind": "method", + "name": "banner", "static": true, "origin": "source", "typeParameters": null, "returns": "void", "params": [ { - "type": "SectionBuilder", - "name": "host" + "type": "PageFlowBuilder", + "name": "flow" + }, + { + "type": "String", + "name": "name" }, { "type": "String", "name": "title" }, { - "type": "DocumentColor", - "name": "color" + "type": "double", + "name": "ruleWidth" }, { "type": "BrandTheme", @@ -10563,89 +10491,87 @@ { "type": "DocumentTextStyle", "name": "titleStyle" + }, + { + "type": "DocumentInsets", + "name": "topRuleMargin" + }, + { + "type": "DocumentInsets", + "name": "bottomRuleMargin" } ] }, { "kind": "method", - "name": "tickLabel", + "name": "banner", "static": true, "origin": "source", "typeParameters": null, "returns": "void", "params": [ { - "type": "SectionBuilder", - "name": "host" + "type": "PageFlowBuilder", + "name": "flow" + }, + { + "type": "String", + "name": "name" }, { "type": "String", "name": "title" }, + { + "type": "double", + "name": "ruleWidth" + }, { "type": "BrandTheme", "name": "theme" }, { - "type": "DocumentColor", - "name": "color" - }, - { - "type": "double", - "name": "tickWidth" - } - ] - }, - { - "kind": "method", - "name": "tickLabel", - "static": true, - "origin": "source", - "typeParameters": null, - "returns": "void", - "params": [ - { - "type": "SectionBuilder", - "name": "host" - }, - { - "type": "String", - "name": "title" - }, - { - "type": "BrandTheme", - "name": "theme" + "type": "DocumentTextStyle", + "name": "titleStyle" }, { "type": "DocumentColor", - "name": "color" + "name": "ruleColor" }, { - "type": "double", - "name": "tickWidth" + "type": "DocumentInsets", + "name": "topRuleMargin" }, { - "type": "DocumentTextStyle", - "name": "titleStyle" + "type": "DocumentInsets", + "name": "bottomRuleMargin" } ] }, { "kind": "method", - "name": "upperRule", + "name": "label", "static": true, "origin": "source", "typeParameters": null, "returns": "void", "params": [ { - "type": "SectionBuilder", - "name": "host" + "type": "PageFlowBuilder", + "name": "flow" + }, + { + "type": "String", + "name": "name" }, { "type": "String", "name": "title" }, + { + "type": "double", + "name": "ruleWidth" + }, { "type": "BrandTheme", "name": "theme" @@ -10655,31 +10581,47 @@ "name": "titleStyle" }, { - "type": "DocumentColor", - "name": "ruleColor" + "type": "DocumentInsets", + "name": "topRuleMargin" }, { - "type": "double", - "name": "ruleWidth" + "type": "DocumentInsets", + "name": "titlePadding" + }, + { + "type": "DocumentInsets", + "name": "bottomRuleMargin" + }, + { + "type": "boolean", + "name": "withTopRule" } ] }, { "kind": "method", - "name": "spacedCapsRule", + "name": "label", "static": true, "origin": "source", "typeParameters": null, "returns": "void", "params": [ { - "type": "SectionBuilder", - "name": "host" + "type": "PageFlowBuilder", + "name": "flow" + }, + { + "type": "String", + "name": "name" }, { "type": "String", "name": "title" }, + { + "type": "double", + "name": "ruleWidth" + }, { "type": "BrandTheme", "name": "theme" @@ -10693,24 +10635,28 @@ "name": "ruleColor" }, { - "type": "double", - "name": "ruleWidth" + "type": "DocumentInsets", + "name": "topRuleMargin" }, { - "type": "double", - "name": "ruleThickness" + "type": "DocumentInsets", + "name": "titlePadding" }, { "type": "DocumentInsets", - "name": "ruleMargin" + "name": "bottomRuleMargin" + }, + { + "type": "boolean", + "name": "withTopRule" } ] } ] }, { - "name": "SectionModule", - "binaryName": "com.demcha.compose.document.templates.cv.widgets.SectionModule", + "name": "IconTextRow", + "binaryName": "com.demcha.compose.document.templates.cv.widgets.IconTextRow", "kind": "class", "modifiers": [ "final" @@ -10719,7 +10665,7 @@ "members": [ { "kind": "method", - "name": "tick", + "name": "render", "static": true, "origin": "source", "typeParameters": null, @@ -10727,41 +10673,41 @@ "params": [ { "type": "SectionBuilder", - "name": "parent" + "name": "host" }, { - "type": "String", - "name": "name" + "type": "SvgGlyph", + "name": "glyph" }, { - "type": "String", - "name": "title" + "type": "DocumentColor", + "name": "glyphColor" }, { - "type": "BrandTheme", - "name": "theme" + "type": "double", + "name": "iconSize" }, { - "type": "DocumentColor", - "name": "color" + "type": "String", + "name": "text" }, { - "type": "double", - "name": "tickWidth" + "type": "DocumentTextStyle", + "name": "style" }, { - "type": "DocumentTextStyle", - "name": "titleStyle" + "type": "DocumentLinkOptions", + "name": "link" }, { - "type": "Consumer", - "name": "body" + "type": "DocumentInsets", + "name": "margin" } ] }, { "kind": "method", - "name": "upperRule", + "name": "render", "static": true, "origin": "source", "typeParameters": null, @@ -10769,49 +10715,75 @@ "params": [ { "type": "SectionBuilder", - "name": "parent" + "name": "host" }, { - "type": "String", - "name": "name" + "type": "DocumentImageData", + "name": "icon" }, { - "type": "String", - "name": "title" + "type": "double", + "name": "iconSize" }, { - "type": "BrandTheme", - "name": "theme" + "type": "String", + "name": "text" }, { "type": "DocumentTextStyle", - "name": "titleStyle" - }, - { - "type": "DocumentColor", - "name": "ruleColor" + "name": "style" }, { - "type": "double", - "name": "ruleWidth" + "type": "DocumentLinkOptions", + "name": "link" }, { - "type": "Consumer", - "name": "body" + "type": "DocumentInsets", + "name": "margin" } ] } ] }, { - "name": "SkillBar", - "binaryName": "com.demcha.compose.document.templates.cv.widgets.SkillBar", + "name": "ProfileBand", + "binaryName": "com.demcha.compose.document.templates.cv.widgets.ProfileBand", "kind": "class", "modifiers": [ "final" ], "artifact": "graph-compose-templates", "members": [ + { + "kind": "method", + "name": "render", + "static": true, + "origin": "source", + "typeParameters": null, + "returns": "void", + "params": [ + { + "type": "PageFlowBuilder", + "name": "flow" + }, + { + "type": "String", + "name": "name" + }, + { + "type": "String", + "name": "title" + }, + { + "type": "String", + "name": "body" + }, + { + "type": "ProfileBand.Style", + "name": "style" + } + ] + }, { "kind": "method", "name": "render", @@ -10825,29 +10797,24 @@ "name": "host" }, { - "type": "CvSkill", - "name": "skill" + "type": "String", + "name": "title" }, { - "type": "double", - "name": "trackWidth" + "type": "String", + "name": "body" }, { - "type": "BrandTheme", - "name": "theme" + "type": "ProfileBand.Style", + "name": "style" } ] } ] - } - ] - }, - { - "name": "com.demcha.compose.document.templates.data.invoice", - "types": [ + }, { - "name": "InvoiceData", - "binaryName": "com.demcha.compose.document.templates.data.invoice.InvoiceData", + "name": "ProfileBand.Style", + "binaryName": "com.demcha.compose.document.templates.cv.widgets.ProfileBand$Style", "kind": "record", "modifiers": [ "final" @@ -10856,197 +10823,245 @@ "members": [ { "kind": "constructor", - "name": "InvoiceData", + "name": "Style", "static": false, "origin": "generated", "typeParameters": null, "returns": null, "params": [ { - "type": "String", + "type": "double", "name": null }, { - "type": "String", + "type": "DocumentInsets", "name": null }, { - "type": "String", + "type": "DocumentColor", "name": null }, { - "type": "String", + "type": "DocumentCornerRadius", "name": null }, { - "type": "String", + "type": "DocumentColor", "name": null }, { - "type": "String", + "type": "double", "name": null }, { - "type": "InvoiceParty", + "type": "DocumentColor", "name": null }, { - "type": "InvoiceParty", + "type": "double", "name": null }, { - "type": "List", + "type": "DocumentColor", "name": null }, { - "type": "List", + "type": "double", "name": null }, { - "type": "List", + "type": "DocumentTextStyle", "name": null }, { - "type": "List", + "type": "TextAlign", "name": null }, { - "type": "String", + "type": "boolean", "name": null - } - ] - }, - { - "kind": "method", - "name": "builder", - "static": true, - "origin": "source", + }, + { + "type": "DocumentTextStyle", + "name": null + }, + { + "type": "TextAlign", + "name": null + }, + { + "type": "double", + "name": null + } + ] + }, + { + "kind": "method", + "name": "defaults", + "static": true, + "origin": "source", "typeParameters": null, - "returns": "InvoiceData.Builder", + "returns": "ProfileBand.Style", "params": [] }, { "kind": "method", - "name": "title", + "name": "builder", + "static": true, + "origin": "source", + "typeParameters": null, + "returns": "ProfileBand.Style.Builder", + "params": [] + }, + { + "kind": "method", + "name": "spacing", "static": false, "origin": "generated", "typeParameters": null, - "returns": "String", + "returns": "double", "params": [] }, { "kind": "method", - "name": "invoiceNumber", + "name": "padding", "static": false, "origin": "generated", "typeParameters": null, - "returns": "String", + "returns": "DocumentInsets", "params": [] }, { "kind": "method", - "name": "issueDate", + "name": "fillColor", "static": false, "origin": "generated", "typeParameters": null, - "returns": "String", + "returns": "DocumentColor", "params": [] }, { "kind": "method", - "name": "dueDate", + "name": "cornerRadius", "static": false, "origin": "generated", "typeParameters": null, - "returns": "String", + "returns": "DocumentCornerRadius", "params": [] }, { "kind": "method", - "name": "reference", + "name": "accentLeftColor", "static": false, "origin": "generated", "typeParameters": null, - "returns": "String", + "returns": "DocumentColor", "params": [] }, { "kind": "method", - "name": "status", + "name": "accentLeftWidth", "static": false, "origin": "generated", "typeParameters": null, - "returns": "String", + "returns": "double", "params": [] }, { "kind": "method", - "name": "fromParty", + "name": "accentTopColor", "static": false, "origin": "generated", "typeParameters": null, - "returns": "InvoiceParty", + "returns": "DocumentColor", "params": [] }, { "kind": "method", - "name": "billToParty", + "name": "accentTopWidth", "static": false, "origin": "generated", "typeParameters": null, - "returns": "InvoiceParty", + "returns": "double", "params": [] }, { "kind": "method", - "name": "lineItems", + "name": "accentBottomColor", "static": false, "origin": "generated", "typeParameters": null, - "returns": "List", + "returns": "DocumentColor", "params": [] }, { "kind": "method", - "name": "summaryRows", + "name": "accentBottomWidth", "static": false, "origin": "generated", "typeParameters": null, - "returns": "List", + "returns": "double", "params": [] }, { "kind": "method", - "name": "notes", + "name": "titleStyle", "static": false, "origin": "generated", "typeParameters": null, - "returns": "List", + "returns": "DocumentTextStyle", "params": [] }, { "kind": "method", - "name": "paymentTerms", + "name": "titleAlign", "static": false, "origin": "generated", "typeParameters": null, - "returns": "List", + "returns": "TextAlign", "params": [] }, { "kind": "method", - "name": "footerNote", + "name": "transformTitle", "static": false, "origin": "generated", "typeParameters": null, - "returns": "String", + "returns": "boolean", + "params": [] + }, + { + "kind": "method", + "name": "bodyStyle", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "DocumentTextStyle", + "params": [] + }, + { + "kind": "method", + "name": "bodyAlign", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "TextAlign", + "params": [] + }, + { + "kind": "method", + "name": "bodyLineSpacing", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "double", "params": [] } ] }, { - "name": "InvoiceData.Builder", - "binaryName": "com.demcha.compose.document.templates.data.invoice.InvoiceData$Builder", + "name": "ProfileBand.Style.Builder", + "binaryName": "com.demcha.compose.document.templates.cv.widgets.ProfileBand$Style$Builder", "kind": "class", "modifiers": [ "final" @@ -11055,815 +11070,1026 @@ "members": [ { "kind": "method", - "name": "title", + "name": "spacing", "static": false, "origin": "source", "typeParameters": null, - "returns": "InvoiceData.Builder", + "returns": "ProfileBand.Style.Builder", "params": [ { - "type": "String", - "name": "title" + "type": "double", + "name": "value" } ] }, { "kind": "method", - "name": "invoiceNumber", + "name": "padding", "static": false, "origin": "source", "typeParameters": null, - "returns": "InvoiceData.Builder", + "returns": "ProfileBand.Style.Builder", "params": [ { - "type": "String", - "name": "invoiceNumber" + "type": "DocumentInsets", + "name": "value" } ] }, { "kind": "method", - "name": "issueDate", + "name": "fillColor", "static": false, "origin": "source", "typeParameters": null, - "returns": "InvoiceData.Builder", + "returns": "ProfileBand.Style.Builder", "params": [ { - "type": "String", - "name": "issueDate" + "type": "DocumentColor", + "name": "value" } ] }, { "kind": "method", - "name": "dueDate", + "name": "cornerRadius", "static": false, "origin": "source", "typeParameters": null, - "returns": "InvoiceData.Builder", + "returns": "ProfileBand.Style.Builder", "params": [ { - "type": "String", - "name": "dueDate" + "type": "DocumentCornerRadius", + "name": "value" } ] }, { "kind": "method", - "name": "reference", + "name": "accentLeft", "static": false, "origin": "source", "typeParameters": null, - "returns": "InvoiceData.Builder", + "returns": "ProfileBand.Style.Builder", "params": [ { - "type": "String", - "name": "reference" + "type": "DocumentColor", + "name": "color" + }, + { + "type": "double", + "name": "width" } ] }, { "kind": "method", - "name": "status", + "name": "accentTop", "static": false, "origin": "source", "typeParameters": null, - "returns": "InvoiceData.Builder", + "returns": "ProfileBand.Style.Builder", "params": [ { - "type": "String", - "name": "status" + "type": "DocumentColor", + "name": "color" + }, + { + "type": "double", + "name": "width" } ] }, { "kind": "method", - "name": "fromParty", + "name": "accentBottom", "static": false, "origin": "source", "typeParameters": null, - "returns": "InvoiceData.Builder", + "returns": "ProfileBand.Style.Builder", "params": [ { - "type": "InvoiceParty", - "name": "fromParty" + "type": "DocumentColor", + "name": "color" + }, + { + "type": "double", + "name": "width" } ] }, { "kind": "method", - "name": "fromParty", + "name": "titleStyle", "static": false, "origin": "source", "typeParameters": null, - "returns": "InvoiceData.Builder", + "returns": "ProfileBand.Style.Builder", "params": [ { - "type": "Consumer", - "name": "spec" + "type": "DocumentTextStyle", + "name": "value" } ] }, { "kind": "method", - "name": "billToParty", + "name": "titleAlign", "static": false, "origin": "source", "typeParameters": null, - "returns": "InvoiceData.Builder", + "returns": "ProfileBand.Style.Builder", "params": [ { - "type": "InvoiceParty", - "name": "billToParty" + "type": "TextAlign", + "name": "value" } ] }, { "kind": "method", - "name": "billToParty", + "name": "transformTitle", "static": false, "origin": "source", "typeParameters": null, - "returns": "InvoiceData.Builder", + "returns": "ProfileBand.Style.Builder", "params": [ { - "type": "Consumer", - "name": "spec" + "type": "boolean", + "name": "value" } ] }, { "kind": "method", - "name": "lineItems", + "name": "bodyStyle", "static": false, "origin": "source", "typeParameters": null, - "returns": "InvoiceData.Builder", + "returns": "ProfileBand.Style.Builder", "params": [ { - "type": "List", - "name": "lineItems" + "type": "DocumentTextStyle", + "name": "value" } ] }, { "kind": "method", - "name": "addLineItem", + "name": "bodyAlign", "static": false, "origin": "source", "typeParameters": null, - "returns": "InvoiceData.Builder", + "returns": "ProfileBand.Style.Builder", "params": [ { - "type": "InvoiceLineItem", - "name": "lineItem" + "type": "TextAlign", + "name": "value" } ] }, { "kind": "method", - "name": "lineItem", + "name": "bodyLineSpacing", "static": false, "origin": "source", "typeParameters": null, - "returns": "InvoiceData.Builder", + "returns": "ProfileBand.Style.Builder", "params": [ { - "type": "Consumer", - "name": "spec" + "type": "double", + "name": "value" } ] }, { "kind": "method", - "name": "lineItem", + "name": "build", "static": false, "origin": "source", "typeParameters": null, - "returns": "InvoiceData.Builder", + "returns": "ProfileBand.Style", + "params": [] + } + ] + }, + { + "name": "SectionHeader", + "binaryName": "com.demcha.compose.document.templates.cv.widgets.SectionHeader", + "kind": "class", + "modifiers": [ + "final" + ], + "artifact": "graph-compose-templates", + "members": [ + { + "kind": "method", + "name": "banner", + "static": true, + "origin": "source", + "typeParameters": null, + "returns": "void", "params": [ { - "type": "String", - "name": "description" - }, - { - "type": "String", - "name": "details" - }, - { - "type": "String", - "name": "quantity" + "type": "SectionBuilder", + "name": "host" }, { "type": "String", - "name": "unitPrice" + "name": "title" }, { - "type": "String", - "name": "amount" + "type": "BrandTheme", + "name": "theme" } ] }, { "kind": "method", - "name": "summaryRows", - "static": false, + "name": "fullWidthBanner", + "static": true, "origin": "source", "typeParameters": null, - "returns": "InvoiceData.Builder", + "returns": "void", "params": [ { - "type": "List", - "name": "summaryRows" + "type": "SectionBuilder", + "name": "host" + }, + { + "type": "String", + "name": "title" + }, + { + "type": "BrandTheme", + "name": "theme" } ] }, { "kind": "method", - "name": "addSummaryRow", - "static": false, + "name": "fullWidthBanner", + "static": true, "origin": "source", "typeParameters": null, - "returns": "InvoiceData.Builder", + "returns": "void", "params": [ { - "type": "InvoiceSummaryRow", - "name": "summaryRow" + "type": "SectionBuilder", + "name": "host" + }, + { + "type": "String", + "name": "title" + }, + { + "type": "BrandTheme", + "name": "theme" + }, + { + "type": "DocumentTextStyle", + "name": "titleStyleOverride" } ] }, { "kind": "method", - "name": "summaryRow", - "static": false, + "name": "underlined", + "static": true, "origin": "source", "typeParameters": null, - "returns": "InvoiceData.Builder", + "returns": "void", "params": [ { - "type": "Consumer", - "name": "spec" + "type": "SectionBuilder", + "name": "host" + }, + { + "type": "String", + "name": "title" + }, + { + "type": "BrandTheme", + "name": "theme" } ] }, { "kind": "method", - "name": "summaryRow", - "static": false, + "name": "flat", + "static": true, "origin": "source", "typeParameters": null, - "returns": "InvoiceData.Builder", + "returns": "void", "params": [ { - "type": "String", - "name": "label" + "type": "SectionBuilder", + "name": "host" }, { "type": "String", - "name": "value" + "name": "title" + }, + { + "type": "DocumentColor", + "name": "color" + }, + { + "type": "BrandTheme", + "name": "theme" } ] }, { "kind": "method", - "name": "totalRow", - "static": false, + "name": "flatSpacedCaps", + "static": true, "origin": "source", "typeParameters": null, - "returns": "InvoiceData.Builder", + "returns": "void", "params": [ { - "type": "String", - "name": "label" + "type": "SectionBuilder", + "name": "host" }, { "type": "String", - "name": "value" + "name": "title" + }, + { + "type": "DocumentColor", + "name": "color" + }, + { + "type": "BrandTheme", + "name": "theme" + }, + { + "type": "DocumentTextStyle", + "name": "titleStyle" } ] }, { "kind": "method", - "name": "notes", - "static": false, + "name": "tickLabel", + "static": true, "origin": "source", "typeParameters": null, - "returns": "InvoiceData.Builder", + "returns": "void", "params": [ { - "type": "List", - "name": "notes" + "type": "SectionBuilder", + "name": "host" + }, + { + "type": "String", + "name": "title" + }, + { + "type": "BrandTheme", + "name": "theme" + }, + { + "type": "DocumentColor", + "name": "color" + }, + { + "type": "double", + "name": "tickWidth" } ] }, { "kind": "method", - "name": "note", - "static": false, + "name": "tickLabel", + "static": true, "origin": "source", "typeParameters": null, - "returns": "InvoiceData.Builder", + "returns": "void", "params": [ + { + "type": "SectionBuilder", + "name": "host" + }, { "type": "String", - "name": "note" + "name": "title" + }, + { + "type": "BrandTheme", + "name": "theme" + }, + { + "type": "DocumentColor", + "name": "color" + }, + { + "type": "double", + "name": "tickWidth" + }, + { + "type": "DocumentTextStyle", + "name": "titleStyle" } ] }, { "kind": "method", - "name": "paymentTerms", - "static": false, + "name": "upperRule", + "static": true, "origin": "source", "typeParameters": null, - "returns": "InvoiceData.Builder", + "returns": "void", "params": [ { - "type": "List", - "name": "paymentTerms" + "type": "SectionBuilder", + "name": "host" + }, + { + "type": "String", + "name": "title" + }, + { + "type": "BrandTheme", + "name": "theme" + }, + { + "type": "DocumentTextStyle", + "name": "titleStyle" + }, + { + "type": "DocumentColor", + "name": "ruleColor" + }, + { + "type": "double", + "name": "ruleWidth" } ] }, { "kind": "method", - "name": "paymentTerm", - "static": false, + "name": "spacedCapsRule", + "static": true, "origin": "source", "typeParameters": null, - "returns": "InvoiceData.Builder", + "returns": "void", "params": [ + { + "type": "SectionBuilder", + "name": "host" + }, { "type": "String", - "name": "paymentTerm" + "name": "title" + }, + { + "type": "BrandTheme", + "name": "theme" + }, + { + "type": "DocumentTextStyle", + "name": "titleStyle" + }, + { + "type": "DocumentColor", + "name": "ruleColor" + }, + { + "type": "double", + "name": "ruleWidth" + }, + { + "type": "double", + "name": "ruleThickness" + }, + { + "type": "DocumentInsets", + "name": "ruleMargin" } ] - }, + } + ] + }, + { + "name": "SectionModule", + "binaryName": "com.demcha.compose.document.templates.cv.widgets.SectionModule", + "kind": "class", + "modifiers": [ + "final" + ], + "artifact": "graph-compose-templates", + "members": [ { "kind": "method", - "name": "footerNote", - "static": false, + "name": "tick", + "static": true, "origin": "source", "typeParameters": null, - "returns": "InvoiceData.Builder", + "returns": "void", "params": [ + { + "type": "SectionBuilder", + "name": "parent" + }, { "type": "String", - "name": "footerNote" + "name": "name" + }, + { + "type": "String", + "name": "title" + }, + { + "type": "BrandTheme", + "name": "theme" + }, + { + "type": "DocumentColor", + "name": "color" + }, + { + "type": "double", + "name": "tickWidth" + }, + { + "type": "DocumentTextStyle", + "name": "titleStyle" + }, + { + "type": "Consumer", + "name": "body" } ] }, { "kind": "method", - "name": "build", - "static": false, + "name": "upperRule", + "static": true, "origin": "source", "typeParameters": null, - "returns": "InvoiceData", - "params": [] + "returns": "void", + "params": [ + { + "type": "SectionBuilder", + "name": "parent" + }, + { + "type": "String", + "name": "name" + }, + { + "type": "String", + "name": "title" + }, + { + "type": "BrandTheme", + "name": "theme" + }, + { + "type": "DocumentTextStyle", + "name": "titleStyle" + }, + { + "type": "DocumentColor", + "name": "ruleColor" + }, + { + "type": "double", + "name": "ruleWidth" + }, + { + "type": "Consumer", + "name": "body" + } + ] } ] }, { - "name": "InvoiceDocumentSpec", - "binaryName": "com.demcha.compose.document.templates.data.invoice.InvoiceDocumentSpec", - "kind": "record", + "name": "SkillBar", + "binaryName": "com.demcha.compose.document.templates.cv.widgets.SkillBar", + "kind": "class", "modifiers": [ "final" ], "artifact": "graph-compose-templates", "members": [ - { - "kind": "constructor", - "name": "InvoiceDocumentSpec", - "static": false, - "origin": "generated", - "typeParameters": null, - "returns": null, - "params": [ - { - "type": "InvoiceData", - "name": null - } - ] - }, { "kind": "method", - "name": "from", + "name": "render", "static": true, "origin": "source", "typeParameters": null, - "returns": "InvoiceDocumentSpec", + "returns": "void", "params": [ { - "type": "InvoiceData", - "name": "invoice" + "type": "SectionBuilder", + "name": "host" + }, + { + "type": "CvSkill", + "name": "skill" + }, + { + "type": "double", + "name": "trackWidth" + }, + { + "type": "BrandTheme", + "name": "theme" } ] - }, - { - "kind": "method", - "name": "builder", - "static": true, - "origin": "source", - "typeParameters": null, - "returns": "InvoiceDocumentSpec.Builder", - "params": [] - }, - { - "kind": "method", - "name": "invoice", - "static": false, - "origin": "generated", - "typeParameters": null, - "returns": "InvoiceData", - "params": [] } ] - }, + } + ] + }, + { + "name": "com.demcha.compose.document.templates.data.invoice", + "types": [ { - "name": "InvoiceDocumentSpec.Builder", - "binaryName": "com.demcha.compose.document.templates.data.invoice.InvoiceDocumentSpec$Builder", - "kind": "class", + "name": "InvoiceBrand", + "binaryName": "com.demcha.compose.document.templates.data.invoice.InvoiceBrand", + "kind": "record", "modifiers": [ "final" ], "artifact": "graph-compose-templates", "members": [ { - "kind": "method", - "name": "title", + "kind": "constructor", + "name": "InvoiceBrand", "static": false, - "origin": "source", + "origin": "generated", "typeParameters": null, - "returns": "InvoiceDocumentSpec.Builder", + "returns": null, "params": [ + { + "type": "DocumentImageData", + "name": null + }, { "type": "String", - "name": "title" + "name": null + }, + { + "type": "String", + "name": null + }, + { + "type": "String", + "name": null + }, + { + "type": "String", + "name": null + }, + { + "type": "String", + "name": null } ] }, { - "kind": "method", - "name": "invoiceNumber", + "kind": "constructor", + "name": "InvoiceBrand", "static": false, "origin": "source", "typeParameters": null, - "returns": "InvoiceDocumentSpec.Builder", + "returns": null, "params": [ + { + "type": "DocumentImageData", + "name": "logo" + }, { "type": "String", - "name": "invoiceNumber" + "name": "name" + }, + { + "type": "String", + "name": "qualifier" + }, + { + "type": "String", + "name": "tagline" } ] }, { "kind": "method", - "name": "issueDate", + "name": "hasMonogram", "static": false, "origin": "source", "typeParameters": null, - "returns": "InvoiceDocumentSpec.Builder", - "params": [ - { - "type": "String", - "name": "issueDate" - } - ] + "returns": "boolean", + "params": [] }, { "kind": "method", - "name": "dueDate", + "name": "hasLogo", "static": false, "origin": "source", "typeParameters": null, - "returns": "InvoiceDocumentSpec.Builder", - "params": [ - { - "type": "String", - "name": "dueDate" - } - ] + "returns": "boolean", + "params": [] }, { "kind": "method", - "name": "reference", + "name": "logo", "static": false, - "origin": "source", + "origin": "generated", "typeParameters": null, - "returns": "InvoiceDocumentSpec.Builder", - "params": [ - { - "type": "String", - "name": "reference" - } - ] + "returns": "DocumentImageData", + "params": [] }, { "kind": "method", - "name": "status", + "name": "name", "static": false, - "origin": "source", + "origin": "generated", "typeParameters": null, - "returns": "InvoiceDocumentSpec.Builder", - "params": [ - { - "type": "String", - "name": "status" - } - ] + "returns": "String", + "params": [] }, { "kind": "method", - "name": "fromParty", + "name": "qualifier", "static": false, - "origin": "source", + "origin": "generated", "typeParameters": null, - "returns": "InvoiceDocumentSpec.Builder", - "params": [ - { - "type": "InvoiceParty", - "name": "fromParty" - } - ] + "returns": "String", + "params": [] }, { "kind": "method", - "name": "fromParty", + "name": "tagline", "static": false, - "origin": "source", + "origin": "generated", "typeParameters": null, - "returns": "InvoiceDocumentSpec.Builder", - "params": [ - { - "type": "Consumer", - "name": "spec" - } - ] + "returns": "String", + "params": [] }, { "kind": "method", - "name": "billToParty", + "name": "monogramTop", "static": false, - "origin": "source", + "origin": "generated", "typeParameters": null, - "returns": "InvoiceDocumentSpec.Builder", - "params": [ - { - "type": "InvoiceParty", - "name": "billToParty" - } - ] + "returns": "String", + "params": [] }, { "kind": "method", - "name": "billToParty", + "name": "monogramBottom", "static": false, - "origin": "source", + "origin": "generated", "typeParameters": null, - "returns": "InvoiceDocumentSpec.Builder", - "params": [ - { - "type": "Consumer", - "name": "spec" - } - ] - }, + "returns": "String", + "params": [] + } + ] + }, + { + "name": "InvoiceContactBlock", + "binaryName": "com.demcha.compose.document.templates.data.invoice.InvoiceContactBlock", + "kind": "record", + "modifiers": [ + "final" + ], + "artifact": "graph-compose-templates", + "members": [ { - "kind": "method", - "name": "lineItems", + "kind": "constructor", + "name": "InvoiceContactBlock", "static": false, - "origin": "source", + "origin": "generated", "typeParameters": null, - "returns": "InvoiceDocumentSpec.Builder", + "returns": null, "params": [ { - "type": "List", - "name": "lineItems" - } - ] - }, - { - "kind": "method", - "name": "addLineItem", - "static": false, - "origin": "source", - "typeParameters": null, - "returns": "InvoiceDocumentSpec.Builder", - "params": [ + "type": "String", + "name": null + }, { - "type": "InvoiceLineItem", - "name": "lineItem" + "type": "List", + "name": null + }, + { + "type": "String", + "name": null + }, + { + "type": "String", + "name": null + }, + { + "type": "String", + "name": null + }, + { + "type": "String", + "name": null + }, + { + "type": "String", + "name": null + }, + { + "type": "String", + "name": null + }, + { + "type": "String", + "name": null + }, + { + "type": "String", + "name": null } ] }, { - "kind": "method", - "name": "lineItem", + "kind": "constructor", + "name": "InvoiceContactBlock", "static": false, "origin": "source", "typeParameters": null, - "returns": "InvoiceDocumentSpec.Builder", + "returns": null, "params": [ { - "type": "Consumer", - "name": "spec" + "type": "String", + "name": "legalName" + }, + { + "type": "List", + "name": "addressLines" + }, + { + "type": "String", + "name": "phone" + }, + { + "type": "String", + "name": "email" + }, + { + "type": "String", + "name": "website" + }, + { + "type": "String", + "name": "registrationLabel" + }, + { + "type": "String", + "name": "registrationNumber" + }, + { + "type": "String", + "name": "taxRegistrationLabel" + }, + { + "type": "String", + "name": "taxRegistrationNumber" } ] }, { - "kind": "method", - "name": "lineItem", + "kind": "constructor", + "name": "InvoiceContactBlock", "static": false, "origin": "source", "typeParameters": null, - "returns": "InvoiceDocumentSpec.Builder", + "returns": null, "params": [ { "type": "String", - "name": "description" + "name": "legalName" + }, + { + "type": "List", + "name": "addressLines" }, { "type": "String", - "name": "details" + "name": "phone" }, { "type": "String", - "name": "quantity" + "name": "email" }, { "type": "String", - "name": "unitPrice" + "name": "website" }, { "type": "String", - "name": "amount" + "name": "registrationLabel" + }, + { + "type": "String", + "name": "registrationNumber" } ] }, { "kind": "method", - "name": "summaryRows", + "name": "legalName", "static": false, - "origin": "source", + "origin": "generated", "typeParameters": null, - "returns": "InvoiceDocumentSpec.Builder", - "params": [ - { - "type": "List", - "name": "summaryRows" - } - ] + "returns": "String", + "params": [] }, { "kind": "method", - "name": "addSummaryRow", + "name": "addressLines", "static": false, - "origin": "source", + "origin": "generated", "typeParameters": null, - "returns": "InvoiceDocumentSpec.Builder", - "params": [ - { - "type": "InvoiceSummaryRow", - "name": "summaryRow" - } - ] + "returns": "List", + "params": [] }, { "kind": "method", - "name": "summaryRow", + "name": "phone", "static": false, - "origin": "source", + "origin": "generated", "typeParameters": null, - "returns": "InvoiceDocumentSpec.Builder", - "params": [ - { - "type": "Consumer", - "name": "spec" - } - ] + "returns": "String", + "params": [] }, { "kind": "method", - "name": "summaryRow", + "name": "email", "static": false, - "origin": "source", + "origin": "generated", "typeParameters": null, - "returns": "InvoiceDocumentSpec.Builder", - "params": [ - { - "type": "String", - "name": "label" - }, - { - "type": "String", - "name": "value" - } - ] + "returns": "String", + "params": [] }, { "kind": "method", - "name": "totalRow", + "name": "website", "static": false, - "origin": "source", + "origin": "generated", "typeParameters": null, - "returns": "InvoiceDocumentSpec.Builder", - "params": [ - { - "type": "String", - "name": "label" - }, - { - "type": "String", - "name": "value" - } - ] + "returns": "String", + "params": [] }, { "kind": "method", - "name": "notes", + "name": "registrationLabel", "static": false, - "origin": "source", + "origin": "generated", "typeParameters": null, - "returns": "InvoiceDocumentSpec.Builder", - "params": [ - { - "type": "List", - "name": "notes" - } - ] + "returns": "String", + "params": [] }, { "kind": "method", - "name": "note", + "name": "registrationNumber", "static": false, - "origin": "source", + "origin": "generated", "typeParameters": null, - "returns": "InvoiceDocumentSpec.Builder", - "params": [ - { - "type": "String", - "name": "note" - } - ] + "returns": "String", + "params": [] }, { "kind": "method", - "name": "paymentTerms", + "name": "taxRegistrationLabel", "static": false, - "origin": "source", + "origin": "generated", "typeParameters": null, - "returns": "InvoiceDocumentSpec.Builder", - "params": [ - { - "type": "List", - "name": "paymentTerms" - } - ] - }, - { - "kind": "method", - "name": "paymentTerm", - "static": false, - "origin": "source", - "typeParameters": null, - "returns": "InvoiceDocumentSpec.Builder", - "params": [ - { - "type": "String", - "name": "paymentTerm" - } - ] + "returns": "String", + "params": [] }, { "kind": "method", - "name": "footerNote", + "name": "taxRegistrationNumber", "static": false, - "origin": "source", + "origin": "generated", "typeParameters": null, - "returns": "InvoiceDocumentSpec.Builder", - "params": [ - { - "type": "String", - "name": "footerNote" - } - ] + "returns": "String", + "params": [] }, { "kind": "method", - "name": "build", + "name": "legalFootnote", "static": false, - "origin": "source", + "origin": "generated", "typeParameters": null, - "returns": "InvoiceDocumentSpec", + "returns": "String", "params": [] } ] }, { - "name": "InvoiceLineItem", - "binaryName": "com.demcha.compose.document.templates.data.invoice.InvoiceLineItem", + "name": "InvoiceData", + "binaryName": "com.demcha.compose.document.templates.data.invoice.InvoiceData", "kind": "record", "modifiers": [ "final" @@ -11872,7 +12098,7 @@ "members": [ { "kind": "constructor", - "name": "InvoiceLineItem", + "name": "InvoiceData", "static": false, "origin": "generated", "typeParameters": null, @@ -11894,6 +12120,38 @@ "type": "String", "name": null }, + { + "type": "String", + "name": null + }, + { + "type": "String", + "name": null + }, + { + "type": "InvoiceParty", + "name": null + }, + { + "type": "InvoiceParty", + "name": null + }, + { + "type": "List", + "name": null + }, + { + "type": "List", + "name": null + }, + { + "type": "List", + "name": null + }, + { + "type": "List", + "name": null + }, { "type": "String", "name": null @@ -11906,12 +12164,12 @@ "static": true, "origin": "source", "typeParameters": null, - "returns": "InvoiceLineItem.Builder", + "returns": "InvoiceData.Builder", "params": [] }, { "kind": "method", - "name": "description", + "name": "title", "static": false, "origin": "generated", "typeParameters": null, @@ -11920,7 +12178,7 @@ }, { "kind": "method", - "name": "details", + "name": "invoiceNumber", "static": false, "origin": "generated", "typeParameters": null, @@ -11929,7 +12187,7 @@ }, { "kind": "method", - "name": "quantity", + "name": "issueDate", "static": false, "origin": "generated", "typeParameters": null, @@ -11938,7 +12196,7 @@ }, { "kind": "method", - "name": "unitPrice", + "name": "dueDate", "static": false, "origin": "generated", "typeParameters": null, @@ -11947,192 +12205,79 @@ }, { "kind": "method", - "name": "amount", + "name": "reference", "static": false, "origin": "generated", "typeParameters": null, "returns": "String", "params": [] - } - ] - }, - { - "name": "InvoiceLineItem.Builder", - "binaryName": "com.demcha.compose.document.templates.data.invoice.InvoiceLineItem$Builder", - "kind": "class", - "modifiers": [ - "final" - ], - "artifact": "graph-compose-templates", - "members": [ - { - "kind": "method", - "name": "description", - "static": false, - "origin": "source", - "typeParameters": null, - "returns": "InvoiceLineItem.Builder", - "params": [ - { - "type": "String", - "name": "description" - } - ] - }, - { - "kind": "method", - "name": "details", - "static": false, - "origin": "source", - "typeParameters": null, - "returns": "InvoiceLineItem.Builder", - "params": [ - { - "type": "String", - "name": "details" - } - ] - }, - { - "kind": "method", - "name": "quantity", - "static": false, - "origin": "source", - "typeParameters": null, - "returns": "InvoiceLineItem.Builder", - "params": [ - { - "type": "String", - "name": "quantity" - } - ] }, { "kind": "method", - "name": "unitPrice", + "name": "status", "static": false, - "origin": "source", + "origin": "generated", "typeParameters": null, - "returns": "InvoiceLineItem.Builder", - "params": [ - { - "type": "String", - "name": "unitPrice" - } - ] + "returns": "String", + "params": [] }, { "kind": "method", - "name": "amount", + "name": "fromParty", "static": false, - "origin": "source", + "origin": "generated", "typeParameters": null, - "returns": "InvoiceLineItem.Builder", - "params": [ - { - "type": "String", - "name": "amount" - } - ] + "returns": "InvoiceParty", + "params": [] }, { "kind": "method", - "name": "build", - "static": false, - "origin": "source", - "typeParameters": null, - "returns": "InvoiceLineItem", - "params": [] - } - ] - }, - { - "name": "InvoiceParty", - "binaryName": "com.demcha.compose.document.templates.data.invoice.InvoiceParty", - "kind": "record", - "modifiers": [ - "final" - ], - "artifact": "graph-compose-templates", - "members": [ - { - "kind": "constructor", - "name": "InvoiceParty", + "name": "billToParty", "static": false, "origin": "generated", "typeParameters": null, - "returns": null, - "params": [ - { - "type": "String", - "name": null - }, - { - "type": "List", - "name": null - }, - { - "type": "String", - "name": null - }, - { - "type": "String", - "name": null - }, - { - "type": "String", - "name": null - } - ] - }, - { - "kind": "method", - "name": "builder", - "static": true, - "origin": "source", - "typeParameters": null, - "returns": "InvoiceParty.Builder", + "returns": "InvoiceParty", "params": [] }, { "kind": "method", - "name": "name", + "name": "lineItems", "static": false, "origin": "generated", "typeParameters": null, - "returns": "String", + "returns": "List", "params": [] }, { "kind": "method", - "name": "addressLines", + "name": "summaryRows", "static": false, "origin": "generated", "typeParameters": null, - "returns": "List", + "returns": "List", "params": [] }, { "kind": "method", - "name": "email", + "name": "notes", "static": false, "origin": "generated", "typeParameters": null, - "returns": "String", + "returns": "List", "params": [] }, { "kind": "method", - "name": "phone", + "name": "paymentTerms", "static": false, "origin": "generated", "typeParameters": null, - "returns": "String", + "returns": "List", "params": [] }, { "kind": "method", - "name": "taxId", + "name": "footerNote", "static": false, "origin": "generated", "typeParameters": null, @@ -12142,8 +12287,8 @@ ] }, { - "name": "InvoiceParty.Builder", - "binaryName": "com.demcha.compose.document.templates.data.invoice.InvoiceParty$Builder", + "name": "InvoiceData.Builder", + "binaryName": "com.demcha.compose.document.templates.data.invoice.InvoiceData$Builder", "kind": "class", "modifiers": [ "final" @@ -12152,451 +12297,435 @@ "members": [ { "kind": "method", - "name": "name", + "name": "title", "static": false, "origin": "source", "typeParameters": null, - "returns": "InvoiceParty.Builder", + "returns": "InvoiceData.Builder", "params": [ { "type": "String", - "name": "name" + "name": "title" } ] }, { "kind": "method", - "name": "addressLines", + "name": "invoiceNumber", "static": false, "origin": "source", "typeParameters": null, - "returns": "InvoiceParty.Builder", + "returns": "InvoiceData.Builder", "params": [ { - "type": "List", - "name": "addressLines" + "type": "String", + "name": "invoiceNumber" } ] }, { "kind": "method", - "name": "addressLines", + "name": "issueDate", "static": false, "origin": "source", "typeParameters": null, - "returns": "InvoiceParty.Builder", + "returns": "InvoiceData.Builder", "params": [ { - "type": "String...", - "name": "addressLines" + "type": "String", + "name": "issueDate" } ] }, { "kind": "method", - "name": "addAddressLine", + "name": "dueDate", "static": false, "origin": "source", "typeParameters": null, - "returns": "InvoiceParty.Builder", + "returns": "InvoiceData.Builder", "params": [ { "type": "String", - "name": "addressLine" + "name": "dueDate" } ] }, { "kind": "method", - "name": "email", + "name": "reference", "static": false, "origin": "source", "typeParameters": null, - "returns": "InvoiceParty.Builder", + "returns": "InvoiceData.Builder", "params": [ { "type": "String", - "name": "email" + "name": "reference" } ] }, { "kind": "method", - "name": "phone", + "name": "status", "static": false, "origin": "source", "typeParameters": null, - "returns": "InvoiceParty.Builder", + "returns": "InvoiceData.Builder", "params": [ { "type": "String", - "name": "phone" + "name": "status" } ] }, { "kind": "method", - "name": "taxId", + "name": "fromParty", "static": false, "origin": "source", "typeParameters": null, - "returns": "InvoiceParty.Builder", + "returns": "InvoiceData.Builder", "params": [ { - "type": "String", - "name": "taxId" + "type": "InvoiceParty", + "name": "fromParty" } ] }, { "kind": "method", - "name": "build", + "name": "fromParty", "static": false, "origin": "source", "typeParameters": null, - "returns": "InvoiceParty", - "params": [] - } - ] - }, - { - "name": "InvoiceSummaryRow", - "binaryName": "com.demcha.compose.document.templates.data.invoice.InvoiceSummaryRow", - "kind": "record", - "modifiers": [ - "final" - ], - "artifact": "graph-compose-templates", - "members": [ - { - "kind": "constructor", - "name": "InvoiceSummaryRow", - "static": false, - "origin": "generated", - "typeParameters": null, - "returns": null, + "returns": "InvoiceData.Builder", "params": [ { - "type": "String", - "name": null - }, - { - "type": "String", - "name": null - }, - { - "type": "boolean", - "name": null + "type": "Consumer", + "name": "spec" } ] }, { "kind": "method", - "name": "builder", - "static": true, - "origin": "source", - "typeParameters": null, - "returns": "InvoiceSummaryRow.Builder", - "params": [] - }, - { - "kind": "method", - "name": "label", + "name": "billToParty", "static": false, - "origin": "generated", + "origin": "source", "typeParameters": null, - "returns": "String", - "params": [] + "returns": "InvoiceData.Builder", + "params": [ + { + "type": "InvoiceParty", + "name": "billToParty" + } + ] }, { "kind": "method", - "name": "value", + "name": "billToParty", "static": false, - "origin": "generated", + "origin": "source", "typeParameters": null, - "returns": "String", - "params": [] + "returns": "InvoiceData.Builder", + "params": [ + { + "type": "Consumer", + "name": "spec" + } + ] }, { "kind": "method", - "name": "emphasized", - "static": false, - "origin": "generated", - "typeParameters": null, - "returns": "boolean", - "params": [] - } - ] - }, - { - "name": "InvoiceSummaryRow.Builder", - "binaryName": "com.demcha.compose.document.templates.data.invoice.InvoiceSummaryRow$Builder", - "kind": "class", - "modifiers": [ - "final" - ], - "artifact": "graph-compose-templates", - "members": [ - { - "kind": "method", - "name": "label", + "name": "lineItems", "static": false, "origin": "source", "typeParameters": null, - "returns": "InvoiceSummaryRow.Builder", + "returns": "InvoiceData.Builder", "params": [ { - "type": "String", - "name": "label" + "type": "List", + "name": "lineItems" } ] }, { "kind": "method", - "name": "value", + "name": "addLineItem", "static": false, "origin": "source", "typeParameters": null, - "returns": "InvoiceSummaryRow.Builder", + "returns": "InvoiceData.Builder", "params": [ { - "type": "String", - "name": "value" + "type": "InvoiceLineItem", + "name": "lineItem" } ] }, { "kind": "method", - "name": "emphasized", + "name": "lineItem", "static": false, "origin": "source", "typeParameters": null, - "returns": "InvoiceSummaryRow.Builder", + "returns": "InvoiceData.Builder", "params": [ { - "type": "boolean", - "name": "emphasized" + "type": "Consumer", + "name": "spec" } ] }, { "kind": "method", - "name": "build", + "name": "lineItem", "static": false, "origin": "source", "typeParameters": null, - "returns": "InvoiceSummaryRow", - "params": [] - } - ] - } - ] - }, - { - "name": "com.demcha.compose.document.templates.data.proposal", - "types": [ - { - "name": "ProposalData", - "binaryName": "com.demcha.compose.document.templates.data.proposal.ProposalData", - "kind": "record", - "modifiers": [ - "final" - ], - "artifact": "graph-compose-templates", - "members": [ - { - "kind": "constructor", - "name": "ProposalData", - "static": false, - "origin": "generated", - "typeParameters": null, - "returns": null, + "returns": "InvoiceData.Builder", "params": [ { "type": "String", - "name": null - }, - { - "type": "String", - "name": null - }, - { - "type": "String", - "name": null + "name": "description" }, { "type": "String", - "name": null + "name": "details" }, { "type": "String", - "name": null + "name": "quantity" }, { "type": "String", - "name": null - }, - { - "type": "ProposalParty", - "name": null - }, - { - "type": "ProposalParty", - "name": null - }, - { - "type": "List", - "name": null - }, - { - "type": "List", - "name": null - }, - { - "type": "List", - "name": null - }, - { - "type": "List", - "name": null + "name": "unitPrice" }, { "type": "String", - "name": null + "name": "amount" } ] }, { "kind": "method", - "name": "builder", - "static": true, + "name": "summaryRows", + "static": false, "origin": "source", "typeParameters": null, - "returns": "ProposalData.Builder", - "params": [] + "returns": "InvoiceData.Builder", + "params": [ + { + "type": "List", + "name": "summaryRows" + } + ] }, { "kind": "method", - "name": "title", + "name": "addSummaryRow", "static": false, - "origin": "generated", + "origin": "source", "typeParameters": null, - "returns": "String", - "params": [] + "returns": "InvoiceData.Builder", + "params": [ + { + "type": "InvoiceSummaryRow", + "name": "summaryRow" + } + ] }, { "kind": "method", - "name": "proposalNumber", + "name": "summaryRow", "static": false, - "origin": "generated", + "origin": "source", "typeParameters": null, - "returns": "String", - "params": [] + "returns": "InvoiceData.Builder", + "params": [ + { + "type": "Consumer", + "name": "spec" + } + ] }, { "kind": "method", - "name": "preparedDate", + "name": "summaryRow", "static": false, - "origin": "generated", + "origin": "source", "typeParameters": null, - "returns": "String", - "params": [] + "returns": "InvoiceData.Builder", + "params": [ + { + "type": "String", + "name": "label" + }, + { + "type": "String", + "name": "value" + } + ] }, { "kind": "method", - "name": "validUntil", + "name": "totalRow", "static": false, - "origin": "generated", + "origin": "source", "typeParameters": null, - "returns": "String", - "params": [] + "returns": "InvoiceData.Builder", + "params": [ + { + "type": "String", + "name": "label" + }, + { + "type": "String", + "name": "value" + } + ] }, { "kind": "method", - "name": "projectTitle", + "name": "notes", "static": false, - "origin": "generated", + "origin": "source", "typeParameters": null, - "returns": "String", - "params": [] + "returns": "InvoiceData.Builder", + "params": [ + { + "type": "List", + "name": "notes" + } + ] }, { "kind": "method", - "name": "executiveSummary", + "name": "note", "static": false, - "origin": "generated", + "origin": "source", "typeParameters": null, - "returns": "String", - "params": [] + "returns": "InvoiceData.Builder", + "params": [ + { + "type": "String", + "name": "note" + } + ] }, { "kind": "method", - "name": "sender", + "name": "paymentTerms", "static": false, - "origin": "generated", + "origin": "source", "typeParameters": null, - "returns": "ProposalParty", - "params": [] + "returns": "InvoiceData.Builder", + "params": [ + { + "type": "List", + "name": "paymentTerms" + } + ] }, { "kind": "method", - "name": "recipient", + "name": "paymentTerm", "static": false, - "origin": "generated", + "origin": "source", "typeParameters": null, - "returns": "ProposalParty", - "params": [] + "returns": "InvoiceData.Builder", + "params": [ + { + "type": "String", + "name": "paymentTerm" + } + ] }, { "kind": "method", - "name": "sections", + "name": "footerNote", "static": false, - "origin": "generated", + "origin": "source", "typeParameters": null, - "returns": "List", - "params": [] + "returns": "InvoiceData.Builder", + "params": [ + { + "type": "String", + "name": "footerNote" + } + ] }, { "kind": "method", - "name": "timeline", + "name": "build", "static": false, - "origin": "generated", + "origin": "source", "typeParameters": null, - "returns": "List", + "returns": "InvoiceData", "params": [] - }, + } + ] + }, + { + "name": "InvoiceDocumentSpec", + "binaryName": "com.demcha.compose.document.templates.data.invoice.InvoiceDocumentSpec", + "kind": "record", + "modifiers": [ + "final" + ], + "artifact": "graph-compose-templates", + "members": [ { - "kind": "method", - "name": "pricingRows", + "kind": "constructor", + "name": "InvoiceDocumentSpec", "static": false, "origin": "generated", "typeParameters": null, - "returns": "List", - "params": [] + "returns": null, + "params": [ + { + "type": "InvoiceData", + "name": null + } + ] }, { "kind": "method", - "name": "acceptanceTerms", - "static": false, - "origin": "generated", + "name": "from", + "static": true, + "origin": "source", "typeParameters": null, - "returns": "List", + "returns": "InvoiceDocumentSpec", + "params": [ + { + "type": "InvoiceData", + "name": "invoice" + } + ] + }, + { + "kind": "method", + "name": "builder", + "static": true, + "origin": "source", + "typeParameters": null, + "returns": "InvoiceDocumentSpec.Builder", "params": [] }, { "kind": "method", - "name": "footerNote", + "name": "invoice", "static": false, "origin": "generated", "typeParameters": null, - "returns": "String", + "returns": "InvoiceData", "params": [] } ] }, { - "name": "ProposalData.Builder", - "binaryName": "com.demcha.compose.document.templates.data.proposal.ProposalData$Builder", + "name": "InvoiceDocumentSpec.Builder", + "binaryName": "com.demcha.compose.document.templates.data.invoice.InvoiceDocumentSpec$Builder", "kind": "class", "modifiers": [ "final" @@ -12609,7 +12738,7 @@ "static": false, "origin": "source", "typeParameters": null, - "returns": "ProposalData.Builder", + "returns": "InvoiceDocumentSpec.Builder", "params": [ { "type": "String", @@ -12619,365 +12748,333 @@ }, { "kind": "method", - "name": "proposalNumber", + "name": "invoiceNumber", "static": false, "origin": "source", "typeParameters": null, - "returns": "ProposalData.Builder", + "returns": "InvoiceDocumentSpec.Builder", "params": [ { "type": "String", - "name": "proposalNumber" + "name": "invoiceNumber" } ] }, { "kind": "method", - "name": "preparedDate", + "name": "issueDate", "static": false, "origin": "source", "typeParameters": null, - "returns": "ProposalData.Builder", + "returns": "InvoiceDocumentSpec.Builder", "params": [ { "type": "String", - "name": "preparedDate" + "name": "issueDate" } ] }, { "kind": "method", - "name": "validUntil", + "name": "dueDate", "static": false, "origin": "source", "typeParameters": null, - "returns": "ProposalData.Builder", + "returns": "InvoiceDocumentSpec.Builder", "params": [ { "type": "String", - "name": "validUntil" + "name": "dueDate" } ] }, { "kind": "method", - "name": "projectTitle", + "name": "reference", "static": false, "origin": "source", "typeParameters": null, - "returns": "ProposalData.Builder", + "returns": "InvoiceDocumentSpec.Builder", "params": [ { "type": "String", - "name": "projectTitle" + "name": "reference" } ] }, { "kind": "method", - "name": "executiveSummary", + "name": "status", "static": false, "origin": "source", "typeParameters": null, - "returns": "ProposalData.Builder", + "returns": "InvoiceDocumentSpec.Builder", "params": [ { "type": "String", - "name": "executiveSummary" + "name": "status" } ] }, { "kind": "method", - "name": "sender", + "name": "fromParty", "static": false, "origin": "source", "typeParameters": null, - "returns": "ProposalData.Builder", + "returns": "InvoiceDocumentSpec.Builder", "params": [ { - "type": "ProposalParty", - "name": "sender" + "type": "InvoiceParty", + "name": "fromParty" } ] }, { "kind": "method", - "name": "sender", + "name": "fromParty", "static": false, "origin": "source", "typeParameters": null, - "returns": "ProposalData.Builder", + "returns": "InvoiceDocumentSpec.Builder", "params": [ { - "type": "Consumer", + "type": "Consumer", "name": "spec" } ] }, { "kind": "method", - "name": "recipient", + "name": "billToParty", "static": false, "origin": "source", "typeParameters": null, - "returns": "ProposalData.Builder", + "returns": "InvoiceDocumentSpec.Builder", "params": [ { - "type": "ProposalParty", - "name": "recipient" + "type": "InvoiceParty", + "name": "billToParty" } ] }, { "kind": "method", - "name": "recipient", + "name": "billToParty", "static": false, "origin": "source", "typeParameters": null, - "returns": "ProposalData.Builder", + "returns": "InvoiceDocumentSpec.Builder", "params": [ { - "type": "Consumer", + "type": "Consumer", "name": "spec" } ] }, { "kind": "method", - "name": "sections", + "name": "lineItems", "static": false, "origin": "source", "typeParameters": null, - "returns": "ProposalData.Builder", + "returns": "InvoiceDocumentSpec.Builder", "params": [ { - "type": "List", - "name": "sections" + "type": "List", + "name": "lineItems" } ] }, { "kind": "method", - "name": "addSection", + "name": "addLineItem", "static": false, "origin": "source", "typeParameters": null, - "returns": "ProposalData.Builder", + "returns": "InvoiceDocumentSpec.Builder", "params": [ { - "type": "ProposalSection", - "name": "section" + "type": "InvoiceLineItem", + "name": "lineItem" } ] }, { "kind": "method", - "name": "section", + "name": "lineItem", "static": false, "origin": "source", "typeParameters": null, - "returns": "ProposalData.Builder", + "returns": "InvoiceDocumentSpec.Builder", "params": [ { - "type": "Consumer", + "type": "Consumer", "name": "spec" } ] }, { "kind": "method", - "name": "section", + "name": "lineItem", "static": false, "origin": "source", "typeParameters": null, - "returns": "ProposalData.Builder", + "returns": "InvoiceDocumentSpec.Builder", "params": [ { "type": "String", - "name": "title" + "name": "description" }, { - "type": "String...", - "name": "paragraphs" - } - ] - }, - { - "kind": "method", - "name": "timeline", - "static": false, - "origin": "source", - "typeParameters": null, - "returns": "ProposalData.Builder", - "params": [ + "type": "String", + "name": "details" + }, { - "type": "List", - "name": "timeline" - } - ] - }, - { - "kind": "method", - "name": "addTimelineItem", - "static": false, - "origin": "source", - "typeParameters": null, - "returns": "ProposalData.Builder", - "params": [ + "type": "String", + "name": "quantity" + }, { - "type": "ProposalTimelineItem", - "name": "item" + "type": "String", + "name": "unitPrice" + }, + { + "type": "String", + "name": "amount" } ] }, { "kind": "method", - "name": "timelineItem", + "name": "summaryRows", "static": false, "origin": "source", "typeParameters": null, - "returns": "ProposalData.Builder", + "returns": "InvoiceDocumentSpec.Builder", "params": [ { - "type": "Consumer", - "name": "spec" + "type": "List", + "name": "summaryRows" } ] }, { "kind": "method", - "name": "timelineItem", + "name": "addSummaryRow", "static": false, "origin": "source", "typeParameters": null, - "returns": "ProposalData.Builder", + "returns": "InvoiceDocumentSpec.Builder", "params": [ { - "type": "String", - "name": "phase" - }, - { - "type": "String", - "name": "duration" - }, - { - "type": "String", - "name": "details" + "type": "InvoiceSummaryRow", + "name": "summaryRow" } ] }, { "kind": "method", - "name": "pricingRows", + "name": "summaryRow", "static": false, "origin": "source", "typeParameters": null, - "returns": "ProposalData.Builder", + "returns": "InvoiceDocumentSpec.Builder", "params": [ { - "type": "List", - "name": "pricingRows" + "type": "Consumer", + "name": "spec" } ] }, { "kind": "method", - "name": "addPricingRow", + "name": "summaryRow", "static": false, "origin": "source", "typeParameters": null, - "returns": "ProposalData.Builder", + "returns": "InvoiceDocumentSpec.Builder", "params": [ { - "type": "ProposalPricingRow", - "name": "pricingRow" + "type": "String", + "name": "label" + }, + { + "type": "String", + "name": "value" } ] }, { "kind": "method", - "name": "pricingRow", + "name": "totalRow", "static": false, "origin": "source", "typeParameters": null, - "returns": "ProposalData.Builder", + "returns": "InvoiceDocumentSpec.Builder", "params": [ { - "type": "Consumer", - "name": "spec" + "type": "String", + "name": "label" + }, + { + "type": "String", + "name": "value" } ] }, { "kind": "method", - "name": "pricingRow", + "name": "notes", "static": false, "origin": "source", "typeParameters": null, - "returns": "ProposalData.Builder", + "returns": "InvoiceDocumentSpec.Builder", "params": [ { - "type": "String", - "name": "label" - }, - { - "type": "String", - "name": "description" - }, - { - "type": "String", - "name": "amount" + "type": "List", + "name": "notes" } ] }, { "kind": "method", - "name": "emphasizedPricingRow", + "name": "note", "static": false, "origin": "source", "typeParameters": null, - "returns": "ProposalData.Builder", + "returns": "InvoiceDocumentSpec.Builder", "params": [ { "type": "String", - "name": "label" - }, - { - "type": "String", - "name": "description" - }, - { - "type": "String", - "name": "amount" + "name": "note" } ] }, { "kind": "method", - "name": "acceptanceTerms", + "name": "paymentTerms", "static": false, "origin": "source", "typeParameters": null, - "returns": "ProposalData.Builder", + "returns": "InvoiceDocumentSpec.Builder", "params": [ { "type": "List", - "name": "acceptanceTerms" + "name": "paymentTerms" } ] }, { "kind": "method", - "name": "acceptanceTerm", + "name": "paymentTerm", "static": false, "origin": "source", "typeParameters": null, - "returns": "ProposalData.Builder", + "returns": "InvoiceDocumentSpec.Builder", "params": [ { "type": "String", - "name": "acceptanceTerm" + "name": "paymentTerm" } ] }, @@ -12987,7 +13084,7 @@ "static": false, "origin": "source", "typeParameters": null, - "returns": "ProposalData.Builder", + "returns": "InvoiceDocumentSpec.Builder", "params": [ { "type": "String", @@ -13001,14 +13098,14 @@ "static": false, "origin": "source", "typeParameters": null, - "returns": "ProposalData", + "returns": "InvoiceDocumentSpec", "params": [] } ] }, { - "name": "ProposalDocumentSpec", - "binaryName": "com.demcha.compose.document.templates.data.proposal.ProposalDocumentSpec", + "name": "InvoiceLineItem", + "binaryName": "com.demcha.compose.document.templates.data.invoice.InvoiceLineItem", "kind": "record", "modifiers": [ "final" @@ -13017,55 +13114,93 @@ "members": [ { "kind": "constructor", - "name": "ProposalDocumentSpec", + "name": "InvoiceLineItem", "static": false, "origin": "generated", "typeParameters": null, "returns": null, "params": [ { - "type": "ProposalData", + "type": "String", + "name": null + }, + { + "type": "String", + "name": null + }, + { + "type": "String", + "name": null + }, + { + "type": "String", + "name": null + }, + { + "type": "String", "name": null } ] }, { "kind": "method", - "name": "from", + "name": "builder", "static": true, "origin": "source", "typeParameters": null, - "returns": "ProposalDocumentSpec", - "params": [ - { - "type": "ProposalData", - "name": "proposal" - } - ] + "returns": "InvoiceLineItem.Builder", + "params": [] }, { "kind": "method", - "name": "builder", - "static": true, - "origin": "source", + "name": "description", + "static": false, + "origin": "generated", "typeParameters": null, - "returns": "ProposalDocumentSpec.Builder", + "returns": "String", "params": [] }, { "kind": "method", - "name": "proposal", + "name": "details", "static": false, "origin": "generated", "typeParameters": null, - "returns": "ProposalData", + "returns": "String", + "params": [] + }, + { + "kind": "method", + "name": "quantity", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "String", + "params": [] + }, + { + "kind": "method", + "name": "unitPrice", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "String", + "params": [] + }, + { + "kind": "method", + "name": "amount", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "String", "params": [] } ] }, { - "name": "ProposalDocumentSpec.Builder", - "binaryName": "com.demcha.compose.document.templates.data.proposal.ProposalDocumentSpec$Builder", + "name": "InvoiceLineItem.Builder", + "binaryName": "com.demcha.compose.document.templates.data.invoice.InvoiceLineItem$Builder", "kind": "class", "modifiers": [ "final" @@ -13074,467 +13209,325 @@ "members": [ { "kind": "method", - "name": "title", + "name": "description", "static": false, "origin": "source", "typeParameters": null, - "returns": "ProposalDocumentSpec.Builder", + "returns": "InvoiceLineItem.Builder", "params": [ { "type": "String", - "name": "title" + "name": "description" } ] }, { "kind": "method", - "name": "proposalNumber", + "name": "details", "static": false, "origin": "source", "typeParameters": null, - "returns": "ProposalDocumentSpec.Builder", + "returns": "InvoiceLineItem.Builder", "params": [ { "type": "String", - "name": "proposalNumber" + "name": "details" } ] }, { "kind": "method", - "name": "preparedDate", + "name": "quantity", "static": false, "origin": "source", "typeParameters": null, - "returns": "ProposalDocumentSpec.Builder", + "returns": "InvoiceLineItem.Builder", "params": [ { "type": "String", - "name": "preparedDate" + "name": "quantity" } ] }, { "kind": "method", - "name": "validUntil", + "name": "unitPrice", "static": false, "origin": "source", "typeParameters": null, - "returns": "ProposalDocumentSpec.Builder", + "returns": "InvoiceLineItem.Builder", "params": [ { "type": "String", - "name": "validUntil" + "name": "unitPrice" } ] }, { "kind": "method", - "name": "projectTitle", + "name": "amount", "static": false, "origin": "source", "typeParameters": null, - "returns": "ProposalDocumentSpec.Builder", + "returns": "InvoiceLineItem.Builder", "params": [ { "type": "String", - "name": "projectTitle" + "name": "amount" } ] }, { "kind": "method", - "name": "executiveSummary", + "name": "build", "static": false, "origin": "source", "typeParameters": null, - "returns": "ProposalDocumentSpec.Builder", - "params": [ - { - "type": "String", - "name": "executiveSummary" - } - ] - }, - { - "kind": "method", - "name": "sender", - "static": false, - "origin": "source", + "returns": "InvoiceLineItem", + "params": [] + } + ] + }, + { + "name": "InvoiceMasthead", + "binaryName": "com.demcha.compose.document.templates.data.invoice.InvoiceMasthead", + "kind": "record", + "modifiers": [ + "final" + ], + "artifact": "graph-compose-templates", + "members": [ + { + "kind": "constructor", + "name": "InvoiceMasthead", + "static": false, + "origin": "generated", "typeParameters": null, - "returns": "ProposalDocumentSpec.Builder", + "returns": null, "params": [ { - "type": "ProposalParty", - "name": "sender" + "type": "String", + "name": null + }, + { + "type": "List", + "name": null } ] }, { "kind": "method", - "name": "sender", + "name": "title", "static": false, - "origin": "source", + "origin": "generated", "typeParameters": null, - "returns": "ProposalDocumentSpec.Builder", - "params": [ - { - "type": "Consumer", - "name": "spec" - } - ] + "returns": "String", + "params": [] }, { "kind": "method", - "name": "recipient", + "name": "entries", "static": false, - "origin": "source", + "origin": "generated", "typeParameters": null, - "returns": "ProposalDocumentSpec.Builder", - "params": [ - { - "type": "ProposalParty", - "name": "recipient" - } - ] - }, + "returns": "List", + "params": [] + } + ] + }, + { + "name": "InvoiceMasthead.Entry", + "binaryName": "com.demcha.compose.document.templates.data.invoice.InvoiceMasthead$Entry", + "kind": "record", + "modifiers": [ + "final" + ], + "artifact": "graph-compose-templates", + "members": [ { - "kind": "method", - "name": "recipient", + "kind": "constructor", + "name": "Entry", "static": false, - "origin": "source", + "origin": "generated", "typeParameters": null, - "returns": "ProposalDocumentSpec.Builder", + "returns": null, "params": [ { - "type": "Consumer", - "name": "spec" + "type": "String", + "name": null + }, + { + "type": "String", + "name": null + }, + { + "type": "boolean", + "name": null } ] }, { "kind": "method", - "name": "sections", + "name": "label", "static": false, - "origin": "source", + "origin": "generated", "typeParameters": null, - "returns": "ProposalDocumentSpec.Builder", - "params": [ - { - "type": "List", - "name": "sections" - } - ] + "returns": "String", + "params": [] }, { "kind": "method", - "name": "addSection", + "name": "value", "static": false, - "origin": "source", + "origin": "generated", "typeParameters": null, - "returns": "ProposalDocumentSpec.Builder", - "params": [ - { - "type": "ProposalSection", - "name": "section" - } - ] + "returns": "String", + "params": [] }, { "kind": "method", - "name": "section", + "name": "emphasized", "static": false, - "origin": "source", + "origin": "generated", "typeParameters": null, - "returns": "ProposalDocumentSpec.Builder", - "params": [ - { - "type": "Consumer", - "name": "spec" - } - ] - }, + "returns": "boolean", + "params": [] + } + ] + }, + { + "name": "InvoiceNotesBlock", + "binaryName": "com.demcha.compose.document.templates.data.invoice.InvoiceNotesBlock", + "kind": "record", + "modifiers": [ + "final" + ], + "artifact": "graph-compose-templates", + "members": [ { - "kind": "method", - "name": "section", + "kind": "constructor", + "name": "InvoiceNotesBlock", "static": false, - "origin": "source", + "origin": "generated", "typeParameters": null, - "returns": "ProposalDocumentSpec.Builder", + "returns": null, "params": [ { "type": "String", - "name": "title" + "name": null }, { - "type": "String...", - "name": "paragraphs" + "type": "List", + "name": null + }, + { + "type": "String", + "name": null + }, + { + "type": "String", + "name": null } ] }, { "kind": "method", - "name": "timeline", + "name": "heading", "static": false, - "origin": "source", + "origin": "generated", "typeParameters": null, - "returns": "ProposalDocumentSpec.Builder", - "params": [ - { - "type": "List", - "name": "timeline" - } - ] + "returns": "String", + "params": [] }, { "kind": "method", - "name": "addTimelineItem", + "name": "paragraphs", "static": false, - "origin": "source", + "origin": "generated", "typeParameters": null, - "returns": "ProposalDocumentSpec.Builder", - "params": [ - { - "type": "ProposalTimelineItem", - "name": "item" - } - ] + "returns": "List", + "params": [] }, { "kind": "method", - "name": "timelineItem", + "name": "contactEmail", "static": false, - "origin": "source", + "origin": "generated", "typeParameters": null, - "returns": "ProposalDocumentSpec.Builder", - "params": [ - { - "type": "Consumer", - "name": "spec" - } - ] + "returns": "String", + "params": [] }, { "kind": "method", - "name": "timelineItem", + "name": "contactPhone", "static": false, - "origin": "source", + "origin": "generated", "typeParameters": null, - "returns": "ProposalDocumentSpec.Builder", + "returns": "String", + "params": [] + } + ] + }, + { + "name": "InvoiceParty", + "binaryName": "com.demcha.compose.document.templates.data.invoice.InvoiceParty", + "kind": "record", + "modifiers": [ + "final" + ], + "artifact": "graph-compose-templates", + "members": [ + { + "kind": "constructor", + "name": "InvoiceParty", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": null, "params": [ { "type": "String", - "name": "phase" + "name": null + }, + { + "type": "List", + "name": null }, { "type": "String", - "name": "duration" + "name": null }, { "type": "String", - "name": "details" + "name": null + }, + { + "type": "String", + "name": null } ] }, { "kind": "method", - "name": "pricingRows", - "static": false, + "name": "builder", + "static": true, "origin": "source", "typeParameters": null, - "returns": "ProposalDocumentSpec.Builder", - "params": [ - { - "type": "List", - "name": "pricingRows" - } - ] + "returns": "InvoiceParty.Builder", + "params": [] }, { "kind": "method", - "name": "addPricingRow", + "name": "name", "static": false, - "origin": "source", + "origin": "generated", "typeParameters": null, - "returns": "ProposalDocumentSpec.Builder", - "params": [ - { - "type": "ProposalPricingRow", - "name": "pricingRow" - } - ] + "returns": "String", + "params": [] }, { "kind": "method", - "name": "pricingRow", - "static": false, - "origin": "source", - "typeParameters": null, - "returns": "ProposalDocumentSpec.Builder", - "params": [ - { - "type": "Consumer", - "name": "spec" - } - ] - }, - { - "kind": "method", - "name": "pricingRow", - "static": false, - "origin": "source", - "typeParameters": null, - "returns": "ProposalDocumentSpec.Builder", - "params": [ - { - "type": "String", - "name": "label" - }, - { - "type": "String", - "name": "description" - }, - { - "type": "String", - "name": "amount" - } - ] - }, - { - "kind": "method", - "name": "emphasizedPricingRow", - "static": false, - "origin": "source", - "typeParameters": null, - "returns": "ProposalDocumentSpec.Builder", - "params": [ - { - "type": "String", - "name": "label" - }, - { - "type": "String", - "name": "description" - }, - { - "type": "String", - "name": "amount" - } - ] - }, - { - "kind": "method", - "name": "acceptanceTerms", - "static": false, - "origin": "source", - "typeParameters": null, - "returns": "ProposalDocumentSpec.Builder", - "params": [ - { - "type": "List", - "name": "acceptanceTerms" - } - ] - }, - { - "kind": "method", - "name": "acceptanceTerm", - "static": false, - "origin": "source", - "typeParameters": null, - "returns": "ProposalDocumentSpec.Builder", - "params": [ - { - "type": "String", - "name": "acceptanceTerm" - } - ] - }, - { - "kind": "method", - "name": "footerNote", - "static": false, - "origin": "source", - "typeParameters": null, - "returns": "ProposalDocumentSpec.Builder", - "params": [ - { - "type": "String", - "name": "footerNote" - } - ] - }, - { - "kind": "method", - "name": "build", - "static": false, - "origin": "source", - "typeParameters": null, - "returns": "ProposalDocumentSpec", - "params": [] - } - ] - }, - { - "name": "ProposalParty", - "binaryName": "com.demcha.compose.document.templates.data.proposal.ProposalParty", - "kind": "record", - "modifiers": [ - "final" - ], - "artifact": "graph-compose-templates", - "members": [ - { - "kind": "constructor", - "name": "ProposalParty", - "static": false, - "origin": "generated", - "typeParameters": null, - "returns": null, - "params": [ - { - "type": "String", - "name": null - }, - { - "type": "List", - "name": null - }, - { - "type": "String", - "name": null - }, - { - "type": "String", - "name": null - }, - { - "type": "String", - "name": null - } - ] - }, - { - "kind": "method", - "name": "builder", - "static": true, - "origin": "source", - "typeParameters": null, - "returns": "ProposalParty.Builder", - "params": [] - }, - { - "kind": "method", - "name": "name", - "static": false, - "origin": "generated", - "typeParameters": null, - "returns": "String", - "params": [] - }, - { - "kind": "method", - "name": "addressLines", + "name": "addressLines", "static": false, "origin": "generated", "typeParameters": null, @@ -13561,7 +13554,7 @@ }, { "kind": "method", - "name": "website", + "name": "taxId", "static": false, "origin": "generated", "typeParameters": null, @@ -13571,8 +13564,8 @@ ] }, { - "name": "ProposalParty.Builder", - "binaryName": "com.demcha.compose.document.templates.data.proposal.ProposalParty$Builder", + "name": "InvoiceParty.Builder", + "binaryName": "com.demcha.compose.document.templates.data.invoice.InvoiceParty$Builder", "kind": "class", "modifiers": [ "final" @@ -13585,7 +13578,7 @@ "static": false, "origin": "source", "typeParameters": null, - "returns": "ProposalParty.Builder", + "returns": "InvoiceParty.Builder", "params": [ { "type": "String", @@ -13599,7 +13592,7 @@ "static": false, "origin": "source", "typeParameters": null, - "returns": "ProposalParty.Builder", + "returns": "InvoiceParty.Builder", "params": [ { "type": "List", @@ -13613,7 +13606,7 @@ "static": false, "origin": "source", "typeParameters": null, - "returns": "ProposalParty.Builder", + "returns": "InvoiceParty.Builder", "params": [ { "type": "String...", @@ -13627,7 +13620,7 @@ "static": false, "origin": "source", "typeParameters": null, - "returns": "ProposalParty.Builder", + "returns": "InvoiceParty.Builder", "params": [ { "type": "String", @@ -13641,7 +13634,7 @@ "static": false, "origin": "source", "typeParameters": null, - "returns": "ProposalParty.Builder", + "returns": "InvoiceParty.Builder", "params": [ { "type": "String", @@ -13655,7 +13648,7 @@ "static": false, "origin": "source", "typeParameters": null, - "returns": "ProposalParty.Builder", + "returns": "InvoiceParty.Builder", "params": [ { "type": "String", @@ -13665,15 +13658,15 @@ }, { "kind": "method", - "name": "website", + "name": "taxId", "static": false, "origin": "source", "typeParameters": null, - "returns": "ProposalParty.Builder", + "returns": "InvoiceParty.Builder", "params": [ { "type": "String", - "name": "website" + "name": "taxId" } ] }, @@ -13683,14 +13676,14 @@ "static": false, "origin": "source", "typeParameters": null, - "returns": "ProposalParty", + "returns": "InvoiceParty", "params": [] } ] }, { - "name": "ProposalPricingRow", - "binaryName": "com.demcha.compose.document.templates.data.proposal.ProposalPricingRow", + "name": "InvoicePaymentBlock", + "binaryName": "com.demcha.compose.document.templates.data.invoice.InvoicePaymentBlock", "kind": "record", "modifiers": [ "final" @@ -13699,7 +13692,7 @@ "members": [ { "kind": "constructor", - "name": "ProposalPricingRow", + "name": "InvoicePaymentBlock", "static": false, "origin": "generated", "typeParameters": null, @@ -13709,6 +13702,10 @@ "type": "String", "name": null }, + { + "type": "List", + "name": null + }, { "type": "String", "name": null @@ -13718,23 +13715,52 @@ "name": null }, { - "type": "boolean", + "type": "String", + "name": null + }, + { + "type": "String", + "name": null + }, + { + "type": "String", "name": null } ] }, { - "kind": "method", - "name": "builder", - "static": true, + "kind": "constructor", + "name": "InvoicePaymentBlock", + "static": false, "origin": "source", "typeParameters": null, - "returns": "ProposalPricingRow.Builder", - "params": [] + "returns": null, + "params": [ + { + "type": "String", + "name": "heading" + }, + { + "type": "List", + "name": "fields" + }, + { + "type": "String", + "name": "instruction" + }, + { + "type": "String", + "name": "dueNotice" + }, + { + "type": "String", + "name": "dueNoticeEmphasis" + } + ] }, { "kind": "method", - "name": "label", + "name": "heading", "static": false, "origin": "generated", "typeParameters": null, @@ -13743,16 +13769,16 @@ }, { "kind": "method", - "name": "description", + "name": "fields", "static": false, "origin": "generated", "typeParameters": null, - "returns": "String", + "returns": "List", "params": [] }, { "kind": "method", - "name": "amount", + "name": "instruction", "static": false, "origin": "generated", "typeParameters": null, @@ -13761,94 +13787,45 @@ }, { "kind": "method", - "name": "emphasized", + "name": "dueNotice", "static": false, "origin": "generated", "typeParameters": null, - "returns": "boolean", + "returns": "String", "params": [] - } - ] - }, - { - "name": "ProposalPricingRow.Builder", - "binaryName": "com.demcha.compose.document.templates.data.proposal.ProposalPricingRow$Builder", - "kind": "class", - "modifiers": [ - "final" - ], - "artifact": "graph-compose-templates", - "members": [ + }, { "kind": "method", - "name": "label", + "name": "dueNoticeEmphasis", "static": false, - "origin": "source", + "origin": "generated", "typeParameters": null, - "returns": "ProposalPricingRow.Builder", - "params": [ - { - "type": "String", - "name": "label" - } - ] - }, - { - "kind": "method", - "name": "description", - "static": false, - "origin": "source", - "typeParameters": null, - "returns": "ProposalPricingRow.Builder", - "params": [ - { - "type": "String", - "name": "description" - } - ] - }, - { - "kind": "method", - "name": "amount", - "static": false, - "origin": "source", - "typeParameters": null, - "returns": "ProposalPricingRow.Builder", - "params": [ - { - "type": "String", - "name": "amount" - } - ] + "returns": "String", + "params": [] }, { "kind": "method", - "name": "emphasized", + "name": "accountHolder", "static": false, - "origin": "source", + "origin": "generated", "typeParameters": null, - "returns": "ProposalPricingRow.Builder", - "params": [ - { - "type": "boolean", - "name": "emphasized" - } - ] + "returns": "String", + "params": [] }, { "kind": "method", - "name": "build", + "name": "signOff", "static": false, - "origin": "source", + "origin": "generated", "typeParameters": null, - "returns": "ProposalPricingRow", + "returns": "String", "params": [] } ] }, { - "name": "ProposalSection", - "binaryName": "com.demcha.compose.document.templates.data.proposal.ProposalSection", + "name": "InvoicePaymentBlock.Field", + "binaryName": "com.demcha.compose.document.templates.data.invoice.InvoicePaymentBlock$Field", "kind": "record", "modifiers": [ "final" @@ -13857,7 +13834,7 @@ "members": [ { "kind": "constructor", - "name": "ProposalSection", + "name": "Field", "static": false, "origin": "generated", "typeParameters": null, @@ -13868,23 +13845,14 @@ "name": null }, { - "type": "List", + "type": "String", "name": null } ] }, { "kind": "method", - "name": "builder", - "static": true, - "origin": "source", - "typeParameters": null, - "returns": "ProposalSection.Builder", - "params": [] - }, - { - "kind": "method", - "name": "title", + "name": "label", "static": false, "origin": "generated", "typeParameters": null, @@ -13893,134 +13861,148 @@ }, { "kind": "method", - "name": "paragraphs", + "name": "value", "static": false, "origin": "generated", "typeParameters": null, - "returns": "List", + "returns": "String", "params": [] } ] }, { - "name": "ProposalSection.Builder", - "binaryName": "com.demcha.compose.document.templates.data.proposal.ProposalSection$Builder", - "kind": "class", + "name": "InvoiceRecipient", + "binaryName": "com.demcha.compose.document.templates.data.invoice.InvoiceRecipient", + "kind": "record", "modifiers": [ "final" ], "artifact": "graph-compose-templates", "members": [ { - "kind": "method", - "name": "title", + "kind": "constructor", + "name": "InvoiceRecipient", "static": false, - "origin": "source", + "origin": "generated", "typeParameters": null, - "returns": "ProposalSection.Builder", + "returns": null, "params": [ { "type": "String", - "name": "title" + "name": null + }, + { + "type": "String", + "name": null + }, + { + "type": "String", + "name": null + }, + { + "type": "List", + "name": null + }, + { + "type": "String", + "name": null + }, + { + "type": "String", + "name": null + }, + { + "type": "String", + "name": null + }, + { + "type": "String", + "name": null } ] }, { - "kind": "method", - "name": "paragraphs", + "kind": "constructor", + "name": "InvoiceRecipient", "static": false, "origin": "source", "typeParameters": null, - "returns": "ProposalSection.Builder", + "returns": null, "params": [ + { + "type": "String", + "name": "heading" + }, + { + "type": "String", + "name": "name" + }, + { + "type": "String", + "name": "subline" + }, { "type": "List", - "name": "paragraphs" + "name": "addressLines" + }, + { + "type": "String", + "name": "emailLabel" + }, + { + "type": "String", + "name": "email" } ] }, { "kind": "method", - "name": "paragraphs", + "name": "hasRegistration", "static": false, "origin": "source", "typeParameters": null, - "returns": "ProposalSection.Builder", - "params": [ - { - "type": "String...", - "name": "paragraphs" - } - ] + "returns": "boolean", + "params": [] }, { "kind": "method", - "name": "addParagraph", + "name": "heading", "static": false, - "origin": "source", + "origin": "generated", "typeParameters": null, - "returns": "ProposalSection.Builder", - "params": [ - { - "type": "String", - "name": "paragraph" - } - ] + "returns": "String", + "params": [] }, { "kind": "method", - "name": "build", + "name": "name", "static": false, - "origin": "source", + "origin": "generated", "typeParameters": null, - "returns": "ProposalSection", + "returns": "String", "params": [] - } - ] - }, - { - "name": "ProposalTimelineItem", - "binaryName": "com.demcha.compose.document.templates.data.proposal.ProposalTimelineItem", - "kind": "record", - "modifiers": [ - "final" - ], - "artifact": "graph-compose-templates", - "members": [ + }, { - "kind": "constructor", - "name": "ProposalTimelineItem", + "kind": "method", + "name": "subline", "static": false, "origin": "generated", "typeParameters": null, - "returns": null, - "params": [ - { - "type": "String", - "name": null - }, - { - "type": "String", - "name": null - }, - { - "type": "String", - "name": null - } - ] + "returns": "String", + "params": [] }, { "kind": "method", - "name": "builder", - "static": true, - "origin": "source", + "name": "addressLines", + "static": false, + "origin": "generated", "typeParameters": null, - "returns": "ProposalTimelineItem.Builder", + "returns": "List", "params": [] }, { "kind": "method", - "name": "phase", + "name": "emailLabel", "static": false, "origin": "generated", "typeParameters": null, @@ -14029,7 +14011,7 @@ }, { "kind": "method", - "name": "duration", + "name": "email", "static": false, "origin": "generated", "typeParameters": null, @@ -14038,7 +14020,16 @@ }, { "kind": "method", - "name": "details", + "name": "registrationLabel", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "String", + "params": [] + }, + { + "kind": "method", + "name": "registrationNumber", "static": false, "origin": "generated", "typeParameters": null, @@ -14048,75 +14039,55 @@ ] }, { - "name": "ProposalTimelineItem.Builder", - "binaryName": "com.demcha.compose.document.templates.data.proposal.ProposalTimelineItem$Builder", - "kind": "class", + "name": "InvoiceServiceLines", + "binaryName": "com.demcha.compose.document.templates.data.invoice.InvoiceServiceLines", + "kind": "record", "modifiers": [ "final" ], "artifact": "graph-compose-templates", "members": [ { - "kind": "method", - "name": "phase", + "kind": "constructor", + "name": "InvoiceServiceLines", "static": false, - "origin": "source", + "origin": "generated", "typeParameters": null, - "returns": "ProposalTimelineItem.Builder", + "returns": null, "params": [ { - "type": "String", - "name": "phase" + "type": "InvoiceServiceLines.Columns", + "name": null + }, + { + "type": "List", + "name": null } ] }, { "kind": "method", - "name": "duration", + "name": "columns", "static": false, - "origin": "source", + "origin": "generated", "typeParameters": null, - "returns": "ProposalTimelineItem.Builder", - "params": [ - { - "type": "String", - "name": "duration" - } - ] + "returns": "InvoiceServiceLines.Columns", + "params": [] }, { "kind": "method", - "name": "details", + "name": "lines", "static": false, - "origin": "source", + "origin": "generated", "typeParameters": null, - "returns": "ProposalTimelineItem.Builder", - "params": [ - { - "type": "String", - "name": "details" - } - ] - }, - { - "kind": "method", - "name": "build", - "static": false, - "origin": "source", - "typeParameters": null, - "returns": "ProposalTimelineItem", + "returns": "List", "params": [] } ] - } - ] - }, - { - "name": "com.demcha.compose.document.templates.data.schedule", - "types": [ + }, { - "name": "ScheduleAssignment", - "binaryName": "com.demcha.compose.document.templates.data.schedule.ScheduleAssignment", + "name": "InvoiceServiceLines.Columns", + "binaryName": "com.demcha.compose.document.templates.data.invoice.InvoiceServiceLines$Columns", "kind": "record", "modifiers": [ "final" @@ -14125,7 +14096,7 @@ "members": [ { "kind": "constructor", - "name": "ScheduleAssignment", + "name": "Columns", "static": false, "origin": "generated", "typeParameters": null, @@ -14144,7 +14115,19 @@ "name": null }, { - "type": "List", + "type": "String", + "name": null + }, + { + "type": "String", + "name": null + }, + { + "type": "String", + "name": null + }, + { + "type": "String", "name": null }, { @@ -14154,26 +14137,80 @@ ] }, { - "kind": "method", - "name": "builder", - "static": true, + "kind": "constructor", + "name": "Columns", + "static": false, "origin": "source", "typeParameters": null, - "returns": "ScheduleAssignment.Builder", - "params": [] + "returns": null, + "params": [ + { + "type": "String", + "name": "index" + }, + { + "type": "String", + "name": "description" + }, + { + "type": "String", + "name": "servicePeriod" + }, + { + "type": "String", + "name": "quantity" + }, + { + "type": "String", + "name": "unitPrice" + }, + { + "type": "String", + "name": "amount" + }, + { + "type": "String", + "name": "vat" + } + ] }, { - "kind": "method", - "name": "personId", + "kind": "constructor", + "name": "Columns", "static": false, - "origin": "generated", + "origin": "source", "typeParameters": null, - "returns": "String", - "params": [] + "returns": null, + "params": [ + { + "type": "String", + "name": "index" + }, + { + "type": "String", + "name": "description" + }, + { + "type": "String", + "name": "servicePeriod" + }, + { + "type": "String", + "name": "quantity" + }, + { + "type": "String", + "name": "unitPrice" + }, + { + "type": "String", + "name": "amount" + } + ] }, { "kind": "method", - "name": "dayId", + "name": "index", "static": false, "origin": "generated", "typeParameters": null, @@ -14182,7 +14219,7 @@ }, { "kind": "method", - "name": "categoryId", + "name": "description", "static": false, "origin": "generated", "typeParameters": null, @@ -14191,163 +14228,63 @@ }, { "kind": "method", - "name": "slots", + "name": "servicePeriod", "static": false, "origin": "generated", "typeParameters": null, - "returns": "List", + "returns": "String", "params": [] }, { "kind": "method", - "name": "note", + "name": "quantity", "static": false, "origin": "generated", "typeParameters": null, "returns": "String", "params": [] - } - ] - }, - { - "name": "ScheduleAssignment.Builder", - "binaryName": "com.demcha.compose.document.templates.data.schedule.ScheduleAssignment$Builder", - "kind": "class", - "modifiers": [ - "final" - ], - "artifact": "graph-compose-templates", - "members": [ - { - "kind": "method", - "name": "personId", - "static": false, - "origin": "source", - "typeParameters": null, - "returns": "ScheduleAssignment.Builder", - "params": [ - { - "type": "String", - "name": "personId" - } - ] - }, - { - "kind": "method", - "name": "dayId", - "static": false, - "origin": "source", - "typeParameters": null, - "returns": "ScheduleAssignment.Builder", - "params": [ - { - "type": "String", - "name": "dayId" - } - ] - }, - { - "kind": "method", - "name": "categoryId", - "static": false, - "origin": "source", - "typeParameters": null, - "returns": "ScheduleAssignment.Builder", - "params": [ - { - "type": "String", - "name": "categoryId" - } - ] - }, - { - "kind": "method", - "name": "slots", - "static": false, - "origin": "source", - "typeParameters": null, - "returns": "ScheduleAssignment.Builder", - "params": [ - { - "type": "List", - "name": "slots" - } - ] - }, - { - "kind": "method", - "name": "slots", - "static": false, - "origin": "source", - "typeParameters": null, - "returns": "ScheduleAssignment.Builder", - "params": [ - { - "type": "ScheduleSlot...", - "name": "slots" - } - ] }, { "kind": "method", - "name": "addSlot", + "name": "unitPrice", "static": false, - "origin": "source", + "origin": "generated", "typeParameters": null, - "returns": "ScheduleAssignment.Builder", - "params": [ - { - "type": "ScheduleSlot", - "name": "slot" - } - ] + "returns": "String", + "params": [] }, { "kind": "method", - "name": "slot", + "name": "amount", "static": false, - "origin": "source", + "origin": "generated", "typeParameters": null, - "returns": "ScheduleAssignment.Builder", - "params": [ - { - "type": "String", - "name": "start" - }, - { - "type": "String", - "name": "end" - } - ] + "returns": "String", + "params": [] }, { "kind": "method", - "name": "note", + "name": "vat", "static": false, - "origin": "source", + "origin": "generated", "typeParameters": null, - "returns": "ScheduleAssignment.Builder", - "params": [ - { - "type": "String", - "name": "note" - } - ] + "returns": "String", + "params": [] }, { "kind": "method", - "name": "build", + "name": "region", "static": false, - "origin": "source", + "origin": "generated", "typeParameters": null, - "returns": "ScheduleAssignment", + "returns": "String", "params": [] } ] }, { - "name": "ScheduleCategory", - "binaryName": "com.demcha.compose.document.templates.data.schedule.ScheduleCategory", + "name": "InvoiceServiceLines.Line", + "binaryName": "com.demcha.compose.document.templates.data.invoice.InvoiceServiceLines$Line", "kind": "record", "modifiers": [ "final" @@ -14356,12 +14293,16 @@ "members": [ { "kind": "constructor", - "name": "ScheduleCategory", + "name": "Line", "static": false, "origin": "generated", "typeParameters": null, "returns": null, "params": [ + { + "type": "int", + "name": null + }, { "type": "String", "name": null @@ -14371,96 +14312,189 @@ "name": null }, { - "type": "Color", + "type": "String", "name": null }, { - "type": "Color", + "type": "BigDecimal", "name": null }, { - "type": "Color", + "type": "String", "name": null - } - ] - }, - { - "kind": "method", - "name": "of", - "static": true, + }, + { + "type": "BigDecimal", + "name": null + }, + { + "type": "BigDecimal", + "name": null + }, + { + "type": "String", + "name": null + }, + { + "type": "String", + "name": null + }, + { + "type": "String", + "name": null + } + ] + }, + { + "kind": "constructor", + "name": "Line", + "static": false, "origin": "source", "typeParameters": null, - "returns": "ScheduleCategory", + "returns": null, "params": [ + { + "type": "int", + "name": "lineNumber" + }, { "type": "String", - "name": "id" + "name": "title" }, { "type": "String", - "name": "label" + "name": "description" }, { - "type": "Color", - "name": "fillColor" + "type": "String", + "name": "servicePeriod" }, { - "type": "Color", - "name": "borderColor" + "type": "BigDecimal", + "name": "quantity" + }, + { + "type": "String", + "name": "unit" + }, + { + "type": "BigDecimal", + "name": "unitPrice" + }, + { + "type": "BigDecimal", + "name": "amount" + }, + { + "type": "String", + "name": "vatRate" + }, + { + "type": "String", + "name": "icon" } ] }, { - "kind": "method", - "name": "of", - "static": true, + "kind": "constructor", + "name": "Line", + "static": false, "origin": "source", "typeParameters": null, - "returns": "ScheduleCategory", + "returns": null, "params": [ + { + "type": "int", + "name": "lineNumber" + }, { "type": "String", - "name": "id" + "name": "title" }, { "type": "String", - "name": "label" + "name": "description" }, { - "type": "Color", - "name": "fillColor" + "type": "String", + "name": "servicePeriod" }, { - "type": "Color", - "name": "textColor" + "type": "BigDecimal", + "name": "quantity" }, { - "type": "Color", - "name": "borderColor" + "type": "String", + "name": "unit" + }, + { + "type": "BigDecimal", + "name": "unitPrice" + }, + { + "type": "BigDecimal", + "name": "amount" + }, + { + "type": "String", + "name": "vatRate" } ] }, { - "kind": "method", - "name": "builder", - "static": true, + "kind": "constructor", + "name": "Line", + "static": false, "origin": "source", "typeParameters": null, - "returns": "ScheduleCategory.Builder", - "params": [] + "returns": null, + "params": [ + { + "type": "int", + "name": "lineNumber" + }, + { + "type": "String", + "name": "title" + }, + { + "type": "String", + "name": "description" + }, + { + "type": "String", + "name": "servicePeriod" + }, + { + "type": "BigDecimal", + "name": "quantity" + }, + { + "type": "String", + "name": "unit" + }, + { + "type": "BigDecimal", + "name": "unitPrice" + }, + { + "type": "BigDecimal", + "name": "amount" + } + ] }, { "kind": "method", - "name": "id", + "name": "lineNumber", "static": false, "origin": "generated", "typeParameters": null, - "returns": "String", + "returns": "int", "params": [] }, { "kind": "method", - "name": "label", + "name": "title", "static": false, "origin": "generated", "typeParameters": null, @@ -14469,126 +14503,90 @@ }, { "kind": "method", - "name": "fillColor", + "name": "description", "static": false, "origin": "generated", "typeParameters": null, - "returns": "Color", + "returns": "String", "params": [] }, { "kind": "method", - "name": "textColor", + "name": "servicePeriod", "static": false, "origin": "generated", "typeParameters": null, - "returns": "Color", + "returns": "String", "params": [] }, { "kind": "method", - "name": "borderColor", + "name": "quantity", "static": false, "origin": "generated", "typeParameters": null, - "returns": "Color", + "returns": "BigDecimal", "params": [] - } - ] - }, - { - "name": "ScheduleCategory.Builder", - "binaryName": "com.demcha.compose.document.templates.data.schedule.ScheduleCategory$Builder", - "kind": "class", - "modifiers": [ - "final" - ], - "artifact": "graph-compose-templates", - "members": [ + }, { "kind": "method", - "name": "id", + "name": "unit", "static": false, - "origin": "source", + "origin": "generated", "typeParameters": null, - "returns": "ScheduleCategory.Builder", - "params": [ - { - "type": "String", - "name": "id" - } - ] + "returns": "String", + "params": [] }, { "kind": "method", - "name": "label", + "name": "unitPrice", "static": false, - "origin": "source", + "origin": "generated", "typeParameters": null, - "returns": "ScheduleCategory.Builder", - "params": [ - { - "type": "String", - "name": "label" - } - ] + "returns": "BigDecimal", + "params": [] }, { "kind": "method", - "name": "fillColor", + "name": "amount", "static": false, - "origin": "source", + "origin": "generated", "typeParameters": null, - "returns": "ScheduleCategory.Builder", - "params": [ - { - "type": "Color", - "name": "fillColor" - } - ] + "returns": "BigDecimal", + "params": [] }, { "kind": "method", - "name": "textColor", + "name": "vatRate", "static": false, - "origin": "source", + "origin": "generated", "typeParameters": null, - "returns": "ScheduleCategory.Builder", - "params": [ - { - "type": "Color", - "name": "textColor" - } - ] + "returns": "String", + "params": [] }, { "kind": "method", - "name": "borderColor", + "name": "icon", "static": false, - "origin": "source", + "origin": "generated", "typeParameters": null, - "returns": "ScheduleCategory.Builder", - "params": [ - { - "type": "Color", - "name": "borderColor" - } - ] + "returns": "String", + "params": [] }, { "kind": "method", - "name": "build", + "name": "region", "static": false, - "origin": "source", + "origin": "generated", "typeParameters": null, - "returns": "ScheduleCategory", + "returns": "String", "params": [] } ] }, { - "name": "ScheduleDay", - "binaryName": "com.demcha.compose.document.templates.data.schedule.ScheduleDay", + "name": "InvoiceSummaryBlock", + "binaryName": "com.demcha.compose.document.templates.data.invoice.InvoiceSummaryBlock", "kind": "record", "modifiers": [ "final" @@ -14597,7 +14595,7 @@ "members": [ { "kind": "constructor", - "name": "ScheduleDay", + "name": "InvoiceSummaryBlock", "static": false, "origin": "generated", "typeParameters": null, @@ -14611,10 +14609,6 @@ "type": "String", "name": null }, - { - "type": "String", - "name": null - }, { "type": "String", "name": null @@ -14623,47 +14617,72 @@ }, { "kind": "method", - "name": "of", - "static": true, - "origin": "source", + "name": "heading", + "static": false, + "origin": "generated", "typeParameters": null, - "returns": "ScheduleDay", - "params": [ - { - "type": "String", - "name": "id" - }, - { - "type": "String", - "name": "label" - }, - { - "type": "String", - "name": "headerNote" - }, - { - "type": "String", - "name": "headerCategoryId" - } - ] + "returns": "String", + "params": [] }, { "kind": "method", - "name": "builder", - "static": true, - "origin": "source", + "name": "intro", + "static": false, + "origin": "generated", "typeParameters": null, - "returns": "ScheduleDay.Builder", + "returns": "String", "params": [] }, { "kind": "method", - "name": "id", + "name": "servicePeriod", "static": false, "origin": "generated", "typeParameters": null, "returns": "String", "params": [] + } + ] + }, + { + "name": "InvoiceSummaryRow", + "binaryName": "com.demcha.compose.document.templates.data.invoice.InvoiceSummaryRow", + "kind": "record", + "modifiers": [ + "final" + ], + "artifact": "graph-compose-templates", + "members": [ + { + "kind": "constructor", + "name": "InvoiceSummaryRow", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": null, + "params": [ + { + "type": "String", + "name": null + }, + { + "type": "String", + "name": null + }, + { + "type": "boolean", + "name": null + } + ] + }, + { + "kind": "method", + "name": "builder", + "static": true, + "origin": "source", + "typeParameters": null, + "returns": "InvoiceSummaryRow.Builder", + "params": [] }, { "kind": "method", @@ -14676,7 +14695,7 @@ }, { "kind": "method", - "name": "headerNote", + "name": "value", "static": false, "origin": "generated", "typeParameters": null, @@ -14685,45 +14704,31 @@ }, { "kind": "method", - "name": "headerCategoryId", + "name": "emphasized", "static": false, "origin": "generated", "typeParameters": null, - "returns": "String", + "returns": "boolean", "params": [] } ] }, { - "name": "ScheduleDay.Builder", - "binaryName": "com.demcha.compose.document.templates.data.schedule.ScheduleDay$Builder", + "name": "InvoiceSummaryRow.Builder", + "binaryName": "com.demcha.compose.document.templates.data.invoice.InvoiceSummaryRow$Builder", "kind": "class", "modifiers": [ "final" ], "artifact": "graph-compose-templates", "members": [ - { - "kind": "method", - "name": "id", - "static": false, - "origin": "source", - "typeParameters": null, - "returns": "ScheduleDay.Builder", - "params": [ - { - "type": "String", - "name": "id" - } - ] - }, { "kind": "method", "name": "label", "static": false, "origin": "source", "typeParameters": null, - "returns": "ScheduleDay.Builder", + "returns": "InvoiceSummaryRow.Builder", "params": [ { "type": "String", @@ -14733,29 +14738,29 @@ }, { "kind": "method", - "name": "headerNote", + "name": "value", "static": false, "origin": "source", "typeParameters": null, - "returns": "ScheduleDay.Builder", + "returns": "InvoiceSummaryRow.Builder", "params": [ { "type": "String", - "name": "headerNote" + "name": "value" } ] }, { "kind": "method", - "name": "headerCategoryId", + "name": "emphasized", "static": false, "origin": "source", "typeParameters": null, - "returns": "ScheduleDay.Builder", + "returns": "InvoiceSummaryRow.Builder", "params": [ { - "type": "String", - "name": "headerCategoryId" + "type": "boolean", + "name": "emphasized" } ] }, @@ -14765,14 +14770,14 @@ "static": false, "origin": "source", "typeParameters": null, - "returns": "ScheduleDay", + "returns": "InvoiceSummaryRow", "params": [] } ] }, { - "name": "ScheduleMetricRow", - "binaryName": "com.demcha.compose.document.templates.data.schedule.ScheduleMetricRow", + "name": "InvoiceTotalsBlock", + "binaryName": "com.demcha.compose.document.templates.data.invoice.InvoiceTotalsBlock", "kind": "record", "modifiers": [ "final" @@ -14781,52 +14786,38 @@ "members": [ { "kind": "constructor", - "name": "ScheduleMetricRow", + "name": "InvoiceTotalsBlock", "static": false, "origin": "generated", "typeParameters": null, "returns": null, "params": [ { - "type": "String", + "type": "List", "name": null }, - { - "type": "List", - "name": null - } - ] - }, - { - "kind": "method", - "name": "of", - "static": true, - "origin": "source", - "typeParameters": null, - "returns": "ScheduleMetricRow", - "params": [ { "type": "String", - "name": "label" + "name": null }, { - "type": "String...", - "name": "dayValues" + "type": "BigDecimal", + "name": null } ] }, { "kind": "method", - "name": "builder", - "static": true, - "origin": "source", + "name": "rows", + "static": false, + "origin": "generated", "typeParameters": null, - "returns": "ScheduleMetricRow.Builder", + "returns": "List", "params": [] }, { "kind": "method", - "name": "label", + "name": "totalLabel", "static": false, "origin": "generated", "typeParameters": null, @@ -14835,94 +14826,65 @@ }, { "kind": "method", - "name": "dayValues", + "name": "totalAmount", "static": false, "origin": "generated", "typeParameters": null, - "returns": "List", + "returns": "BigDecimal", "params": [] } ] }, { - "name": "ScheduleMetricRow.Builder", - "binaryName": "com.demcha.compose.document.templates.data.schedule.ScheduleMetricRow$Builder", - "kind": "class", + "name": "InvoiceTotalsBlock.Row", + "binaryName": "com.demcha.compose.document.templates.data.invoice.InvoiceTotalsBlock$Row", + "kind": "record", "modifiers": [ "final" ], "artifact": "graph-compose-templates", "members": [ { - "kind": "method", - "name": "label", + "kind": "constructor", + "name": "Row", "static": false, - "origin": "source", + "origin": "generated", "typeParameters": null, - "returns": "ScheduleMetricRow.Builder", + "returns": null, "params": [ { "type": "String", - "name": "label" - } - ] - }, - { - "kind": "method", - "name": "dayValues", - "static": false, - "origin": "source", - "typeParameters": null, - "returns": "ScheduleMetricRow.Builder", - "params": [ - { - "type": "List", - "name": "dayValues" - } - ] - }, - { - "kind": "method", - "name": "dayValues", - "static": false, - "origin": "source", - "typeParameters": null, - "returns": "ScheduleMetricRow.Builder", - "params": [ + "name": null + }, { - "type": "String...", - "name": "dayValues" + "type": "BigDecimal", + "name": null } ] }, { "kind": "method", - "name": "addDayValue", + "name": "label", "static": false, - "origin": "source", + "origin": "generated", "typeParameters": null, - "returns": "ScheduleMetricRow.Builder", - "params": [ - { - "type": "String", - "name": "dayValue" - } - ] + "returns": "String", + "params": [] }, { "kind": "method", - "name": "build", + "name": "amount", "static": false, - "origin": "source", + "origin": "generated", "typeParameters": null, - "returns": "ScheduleMetricRow", + "returns": "BigDecimal", "params": [] } ] }, { - "name": "SchedulePerson", - "binaryName": "com.demcha.compose.document.templates.data.schedule.SchedulePerson", + "name": "StructuredInvoiceData", + "binaryName": "com.demcha.compose.document.templates.data.invoice.StructuredInvoiceData", "kind": "record", "modifiers": [ "final" @@ -14931,45 +14893,105 @@ "members": [ { "kind": "constructor", - "name": "SchedulePerson", + "name": "StructuredInvoiceData", "static": false, "origin": "generated", "typeParameters": null, "returns": null, "params": [ { - "type": "String", + "type": "InvoiceBrand", "name": null }, { - "type": "String", + "type": "InvoiceContactBlock", "name": null }, { - "type": "int", + "type": "InvoiceMasthead", + "name": null + }, + { + "type": "InvoiceRecipient", + "name": null + }, + { + "type": "InvoiceRecipient", + "name": null + }, + { + "type": "InvoiceSummaryBlock", + "name": null + }, + { + "type": "InvoiceServiceLines", + "name": null + }, + { + "type": "InvoiceTotalsBlock", + "name": null + }, + { + "type": "InvoicePaymentBlock", + "name": null + }, + { + "type": "InvoiceNotesBlock", + "name": null + }, + { + "type": "String", "name": null } ] }, { - "kind": "method", - "name": "of", - "static": true, + "kind": "constructor", + "name": "StructuredInvoiceData", + "static": false, "origin": "source", "typeParameters": null, - "returns": "SchedulePerson", + "returns": null, "params": [ { - "type": "String", - "name": "id" + "type": "InvoiceBrand", + "name": "brand" }, { - "type": "String", - "name": "displayName" + "type": "InvoiceContactBlock", + "name": "supplier" }, { - "type": "int", - "name": "sortOrder" + "type": "InvoiceMasthead", + "name": "masthead" + }, + { + "type": "InvoiceRecipient", + "name": "billTo" + }, + { + "type": "InvoiceSummaryBlock", + "name": "summary" + }, + { + "type": "InvoiceServiceLines", + "name": "serviceLines" + }, + { + "type": "InvoiceTotalsBlock", + "name": "totals" + }, + { + "type": "InvoicePaymentBlock", + "name": "payment" + }, + { + "type": "InvoiceNotesBlock", + "name": "notes" + }, + { + "type": "String", + "name": "currencyCode" } ] }, @@ -14979,234 +15001,340 @@ "static": true, "origin": "source", "typeParameters": null, - "returns": "SchedulePerson.Builder", + "returns": "StructuredInvoiceData.Builder", "params": [] }, { "kind": "method", - "name": "id", + "name": "brand", "static": false, "origin": "generated", "typeParameters": null, - "returns": "String", + "returns": "InvoiceBrand", "params": [] }, { "kind": "method", - "name": "displayName", + "name": "supplier", "static": false, "origin": "generated", "typeParameters": null, - "returns": "String", + "returns": "InvoiceContactBlock", "params": [] }, { "kind": "method", - "name": "sortOrder", + "name": "masthead", "static": false, "origin": "generated", "typeParameters": null, - "returns": "int", + "returns": "InvoiceMasthead", "params": [] - } - ] - }, - { - "name": "SchedulePerson.Builder", - "binaryName": "com.demcha.compose.document.templates.data.schedule.SchedulePerson$Builder", - "kind": "class", - "modifiers": [ - "final" - ], + }, + { + "kind": "method", + "name": "billTo", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "InvoiceRecipient", + "params": [] + }, + { + "kind": "method", + "name": "shipTo", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "InvoiceRecipient", + "params": [] + }, + { + "kind": "method", + "name": "summary", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "InvoiceSummaryBlock", + "params": [] + }, + { + "kind": "method", + "name": "serviceLines", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "InvoiceServiceLines", + "params": [] + }, + { + "kind": "method", + "name": "totals", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "InvoiceTotalsBlock", + "params": [] + }, + { + "kind": "method", + "name": "payment", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "InvoicePaymentBlock", + "params": [] + }, + { + "kind": "method", + "name": "notes", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "InvoiceNotesBlock", + "params": [] + }, + { + "kind": "method", + "name": "currencyCode", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "String", + "params": [] + } + ] + }, + { + "name": "StructuredInvoiceData.Builder", + "binaryName": "com.demcha.compose.document.templates.data.invoice.StructuredInvoiceData$Builder", + "kind": "class", + "modifiers": [ + "final" + ], "artifact": "graph-compose-templates", "members": [ { "kind": "method", - "name": "id", + "name": "brand", "static": false, "origin": "source", "typeParameters": null, - "returns": "SchedulePerson.Builder", + "returns": "StructuredInvoiceData.Builder", "params": [ { - "type": "String", - "name": "id" + "type": "InvoiceBrand", + "name": "brand" } ] }, { "kind": "method", - "name": "displayName", + "name": "supplier", "static": false, "origin": "source", "typeParameters": null, - "returns": "SchedulePerson.Builder", + "returns": "StructuredInvoiceData.Builder", "params": [ { - "type": "String", - "name": "displayName" + "type": "InvoiceContactBlock", + "name": "supplier" } ] }, { "kind": "method", - "name": "sortOrder", + "name": "masthead", "static": false, "origin": "source", "typeParameters": null, - "returns": "SchedulePerson.Builder", + "returns": "StructuredInvoiceData.Builder", "params": [ { - "type": "int", - "name": "sortOrder" + "type": "InvoiceMasthead", + "name": "masthead" } ] }, { "kind": "method", - "name": "build", + "name": "billTo", "static": false, "origin": "source", "typeParameters": null, - "returns": "SchedulePerson", - "params": [] - } - ] - }, - { - "name": "ScheduleSlot", - "binaryName": "com.demcha.compose.document.templates.data.schedule.ScheduleSlot", - "kind": "record", - "modifiers": [ - "final" - ], - "artifact": "graph-compose-templates", - "members": [ + "returns": "StructuredInvoiceData.Builder", + "params": [ + { + "type": "InvoiceRecipient", + "name": "billTo" + } + ] + }, { - "kind": "constructor", - "name": "ScheduleSlot", + "kind": "method", + "name": "shipTo", "static": false, - "origin": "generated", + "origin": "source", "typeParameters": null, - "returns": null, + "returns": "StructuredInvoiceData.Builder", "params": [ { - "type": "String", - "name": null - }, + "type": "InvoiceRecipient", + "name": "shipTo" + } + ] + }, + { + "kind": "method", + "name": "summary", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "StructuredInvoiceData.Builder", + "params": [ { - "type": "String", - "name": null + "type": "InvoiceSummaryBlock", + "name": "summary" } ] }, { "kind": "method", - "name": "of", - "static": true, + "name": "serviceLines", + "static": false, "origin": "source", "typeParameters": null, - "returns": "ScheduleSlot", + "returns": "StructuredInvoiceData.Builder", "params": [ { - "type": "String", - "name": "start" - }, + "type": "InvoiceServiceLines", + "name": "serviceLines" + } + ] + }, + { + "kind": "method", + "name": "totals", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "StructuredInvoiceData.Builder", + "params": [ { - "type": "String", - "name": "end" + "type": "InvoiceTotalsBlock", + "name": "totals" } ] }, { "kind": "method", - "name": "builder", - "static": true, + "name": "payment", + "static": false, "origin": "source", "typeParameters": null, - "returns": "ScheduleSlot.Builder", - "params": [] + "returns": "StructuredInvoiceData.Builder", + "params": [ + { + "type": "InvoicePaymentBlock", + "name": "payment" + } + ] }, { "kind": "method", - "name": "displayText", + "name": "notes", "static": false, "origin": "source", "typeParameters": null, - "returns": "String", - "params": [] + "returns": "StructuredInvoiceData.Builder", + "params": [ + { + "type": "InvoiceNotesBlock", + "name": "notes" + } + ] }, { "kind": "method", - "name": "start", + "name": "currencyCode", "static": false, - "origin": "generated", + "origin": "source", "typeParameters": null, - "returns": "String", - "params": [] + "returns": "StructuredInvoiceData.Builder", + "params": [ + { + "type": "String", + "name": "currencyCode" + } + ] }, { "kind": "method", - "name": "end", + "name": "build", "static": false, - "origin": "generated", + "origin": "source", "typeParameters": null, - "returns": "String", + "returns": "StructuredInvoiceData", "params": [] } ] }, { - "name": "ScheduleSlot.Builder", - "binaryName": "com.demcha.compose.document.templates.data.schedule.ScheduleSlot$Builder", - "kind": "class", + "name": "StructuredInvoiceDocumentSpec", + "binaryName": "com.demcha.compose.document.templates.data.invoice.StructuredInvoiceDocumentSpec", + "kind": "record", "modifiers": [ "final" ], "artifact": "graph-compose-templates", "members": [ { - "kind": "method", - "name": "start", + "kind": "constructor", + "name": "StructuredInvoiceDocumentSpec", "static": false, - "origin": "source", + "origin": "generated", "typeParameters": null, - "returns": "ScheduleSlot.Builder", + "returns": null, "params": [ { - "type": "String", - "name": "start" + "type": "StructuredInvoiceData", + "name": null } ] }, { "kind": "method", - "name": "end", - "static": false, + "name": "from", + "static": true, "origin": "source", "typeParameters": null, - "returns": "ScheduleSlot.Builder", + "returns": "StructuredInvoiceDocumentSpec", "params": [ { - "type": "String", - "name": "end" + "type": "StructuredInvoiceData", + "name": "invoice" } ] }, { "kind": "method", - "name": "build", + "name": "invoice", "static": false, - "origin": "source", + "origin": "generated", "typeParameters": null, - "returns": "ScheduleSlot", + "returns": "StructuredInvoiceData", "params": [] } ] - }, + } + ] + }, + { + "name": "com.demcha.compose.document.templates.data.proposal", + "types": [ { - "name": "WeeklyScheduleData", - "binaryName": "com.demcha.compose.document.templates.data.schedule.WeeklyScheduleData", + "name": "ProposalAcceptance", + "binaryName": "com.demcha.compose.document.templates.data.proposal.ProposalAcceptance", "kind": "record", "modifiers": [ "final" @@ -15215,7 +15343,7 @@ "members": [ { "kind": "constructor", - "name": "WeeklyScheduleData", + "name": "ProposalAcceptance", "static": false, "origin": "generated", "typeParameters": null, @@ -15230,23 +15358,7 @@ "name": null }, { - "type": "List", - "name": null - }, - { - "type": "List", - "name": null - }, - { - "type": "List", - "name": null - }, - { - "type": "List", - "name": null - }, - { - "type": "List", + "type": "String", "name": null }, { @@ -15257,16 +15369,7 @@ }, { "kind": "method", - "name": "builder", - "static": true, - "origin": "source", - "typeParameters": null, - "returns": "WeeklyScheduleData.Builder", - "params": [] - }, - { - "kind": "method", - "name": "title", + "name": "heading", "static": false, "origin": "generated", "typeParameters": null, @@ -15275,7 +15378,7 @@ }, { "kind": "method", - "name": "weekLabel", + "name": "icon", "static": false, "origin": "generated", "typeParameters": null, @@ -15284,1133 +15387,9966 @@ }, { "kind": "method", - "name": "days", + "name": "statement", "static": false, "origin": "generated", "typeParameters": null, - "returns": "List", + "returns": "String", "params": [] }, { "kind": "method", - "name": "categories", + "name": "fields", "static": false, "origin": "generated", "typeParameters": null, - "returns": "List", + "returns": "List", "params": [] - }, + } + ] + }, + { + "name": "ProposalAttention", + "binaryName": "com.demcha.compose.document.templates.data.proposal.ProposalAttention", + "kind": "record", + "modifiers": [ + "final" + ], + "artifact": "graph-compose-templates", + "members": [ { - "kind": "method", - "name": "headerMetrics", + "kind": "constructor", + "name": "ProposalAttention", "static": false, "origin": "generated", "typeParameters": null, - "returns": "List", + "returns": null, + "params": [ + { + "type": "String", + "name": null + }, + { + "type": "String", + "name": null + }, + { + "type": "String", + "name": null + }, + { + "type": "String", + "name": null + }, + { + "type": "String", + "name": null + } + ] + }, + { + "kind": "method", + "name": "isPresent", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "boolean", "params": [] }, { "kind": "method", - "name": "people", + "name": "label", "static": false, "origin": "generated", "typeParameters": null, - "returns": "List", + "returns": "String", "params": [] }, { "kind": "method", - "name": "assignments", + "name": "name", "static": false, "origin": "generated", "typeParameters": null, - "returns": "List", + "returns": "String", "params": [] }, { "kind": "method", - "name": "footerNotes", + "name": "role", "static": false, "origin": "generated", "typeParameters": null, - "returns": "List", + "returns": "String", + "params": [] + }, + { + "kind": "method", + "name": "email", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "String", + "params": [] + }, + { + "kind": "method", + "name": "phone", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "String", "params": [] } ] }, { - "name": "WeeklyScheduleData.Builder", - "binaryName": "com.demcha.compose.document.templates.data.schedule.WeeklyScheduleData$Builder", - "kind": "class", + "name": "ProposalBrand", + "binaryName": "com.demcha.compose.document.templates.data.proposal.ProposalBrand", + "kind": "record", "modifiers": [ "final" ], "artifact": "graph-compose-templates", "members": [ { - "kind": "method", - "name": "title", + "kind": "constructor", + "name": "ProposalBrand", "static": false, - "origin": "source", + "origin": "generated", "typeParameters": null, - "returns": "WeeklyScheduleData.Builder", + "returns": null, "params": [ { "type": "String", - "name": "title" - } - ] - }, - { - "kind": "method", - "name": "weekLabel", - "static": false, - "origin": "source", - "typeParameters": null, - "returns": "WeeklyScheduleData.Builder", - "params": [ + "name": null + }, { "type": "String", - "name": "weekLabel" - } - ] - }, - { - "kind": "method", - "name": "days", - "static": false, - "origin": "source", - "typeParameters": null, - "returns": "WeeklyScheduleData.Builder", - "params": [ + "name": null + }, { - "type": "List", - "name": "days" + "type": "String", + "name": null + }, + { + "type": "String", + "name": null + }, + { + "type": "String", + "name": null + }, + { + "type": "String", + "name": null } ] }, { "kind": "method", - "name": "addDay", + "name": "monogram", "static": false, - "origin": "source", + "origin": "generated", "typeParameters": null, - "returns": "WeeklyScheduleData.Builder", - "params": [ - { - "type": "ScheduleDay", - "name": "day" - } - ] + "returns": "String", + "params": [] }, { "kind": "method", - "name": "day", + "name": "nameLine1", "static": false, - "origin": "source", + "origin": "generated", "typeParameters": null, - "returns": "WeeklyScheduleData.Builder", - "params": [ - { - "type": "Consumer", - "name": "spec" - } - ] + "returns": "String", + "params": [] }, { "kind": "method", - "name": "day", + "name": "nameLine2", "static": false, - "origin": "source", + "origin": "generated", "typeParameters": null, - "returns": "WeeklyScheduleData.Builder", - "params": [ - { - "type": "String", - "name": "id" - }, - { - "type": "String", - "name": "label" - }, - { - "type": "String", - "name": "headerNote" - }, - { - "type": "String", - "name": "headerCategoryId" - } - ] + "returns": "String", + "params": [] }, { "kind": "method", - "name": "categories", + "name": "documentLabel", "static": false, - "origin": "source", + "origin": "generated", "typeParameters": null, - "returns": "WeeklyScheduleData.Builder", - "params": [ - { - "type": "List", - "name": "categories" - } - ] + "returns": "String", + "params": [] }, { "kind": "method", - "name": "addCategory", + "name": "website", "static": false, - "origin": "source", + "origin": "generated", "typeParameters": null, - "returns": "WeeklyScheduleData.Builder", - "params": [ - { - "type": "ScheduleCategory", - "name": "category" - } - ] + "returns": "String", + "params": [] }, { "kind": "method", - "name": "category", + "name": "footerName", "static": false, - "origin": "source", + "origin": "generated", "typeParameters": null, - "returns": "WeeklyScheduleData.Builder", - "params": [ - { - "type": "Consumer", - "name": "spec" - } - ] - }, + "returns": "String", + "params": [] + } + ] + }, + { + "name": "ProposalData", + "binaryName": "com.demcha.compose.document.templates.data.proposal.ProposalData", + "kind": "record", + "modifiers": [ + "final" + ], + "artifact": "graph-compose-templates", + "members": [ { - "kind": "method", - "name": "category", + "kind": "constructor", + "name": "ProposalData", "static": false, - "origin": "source", + "origin": "generated", "typeParameters": null, - "returns": "WeeklyScheduleData.Builder", + "returns": null, "params": [ { "type": "String", - "name": "id" + "name": null }, { "type": "String", - "name": "label" + "name": null }, { - "type": "Color", - "name": "fillColor" + "type": "String", + "name": null }, { - "type": "Color", - "name": "borderColor" - } - ] - }, - { - "kind": "method", - "name": "category", - "static": false, - "origin": "source", - "typeParameters": null, - "returns": "WeeklyScheduleData.Builder", - "params": [ + "type": "String", + "name": null + }, { "type": "String", - "name": "id" + "name": null }, { "type": "String", - "name": "label" + "name": null }, { - "type": "Color", - "name": "fillColor" + "type": "ProposalParty", + "name": null }, { - "type": "Color", - "name": "textColor" + "type": "ProposalParty", + "name": null }, { - "type": "Color", - "name": "borderColor" + "type": "List", + "name": null + }, + { + "type": "List", + "name": null + }, + { + "type": "List", + "name": null + }, + { + "type": "List", + "name": null + }, + { + "type": "String", + "name": null } ] }, { "kind": "method", - "name": "headerMetrics", + "name": "builder", + "static": true, + "origin": "source", + "typeParameters": null, + "returns": "ProposalData.Builder", + "params": [] + }, + { + "kind": "method", + "name": "title", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "String", + "params": [] + }, + { + "kind": "method", + "name": "proposalNumber", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "String", + "params": [] + }, + { + "kind": "method", + "name": "preparedDate", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "String", + "params": [] + }, + { + "kind": "method", + "name": "validUntil", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "String", + "params": [] + }, + { + "kind": "method", + "name": "projectTitle", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "String", + "params": [] + }, + { + "kind": "method", + "name": "executiveSummary", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "String", + "params": [] + }, + { + "kind": "method", + "name": "sender", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "ProposalParty", + "params": [] + }, + { + "kind": "method", + "name": "recipient", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "ProposalParty", + "params": [] + }, + { + "kind": "method", + "name": "sections", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "List", + "params": [] + }, + { + "kind": "method", + "name": "timeline", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "List", + "params": [] + }, + { + "kind": "method", + "name": "pricingRows", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "List", + "params": [] + }, + { + "kind": "method", + "name": "acceptanceTerms", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "List", + "params": [] + }, + { + "kind": "method", + "name": "footerNote", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "String", + "params": [] + } + ] + }, + { + "name": "ProposalData.Builder", + "binaryName": "com.demcha.compose.document.templates.data.proposal.ProposalData$Builder", + "kind": "class", + "modifiers": [ + "final" + ], + "artifact": "graph-compose-templates", + "members": [ + { + "kind": "method", + "name": "title", "static": false, "origin": "source", "typeParameters": null, - "returns": "WeeklyScheduleData.Builder", + "returns": "ProposalData.Builder", "params": [ { - "type": "List", - "name": "headerMetrics" + "type": "String", + "name": "title" } ] }, { "kind": "method", - "name": "addHeaderMetric", + "name": "proposalNumber", "static": false, "origin": "source", "typeParameters": null, - "returns": "WeeklyScheduleData.Builder", + "returns": "ProposalData.Builder", "params": [ { - "type": "ScheduleMetricRow", - "name": "headerMetric" + "type": "String", + "name": "proposalNumber" } ] }, { "kind": "method", - "name": "headerMetric", + "name": "preparedDate", "static": false, "origin": "source", "typeParameters": null, - "returns": "WeeklyScheduleData.Builder", + "returns": "ProposalData.Builder", "params": [ { - "type": "Consumer", - "name": "spec" + "type": "String", + "name": "preparedDate" } ] }, { "kind": "method", - "name": "headerMetric", + "name": "validUntil", "static": false, "origin": "source", "typeParameters": null, - "returns": "WeeklyScheduleData.Builder", + "returns": "ProposalData.Builder", "params": [ { "type": "String", - "name": "label" - }, - { - "type": "List", - "name": "dayValues" + "name": "validUntil" } ] }, { "kind": "method", - "name": "headerMetric", + "name": "projectTitle", "static": false, "origin": "source", "typeParameters": null, - "returns": "WeeklyScheduleData.Builder", + "returns": "ProposalData.Builder", "params": [ { "type": "String", - "name": "label" - }, - { - "type": "String...", - "name": "dayValues" + "name": "projectTitle" } ] }, { "kind": "method", - "name": "people", + "name": "executiveSummary", "static": false, "origin": "source", "typeParameters": null, - "returns": "WeeklyScheduleData.Builder", + "returns": "ProposalData.Builder", "params": [ { - "type": "List", - "name": "people" + "type": "String", + "name": "executiveSummary" } ] }, { "kind": "method", - "name": "addPerson", + "name": "sender", "static": false, "origin": "source", "typeParameters": null, - "returns": "WeeklyScheduleData.Builder", + "returns": "ProposalData.Builder", "params": [ { - "type": "SchedulePerson", - "name": "person" + "type": "ProposalParty", + "name": "sender" } ] }, { "kind": "method", - "name": "person", + "name": "sender", "static": false, "origin": "source", "typeParameters": null, - "returns": "WeeklyScheduleData.Builder", + "returns": "ProposalData.Builder", "params": [ { - "type": "Consumer", + "type": "Consumer", "name": "spec" } ] }, { "kind": "method", - "name": "person", + "name": "recipient", "static": false, "origin": "source", "typeParameters": null, - "returns": "WeeklyScheduleData.Builder", + "returns": "ProposalData.Builder", "params": [ { - "type": "String", - "name": "id" - }, - { - "type": "String", - "name": "displayName" - }, - { - "type": "int", - "name": "sortOrder" + "type": "ProposalParty", + "name": "recipient" } ] }, { "kind": "method", - "name": "assignments", + "name": "recipient", "static": false, "origin": "source", "typeParameters": null, - "returns": "WeeklyScheduleData.Builder", + "returns": "ProposalData.Builder", "params": [ { - "type": "List", - "name": "assignments" + "type": "Consumer", + "name": "spec" } ] }, { "kind": "method", - "name": "addAssignment", + "name": "sections", "static": false, "origin": "source", "typeParameters": null, - "returns": "WeeklyScheduleData.Builder", + "returns": "ProposalData.Builder", "params": [ { - "type": "ScheduleAssignment", - "name": "assignment" + "type": "List", + "name": "sections" } ] }, { "kind": "method", - "name": "assignment", - "static": false, + "name": "addSection", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "ProposalData.Builder", + "params": [ + { + "type": "ProposalSection", + "name": "section" + } + ] + }, + { + "kind": "method", + "name": "section", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "ProposalData.Builder", + "params": [ + { + "type": "Consumer", + "name": "spec" + } + ] + }, + { + "kind": "method", + "name": "section", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "ProposalData.Builder", + "params": [ + { + "type": "String", + "name": "title" + }, + { + "type": "String...", + "name": "paragraphs" + } + ] + }, + { + "kind": "method", + "name": "timeline", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "ProposalData.Builder", + "params": [ + { + "type": "List", + "name": "timeline" + } + ] + }, + { + "kind": "method", + "name": "addTimelineItem", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "ProposalData.Builder", + "params": [ + { + "type": "ProposalTimelineItem", + "name": "item" + } + ] + }, + { + "kind": "method", + "name": "timelineItem", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "ProposalData.Builder", + "params": [ + { + "type": "Consumer", + "name": "spec" + } + ] + }, + { + "kind": "method", + "name": "timelineItem", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "ProposalData.Builder", + "params": [ + { + "type": "String", + "name": "phase" + }, + { + "type": "String", + "name": "duration" + }, + { + "type": "String", + "name": "details" + } + ] + }, + { + "kind": "method", + "name": "pricingRows", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "ProposalData.Builder", + "params": [ + { + "type": "List", + "name": "pricingRows" + } + ] + }, + { + "kind": "method", + "name": "addPricingRow", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "ProposalData.Builder", + "params": [ + { + "type": "ProposalPricingRow", + "name": "pricingRow" + } + ] + }, + { + "kind": "method", + "name": "pricingRow", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "ProposalData.Builder", + "params": [ + { + "type": "Consumer", + "name": "spec" + } + ] + }, + { + "kind": "method", + "name": "pricingRow", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "ProposalData.Builder", + "params": [ + { + "type": "String", + "name": "label" + }, + { + "type": "String", + "name": "description" + }, + { + "type": "String", + "name": "amount" + } + ] + }, + { + "kind": "method", + "name": "emphasizedPricingRow", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "ProposalData.Builder", + "params": [ + { + "type": "String", + "name": "label" + }, + { + "type": "String", + "name": "description" + }, + { + "type": "String", + "name": "amount" + } + ] + }, + { + "kind": "method", + "name": "acceptanceTerms", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "ProposalData.Builder", + "params": [ + { + "type": "List", + "name": "acceptanceTerms" + } + ] + }, + { + "kind": "method", + "name": "acceptanceTerm", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "ProposalData.Builder", + "params": [ + { + "type": "String", + "name": "acceptanceTerm" + } + ] + }, + { + "kind": "method", + "name": "footerNote", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "ProposalData.Builder", + "params": [ + { + "type": "String", + "name": "footerNote" + } + ] + }, + { + "kind": "method", + "name": "build", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "ProposalData", + "params": [] + } + ] + }, + { + "name": "ProposalDeliverables", + "binaryName": "com.demcha.compose.document.templates.data.proposal.ProposalDeliverables", + "kind": "record", + "modifiers": [ + "final" + ], + "artifact": "graph-compose-templates", + "members": [ + { + "kind": "constructor", + "name": "ProposalDeliverables", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": null, + "params": [ + { + "type": "String", + "name": null + }, + { + "type": "String", + "name": null + }, + { + "type": "List", + "name": null + }, + { + "type": "List", + "name": null + } + ] + }, + { + "kind": "method", + "name": "heading", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "String", + "params": [] + }, + { + "kind": "method", + "name": "icon", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "String", + "params": [] + }, + { + "kind": "method", + "name": "leftColumn", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "List", + "params": [] + }, + { + "kind": "method", + "name": "rightColumn", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "List", + "params": [] + } + ] + }, + { + "name": "ProposalDocumentSpec", + "binaryName": "com.demcha.compose.document.templates.data.proposal.ProposalDocumentSpec", + "kind": "record", + "modifiers": [ + "final" + ], + "artifact": "graph-compose-templates", + "members": [ + { + "kind": "constructor", + "name": "ProposalDocumentSpec", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": null, + "params": [ + { + "type": "ProposalData", + "name": null + } + ] + }, + { + "kind": "method", + "name": "from", + "static": true, + "origin": "source", + "typeParameters": null, + "returns": "ProposalDocumentSpec", + "params": [ + { + "type": "ProposalData", + "name": "proposal" + } + ] + }, + { + "kind": "method", + "name": "builder", + "static": true, + "origin": "source", + "typeParameters": null, + "returns": "ProposalDocumentSpec.Builder", + "params": [] + }, + { + "kind": "method", + "name": "proposal", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "ProposalData", + "params": [] + } + ] + }, + { + "name": "ProposalDocumentSpec.Builder", + "binaryName": "com.demcha.compose.document.templates.data.proposal.ProposalDocumentSpec$Builder", + "kind": "class", + "modifiers": [ + "final" + ], + "artifact": "graph-compose-templates", + "members": [ + { + "kind": "method", + "name": "title", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "ProposalDocumentSpec.Builder", + "params": [ + { + "type": "String", + "name": "title" + } + ] + }, + { + "kind": "method", + "name": "proposalNumber", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "ProposalDocumentSpec.Builder", + "params": [ + { + "type": "String", + "name": "proposalNumber" + } + ] + }, + { + "kind": "method", + "name": "preparedDate", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "ProposalDocumentSpec.Builder", + "params": [ + { + "type": "String", + "name": "preparedDate" + } + ] + }, + { + "kind": "method", + "name": "validUntil", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "ProposalDocumentSpec.Builder", + "params": [ + { + "type": "String", + "name": "validUntil" + } + ] + }, + { + "kind": "method", + "name": "projectTitle", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "ProposalDocumentSpec.Builder", + "params": [ + { + "type": "String", + "name": "projectTitle" + } + ] + }, + { + "kind": "method", + "name": "executiveSummary", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "ProposalDocumentSpec.Builder", + "params": [ + { + "type": "String", + "name": "executiveSummary" + } + ] + }, + { + "kind": "method", + "name": "sender", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "ProposalDocumentSpec.Builder", + "params": [ + { + "type": "ProposalParty", + "name": "sender" + } + ] + }, + { + "kind": "method", + "name": "sender", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "ProposalDocumentSpec.Builder", + "params": [ + { + "type": "Consumer", + "name": "spec" + } + ] + }, + { + "kind": "method", + "name": "recipient", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "ProposalDocumentSpec.Builder", + "params": [ + { + "type": "ProposalParty", + "name": "recipient" + } + ] + }, + { + "kind": "method", + "name": "recipient", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "ProposalDocumentSpec.Builder", + "params": [ + { + "type": "Consumer", + "name": "spec" + } + ] + }, + { + "kind": "method", + "name": "sections", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "ProposalDocumentSpec.Builder", + "params": [ + { + "type": "List", + "name": "sections" + } + ] + }, + { + "kind": "method", + "name": "addSection", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "ProposalDocumentSpec.Builder", + "params": [ + { + "type": "ProposalSection", + "name": "section" + } + ] + }, + { + "kind": "method", + "name": "section", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "ProposalDocumentSpec.Builder", + "params": [ + { + "type": "Consumer", + "name": "spec" + } + ] + }, + { + "kind": "method", + "name": "section", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "ProposalDocumentSpec.Builder", + "params": [ + { + "type": "String", + "name": "title" + }, + { + "type": "String...", + "name": "paragraphs" + } + ] + }, + { + "kind": "method", + "name": "timeline", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "ProposalDocumentSpec.Builder", + "params": [ + { + "type": "List", + "name": "timeline" + } + ] + }, + { + "kind": "method", + "name": "addTimelineItem", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "ProposalDocumentSpec.Builder", + "params": [ + { + "type": "ProposalTimelineItem", + "name": "item" + } + ] + }, + { + "kind": "method", + "name": "timelineItem", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "ProposalDocumentSpec.Builder", + "params": [ + { + "type": "Consumer", + "name": "spec" + } + ] + }, + { + "kind": "method", + "name": "timelineItem", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "ProposalDocumentSpec.Builder", + "params": [ + { + "type": "String", + "name": "phase" + }, + { + "type": "String", + "name": "duration" + }, + { + "type": "String", + "name": "details" + } + ] + }, + { + "kind": "method", + "name": "pricingRows", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "ProposalDocumentSpec.Builder", + "params": [ + { + "type": "List", + "name": "pricingRows" + } + ] + }, + { + "kind": "method", + "name": "addPricingRow", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "ProposalDocumentSpec.Builder", + "params": [ + { + "type": "ProposalPricingRow", + "name": "pricingRow" + } + ] + }, + { + "kind": "method", + "name": "pricingRow", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "ProposalDocumentSpec.Builder", + "params": [ + { + "type": "Consumer", + "name": "spec" + } + ] + }, + { + "kind": "method", + "name": "pricingRow", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "ProposalDocumentSpec.Builder", + "params": [ + { + "type": "String", + "name": "label" + }, + { + "type": "String", + "name": "description" + }, + { + "type": "String", + "name": "amount" + } + ] + }, + { + "kind": "method", + "name": "emphasizedPricingRow", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "ProposalDocumentSpec.Builder", + "params": [ + { + "type": "String", + "name": "label" + }, + { + "type": "String", + "name": "description" + }, + { + "type": "String", + "name": "amount" + } + ] + }, + { + "kind": "method", + "name": "acceptanceTerms", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "ProposalDocumentSpec.Builder", + "params": [ + { + "type": "List", + "name": "acceptanceTerms" + } + ] + }, + { + "kind": "method", + "name": "acceptanceTerm", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "ProposalDocumentSpec.Builder", + "params": [ + { + "type": "String", + "name": "acceptanceTerm" + } + ] + }, + { + "kind": "method", + "name": "footerNote", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "ProposalDocumentSpec.Builder", + "params": [ + { + "type": "String", + "name": "footerNote" + } + ] + }, + { + "kind": "method", + "name": "build", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "ProposalDocumentSpec", + "params": [] + } + ] + }, + { + "name": "ProposalFooter", + "binaryName": "com.demcha.compose.document.templates.data.proposal.ProposalFooter", + "kind": "record", + "modifiers": [ + "final" + ], + "artifact": "graph-compose-templates", + "members": [ + { + "kind": "constructor", + "name": "ProposalFooter", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": null, + "params": [ + { + "type": "String", + "name": null + }, + { + "type": "List", + "name": null + }, + { + "type": "List", + "name": null + }, + { + "type": "String", + "name": null + } + ] + }, + { + "kind": "method", + "name": "isPresent", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "boolean", + "params": [] + }, + { + "kind": "method", + "name": "name", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "String", + "params": [] + }, + { + "kind": "method", + "name": "addressLines", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "List", + "params": [] + }, + { + "kind": "method", + "name": "contacts", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "List", + "params": [] + }, + { + "kind": "method", + "name": "confidentiality", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "String", + "params": [] + } + ] + }, + { + "name": "ProposalGlance", + "binaryName": "com.demcha.compose.document.templates.data.proposal.ProposalGlance", + "kind": "record", + "modifiers": [ + "final" + ], + "artifact": "graph-compose-templates", + "members": [ + { + "kind": "constructor", + "name": "ProposalGlance", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": null, + "params": [ + { + "type": "String", + "name": null + }, + { + "type": "List", + "name": null + }, + { + "type": "String", + "name": null + } + ] + }, + { + "kind": "constructor", + "name": "ProposalGlance", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": null, + "params": [ + { + "type": "String", + "name": "heading" + }, + { + "type": "List", + "name": "facts" + } + ] + }, + { + "kind": "method", + "name": "heading", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "String", + "params": [] + }, + { + "kind": "method", + "name": "facts", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "List", + "params": [] + }, + { + "kind": "method", + "name": "intro", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "String", + "params": [] + } + ] + }, + { + "name": "ProposalGlance.Fact", + "binaryName": "com.demcha.compose.document.templates.data.proposal.ProposalGlance$Fact", + "kind": "record", + "modifiers": [ + "final" + ], + "artifact": "graph-compose-templates", + "members": [ + { + "kind": "constructor", + "name": "Fact", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": null, + "params": [ + { + "type": "String", + "name": null + }, + { + "type": "String", + "name": null + }, + { + "type": "String", + "name": null + }, + { + "type": "String", + "name": null + } + ] + }, + { + "kind": "method", + "name": "icon", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "String", + "params": [] + }, + { + "kind": "method", + "name": "label", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "String", + "params": [] + }, + { + "kind": "method", + "name": "value", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "String", + "params": [] + }, + { + "kind": "method", + "name": "note", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "String", + "params": [] + } + ] + }, + { + "name": "ProposalGoals", + "binaryName": "com.demcha.compose.document.templates.data.proposal.ProposalGoals", + "kind": "record", + "modifiers": [ + "final" + ], + "artifact": "graph-compose-templates", + "members": [ + { + "kind": "constructor", + "name": "ProposalGoals", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": null, + "params": [ + { + "type": "String", + "name": null + }, + { + "type": "String", + "name": null + }, + { + "type": "List", + "name": null + }, + { + "type": "String", + "name": null + } + ] + }, + { + "kind": "constructor", + "name": "ProposalGoals", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": null, + "params": [ + { + "type": "String", + "name": "heading" + }, + { + "type": "String", + "name": "icon" + }, + { + "type": "List", + "name": "items" + } + ] + }, + { + "kind": "method", + "name": "heading", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "String", + "params": [] + }, + { + "kind": "method", + "name": "icon", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "String", + "params": [] + }, + { + "kind": "method", + "name": "items", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "List", + "params": [] + }, + { + "kind": "method", + "name": "intro", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "String", + "params": [] + } + ] + }, + { + "name": "ProposalGoals.Goal", + "binaryName": "com.demcha.compose.document.templates.data.proposal.ProposalGoals$Goal", + "kind": "record", + "modifiers": [ + "final" + ], + "artifact": "graph-compose-templates", + "members": [ + { + "kind": "constructor", + "name": "Goal", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": null, + "params": [ + { + "type": "String", + "name": null + }, + { + "type": "String", + "name": null + } + ] + }, + { + "kind": "method", + "name": "icon", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "String", + "params": [] + }, + { + "kind": "method", + "name": "text", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "String", + "params": [] + } + ] + }, + { + "name": "ProposalInvestment", + "binaryName": "com.demcha.compose.document.templates.data.proposal.ProposalInvestment", + "kind": "record", + "modifiers": [ + "final" + ], + "artifact": "graph-compose-templates", + "members": [ + { + "kind": "constructor", + "name": "ProposalInvestment", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": null, + "params": [ + { + "type": "String", + "name": null + }, + { + "type": "String", + "name": null + }, + { + "type": "String", + "name": null + }, + { + "type": "String", + "name": null + }, + { + "type": "List", + "name": null + }, + { + "type": "String", + "name": null + }, + { + "type": "String", + "name": null + } + ] + }, + { + "kind": "method", + "name": "heading", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "String", + "params": [] + }, + { + "kind": "method", + "name": "icon", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "String", + "params": [] + }, + { + "kind": "method", + "name": "itemHeader", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "String", + "params": [] + }, + { + "kind": "method", + "name": "amountHeader", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "String", + "params": [] + }, + { + "kind": "method", + "name": "rows", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "List", + "params": [] + }, + { + "kind": "method", + "name": "totalLabel", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "String", + "params": [] + }, + { + "kind": "method", + "name": "totalAmount", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "String", + "params": [] + } + ] + }, + { + "name": "ProposalInvestment.Role", + "binaryName": "com.demcha.compose.document.templates.data.proposal.ProposalInvestment$Role", + "kind": "enum", + "modifiers": [ + "final" + ], + "artifact": "graph-compose-templates", + "members": [ + { + "kind": "constant", + "name": "NONE", + "static": true, + "origin": "generated", + "type": "ProposalInvestment.Role" + }, + { + "kind": "constant", + "name": "SUBTOTAL", + "static": true, + "origin": "generated", + "type": "ProposalInvestment.Role" + }, + { + "kind": "constant", + "name": "OPTIONAL", + "static": true, + "origin": "generated", + "type": "ProposalInvestment.Role" + } + ] + }, + { + "name": "ProposalInvestment.Row", + "binaryName": "com.demcha.compose.document.templates.data.proposal.ProposalInvestment$Row", + "kind": "record", + "modifiers": [ + "final" + ], + "artifact": "graph-compose-templates", + "members": [ + { + "kind": "constructor", + "name": "Row", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": null, + "params": [ + { + "type": "String", + "name": null + }, + { + "type": "String", + "name": null + }, + { + "type": "ProposalInvestment.Role", + "name": null + } + ] + }, + { + "kind": "method", + "name": "label", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "String", + "params": [] + }, + { + "kind": "method", + "name": "amount", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "String", + "params": [] + }, + { + "kind": "method", + "name": "role", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "ProposalInvestment.Role", + "params": [] + } + ] + }, + { + "name": "ProposalMetaLine", + "binaryName": "com.demcha.compose.document.templates.data.proposal.ProposalMetaLine", + "kind": "record", + "modifiers": [ + "final" + ], + "artifact": "graph-compose-templates", + "members": [ + { + "kind": "constructor", + "name": "ProposalMetaLine", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": null, + "params": [ + { + "type": "String", + "name": null + }, + { + "type": "String", + "name": null + }, + { + "type": "String", + "name": null + }, + { + "type": "List", + "name": null + } + ] + }, + { + "kind": "constructor", + "name": "ProposalMetaLine", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": null, + "params": [ + { + "type": "String", + "name": "preparedFor" + }, + { + "type": "String", + "name": "preparedBy" + }, + { + "type": "String", + "name": "date" + } + ] + }, + { + "kind": "constructor", + "name": "ProposalMetaLine", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": null, + "params": [ + { + "type": "List", + "name": "entries" + } + ] + }, + { + "kind": "method", + "name": "preparedFor", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "String", + "params": [] + }, + { + "kind": "method", + "name": "preparedBy", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "String", + "params": [] + }, + { + "kind": "method", + "name": "date", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "String", + "params": [] + }, + { + "kind": "method", + "name": "entries", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "List", + "params": [] + } + ] + }, + { + "name": "ProposalMetaLine.Entry", + "binaryName": "com.demcha.compose.document.templates.data.proposal.ProposalMetaLine$Entry", + "kind": "record", + "modifiers": [ + "final" + ], + "artifact": "graph-compose-templates", + "members": [ + { + "kind": "constructor", + "name": "Entry", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": null, + "params": [ + { + "type": "String", + "name": null + }, + { + "type": "String", + "name": null + }, + { + "type": "String", + "name": null + } + ] + }, + { + "kind": "method", + "name": "icon", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "String", + "params": [] + }, + { + "kind": "method", + "name": "label", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "String", + "params": [] + }, + { + "kind": "method", + "name": "value", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "String", + "params": [] + } + ] + }, + { + "name": "ProposalParty", + "binaryName": "com.demcha.compose.document.templates.data.proposal.ProposalParty", + "kind": "record", + "modifiers": [ + "final" + ], + "artifact": "graph-compose-templates", + "members": [ + { + "kind": "constructor", + "name": "ProposalParty", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": null, + "params": [ + { + "type": "String", + "name": null + }, + { + "type": "List", + "name": null + }, + { + "type": "String", + "name": null + }, + { + "type": "String", + "name": null + }, + { + "type": "String", + "name": null + } + ] + }, + { + "kind": "method", + "name": "builder", + "static": true, + "origin": "source", + "typeParameters": null, + "returns": "ProposalParty.Builder", + "params": [] + }, + { + "kind": "method", + "name": "name", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "String", + "params": [] + }, + { + "kind": "method", + "name": "addressLines", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "List", + "params": [] + }, + { + "kind": "method", + "name": "email", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "String", + "params": [] + }, + { + "kind": "method", + "name": "phone", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "String", + "params": [] + }, + { + "kind": "method", + "name": "website", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "String", + "params": [] + } + ] + }, + { + "name": "ProposalParty.Builder", + "binaryName": "com.demcha.compose.document.templates.data.proposal.ProposalParty$Builder", + "kind": "class", + "modifiers": [ + "final" + ], + "artifact": "graph-compose-templates", + "members": [ + { + "kind": "method", + "name": "name", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "ProposalParty.Builder", + "params": [ + { + "type": "String", + "name": "name" + } + ] + }, + { + "kind": "method", + "name": "addressLines", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "ProposalParty.Builder", + "params": [ + { + "type": "List", + "name": "addressLines" + } + ] + }, + { + "kind": "method", + "name": "addressLines", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "ProposalParty.Builder", + "params": [ + { + "type": "String...", + "name": "addressLines" + } + ] + }, + { + "kind": "method", + "name": "addAddressLine", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "ProposalParty.Builder", + "params": [ + { + "type": "String", + "name": "addressLine" + } + ] + }, + { + "kind": "method", + "name": "email", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "ProposalParty.Builder", + "params": [ + { + "type": "String", + "name": "email" + } + ] + }, + { + "kind": "method", + "name": "phone", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "ProposalParty.Builder", + "params": [ + { + "type": "String", + "name": "phone" + } + ] + }, + { + "kind": "method", + "name": "website", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "ProposalParty.Builder", + "params": [ + { + "type": "String", + "name": "website" + } + ] + }, + { + "kind": "method", + "name": "build", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "ProposalParty", + "params": [] + } + ] + }, + { + "name": "ProposalPhaseGrid", + "binaryName": "com.demcha.compose.document.templates.data.proposal.ProposalPhaseGrid", + "kind": "record", + "modifiers": [ + "final" + ], + "artifact": "graph-compose-templates", + "members": [ + { + "kind": "constructor", + "name": "ProposalPhaseGrid", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": null, + "params": [ + { + "type": "String", + "name": null + }, + { + "type": "String", + "name": null + }, + { + "type": "List", + "name": null + }, + { + "type": "List", + "name": null + } + ] + }, + { + "kind": "method", + "name": "heading", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "String", + "params": [] + }, + { + "kind": "method", + "name": "icon", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "String", + "params": [] + }, + { + "kind": "method", + "name": "columnHeaders", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "List", + "params": [] + }, + { + "kind": "method", + "name": "phases", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "List", + "params": [] + } + ] + }, + { + "name": "ProposalPhaseGrid.Phase", + "binaryName": "com.demcha.compose.document.templates.data.proposal.ProposalPhaseGrid$Phase", + "kind": "record", + "modifiers": [ + "final" + ], + "artifact": "graph-compose-templates", + "members": [ + { + "kind": "constructor", + "name": "Phase", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": null, + "params": [ + { + "type": "String", + "name": null + }, + { + "type": "String", + "name": null + }, + { + "type": "String", + "name": null + }, + { + "type": "String", + "name": null + }, + { + "type": "String", + "name": null + } + ] + }, + { + "kind": "method", + "name": "number", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "String", + "params": [] + }, + { + "kind": "method", + "name": "name", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "String", + "params": [] + }, + { + "kind": "method", + "name": "focus", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "String", + "params": [] + }, + { + "kind": "method", + "name": "duration", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "String", + "params": [] + }, + { + "kind": "method", + "name": "output", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "String", + "params": [] + } + ] + }, + { + "name": "ProposalPricingRow", + "binaryName": "com.demcha.compose.document.templates.data.proposal.ProposalPricingRow", + "kind": "record", + "modifiers": [ + "final" + ], + "artifact": "graph-compose-templates", + "members": [ + { + "kind": "constructor", + "name": "ProposalPricingRow", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": null, + "params": [ + { + "type": "String", + "name": null + }, + { + "type": "String", + "name": null + }, + { + "type": "String", + "name": null + }, + { + "type": "boolean", + "name": null + } + ] + }, + { + "kind": "method", + "name": "builder", + "static": true, + "origin": "source", + "typeParameters": null, + "returns": "ProposalPricingRow.Builder", + "params": [] + }, + { + "kind": "method", + "name": "label", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "String", + "params": [] + }, + { + "kind": "method", + "name": "description", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "String", + "params": [] + }, + { + "kind": "method", + "name": "amount", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "String", + "params": [] + }, + { + "kind": "method", + "name": "emphasized", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "boolean", + "params": [] + } + ] + }, + { + "name": "ProposalPricingRow.Builder", + "binaryName": "com.demcha.compose.document.templates.data.proposal.ProposalPricingRow$Builder", + "kind": "class", + "modifiers": [ + "final" + ], + "artifact": "graph-compose-templates", + "members": [ + { + "kind": "method", + "name": "label", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "ProposalPricingRow.Builder", + "params": [ + { + "type": "String", + "name": "label" + } + ] + }, + { + "kind": "method", + "name": "description", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "ProposalPricingRow.Builder", + "params": [ + { + "type": "String", + "name": "description" + } + ] + }, + { + "kind": "method", + "name": "amount", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "ProposalPricingRow.Builder", + "params": [ + { + "type": "String", + "name": "amount" + } + ] + }, + { + "kind": "method", + "name": "emphasized", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "ProposalPricingRow.Builder", + "params": [ + { + "type": "boolean", + "name": "emphasized" + } + ] + }, + { + "kind": "method", + "name": "build", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "ProposalPricingRow", + "params": [] + } + ] + }, + { + "name": "ProposalRecipient", + "binaryName": "com.demcha.compose.document.templates.data.proposal.ProposalRecipient", + "kind": "record", + "modifiers": [ + "final" + ], + "artifact": "graph-compose-templates", + "members": [ + { + "kind": "constructor", + "name": "ProposalRecipient", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": null, + "params": [ + { + "type": "String", + "name": null + }, + { + "type": "String", + "name": null + }, + { + "type": "List", + "name": null + } + ] + }, + { + "kind": "method", + "name": "isPresent", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "boolean", + "params": [] + }, + { + "kind": "method", + "name": "label", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "String", + "params": [] + }, + { + "kind": "method", + "name": "name", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "String", + "params": [] + }, + { + "kind": "method", + "name": "addressLines", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "List", + "params": [] + } + ] + }, + { + "name": "ProposalScope", + "binaryName": "com.demcha.compose.document.templates.data.proposal.ProposalScope", + "kind": "record", + "modifiers": [ + "final" + ], + "artifact": "graph-compose-templates", + "members": [ + { + "kind": "constructor", + "name": "ProposalScope", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": null, + "params": [ + { + "type": "String", + "name": null + }, + { + "type": "String", + "name": null + }, + { + "type": "List", + "name": null + }, + { + "type": "String", + "name": null + } + ] + }, + { + "kind": "constructor", + "name": "ProposalScope", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": null, + "params": [ + { + "type": "String", + "name": "heading" + }, + { + "type": "String", + "name": "icon" + }, + { + "type": "List", + "name": "items" + } + ] + }, + { + "kind": "method", + "name": "heading", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "String", + "params": [] + }, + { + "kind": "method", + "name": "icon", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "String", + "params": [] + }, + { + "kind": "method", + "name": "items", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "List", + "params": [] + }, + { + "kind": "method", + "name": "intro", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "String", + "params": [] + } + ] + }, + { + "name": "ProposalScope.Item", + "binaryName": "com.demcha.compose.document.templates.data.proposal.ProposalScope$Item", + "kind": "record", + "modifiers": [ + "final" + ], + "artifact": "graph-compose-templates", + "members": [ + { + "kind": "constructor", + "name": "Item", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": null, + "params": [ + { + "type": "String", + "name": null + }, + { + "type": "String", + "name": null + }, + { + "type": "String", + "name": null + } + ] + }, + { + "kind": "method", + "name": "number", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "String", + "params": [] + }, + { + "kind": "method", + "name": "title", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "String", + "params": [] + }, + { + "kind": "method", + "name": "description", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "String", + "params": [] + } + ] + }, + { + "name": "ProposalSection", + "binaryName": "com.demcha.compose.document.templates.data.proposal.ProposalSection", + "kind": "record", + "modifiers": [ + "final" + ], + "artifact": "graph-compose-templates", + "members": [ + { + "kind": "constructor", + "name": "ProposalSection", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": null, + "params": [ + { + "type": "String", + "name": null + }, + { + "type": "List", + "name": null + } + ] + }, + { + "kind": "method", + "name": "builder", + "static": true, + "origin": "source", + "typeParameters": null, + "returns": "ProposalSection.Builder", + "params": [] + }, + { + "kind": "method", + "name": "title", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "String", + "params": [] + }, + { + "kind": "method", + "name": "paragraphs", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "List", + "params": [] + } + ] + }, + { + "name": "ProposalSection.Builder", + "binaryName": "com.demcha.compose.document.templates.data.proposal.ProposalSection$Builder", + "kind": "class", + "modifiers": [ + "final" + ], + "artifact": "graph-compose-templates", + "members": [ + { + "kind": "method", + "name": "title", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "ProposalSection.Builder", + "params": [ + { + "type": "String", + "name": "title" + } + ] + }, + { + "kind": "method", + "name": "paragraphs", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "ProposalSection.Builder", + "params": [ + { + "type": "List", + "name": "paragraphs" + } + ] + }, + { + "kind": "method", + "name": "paragraphs", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "ProposalSection.Builder", + "params": [ + { + "type": "String...", + "name": "paragraphs" + } + ] + }, + { + "kind": "method", + "name": "addParagraph", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "ProposalSection.Builder", + "params": [ + { + "type": "String", + "name": "paragraph" + } + ] + }, + { + "kind": "method", + "name": "build", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "ProposalSection", + "params": [] + } + ] + }, + { + "name": "ProposalSummaryBlock", + "binaryName": "com.demcha.compose.document.templates.data.proposal.ProposalSummaryBlock", + "kind": "record", + "modifiers": [ + "final" + ], + "artifact": "graph-compose-templates", + "members": [ + { + "kind": "constructor", + "name": "ProposalSummaryBlock", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": null, + "params": [ + { + "type": "String", + "name": null + }, + { + "type": "String", + "name": null + }, + { + "type": "List", + "name": null + } + ] + }, + { + "kind": "method", + "name": "heading", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "String", + "params": [] + }, + { + "kind": "method", + "name": "icon", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "String", + "params": [] + }, + { + "kind": "method", + "name": "paragraphs", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "List", + "params": [] + } + ] + }, + { + "name": "ProposalTermsBlock", + "binaryName": "com.demcha.compose.document.templates.data.proposal.ProposalTermsBlock", + "kind": "record", + "modifiers": [ + "final" + ], + "artifact": "graph-compose-templates", + "members": [ + { + "kind": "constructor", + "name": "ProposalTermsBlock", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": null, + "params": [ + { + "type": "String", + "name": null + }, + { + "type": "String", + "name": null + }, + { + "type": "List", + "name": null + } + ] + }, + { + "kind": "method", + "name": "heading", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "String", + "params": [] + }, + { + "kind": "method", + "name": "icon", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "String", + "params": [] + }, + { + "kind": "method", + "name": "items", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "List", + "params": [] + } + ] + }, + { + "name": "ProposalTimelineItem", + "binaryName": "com.demcha.compose.document.templates.data.proposal.ProposalTimelineItem", + "kind": "record", + "modifiers": [ + "final" + ], + "artifact": "graph-compose-templates", + "members": [ + { + "kind": "constructor", + "name": "ProposalTimelineItem", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": null, + "params": [ + { + "type": "String", + "name": null + }, + { + "type": "String", + "name": null + }, + { + "type": "String", + "name": null + } + ] + }, + { + "kind": "method", + "name": "builder", + "static": true, + "origin": "source", + "typeParameters": null, + "returns": "ProposalTimelineItem.Builder", + "params": [] + }, + { + "kind": "method", + "name": "phase", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "String", + "params": [] + }, + { + "kind": "method", + "name": "duration", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "String", + "params": [] + }, + { + "kind": "method", + "name": "details", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "String", + "params": [] + } + ] + }, + { + "name": "ProposalTimelineItem.Builder", + "binaryName": "com.demcha.compose.document.templates.data.proposal.ProposalTimelineItem$Builder", + "kind": "class", + "modifiers": [ + "final" + ], + "artifact": "graph-compose-templates", + "members": [ + { + "kind": "method", + "name": "phase", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "ProposalTimelineItem.Builder", + "params": [ + { + "type": "String", + "name": "phase" + } + ] + }, + { + "kind": "method", + "name": "duration", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "ProposalTimelineItem.Builder", + "params": [ + { + "type": "String", + "name": "duration" + } + ] + }, + { + "kind": "method", + "name": "details", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "ProposalTimelineItem.Builder", + "params": [ + { + "type": "String", + "name": "details" + } + ] + }, + { + "kind": "method", + "name": "build", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "ProposalTimelineItem", + "params": [] + } + ] + }, + { + "name": "ProposalTitleLines", + "binaryName": "com.demcha.compose.document.templates.data.proposal.ProposalTitleLines", + "kind": "record", + "modifiers": [ + "final" + ], + "artifact": "graph-compose-templates", + "members": [ + { + "kind": "constructor", + "name": "ProposalTitleLines", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": null, + "params": [ + { + "type": "String", + "name": null + }, + { + "type": "String", + "name": null + }, + { + "type": "String", + "name": null + }, + { + "type": "String", + "name": null + }, + { + "type": "List", + "name": null + }, + { + "type": "List", + "name": null + } + ] + }, + { + "kind": "constructor", + "name": "ProposalTitleLines", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": null, + "params": [ + { + "type": "String", + "name": "lead" + }, + { + "type": "String", + "name": "second" + }, + { + "type": "String", + "name": "third" + } + ] + }, + { + "kind": "method", + "name": "of", + "static": true, + "origin": "source", + "typeParameters": null, + "returns": "ProposalTitleLines", + "params": [ + { + "type": "String", + "name": "eyebrow" + }, + { + "type": "List", + "name": "lines" + }, + { + "type": "List", + "name": "standfirst" + } + ] + }, + { + "kind": "method", + "name": "lead", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "String", + "params": [] + }, + { + "kind": "method", + "name": "second", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "String", + "params": [] + }, + { + "kind": "method", + "name": "third", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "String", + "params": [] + }, + { + "kind": "method", + "name": "eyebrow", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "String", + "params": [] + }, + { + "kind": "method", + "name": "lines", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "List", + "params": [] + }, + { + "kind": "method", + "name": "standfirst", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "List", + "params": [] + } + ] + }, + { + "name": "StructuredProposalData", + "binaryName": "com.demcha.compose.document.templates.data.proposal.StructuredProposalData", + "kind": "record", + "modifiers": [ + "final" + ], + "artifact": "graph-compose-templates", + "members": [ + { + "kind": "constructor", + "name": "StructuredProposalData", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": null, + "params": [ + { + "type": "ProposalBrand", + "name": null + }, + { + "type": "ProposalTitleLines", + "name": null + }, + { + "type": "ProposalMetaLine", + "name": null + }, + { + "type": "ProposalSummaryBlock", + "name": null + }, + { + "type": "ProposalGlance", + "name": null + }, + { + "type": "ProposalGoals", + "name": null + }, + { + "type": "ProposalScope", + "name": null + }, + { + "type": "ProposalDeliverables", + "name": null + }, + { + "type": "ProposalPhaseGrid", + "name": null + }, + { + "type": "ProposalInvestment", + "name": null + }, + { + "type": "ProposalTermsBlock", + "name": null + }, + { + "type": "ProposalAcceptance", + "name": null + }, + { + "type": "ProposalRecipient", + "name": null + }, + { + "type": "ProposalAttention", + "name": null + }, + { + "type": "ProposalFooter", + "name": null + } + ] + }, + { + "kind": "constructor", + "name": "StructuredProposalData", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": null, + "params": [ + { + "type": "ProposalBrand", + "name": "brand" + }, + { + "type": "ProposalTitleLines", + "name": "title" + }, + { + "type": "ProposalMetaLine", + "name": "meta" + }, + { + "type": "ProposalSummaryBlock", + "name": "executiveSummary" + }, + { + "type": "ProposalGlance", + "name": "glance" + }, + { + "type": "ProposalGoals", + "name": "goals" + }, + { + "type": "ProposalScope", + "name": "scope" + }, + { + "type": "ProposalDeliverables", + "name": "deliverables" + }, + { + "type": "ProposalPhaseGrid", + "name": "timeline" + }, + { + "type": "ProposalInvestment", + "name": "investment" + }, + { + "type": "ProposalTermsBlock", + "name": "terms" + }, + { + "type": "ProposalAcceptance", + "name": "acceptance" + } + ] + }, + { + "kind": "method", + "name": "builder", + "static": true, + "origin": "source", + "typeParameters": null, + "returns": "StructuredProposalData.Builder", + "params": [] + }, + { + "kind": "method", + "name": "brand", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "ProposalBrand", + "params": [] + }, + { + "kind": "method", + "name": "title", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "ProposalTitleLines", + "params": [] + }, + { + "kind": "method", + "name": "meta", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "ProposalMetaLine", + "params": [] + }, + { + "kind": "method", + "name": "executiveSummary", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "ProposalSummaryBlock", + "params": [] + }, + { + "kind": "method", + "name": "glance", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "ProposalGlance", + "params": [] + }, + { + "kind": "method", + "name": "goals", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "ProposalGoals", + "params": [] + }, + { + "kind": "method", + "name": "scope", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "ProposalScope", + "params": [] + }, + { + "kind": "method", + "name": "deliverables", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "ProposalDeliverables", + "params": [] + }, + { + "kind": "method", + "name": "timeline", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "ProposalPhaseGrid", + "params": [] + }, + { + "kind": "method", + "name": "investment", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "ProposalInvestment", + "params": [] + }, + { + "kind": "method", + "name": "terms", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "ProposalTermsBlock", + "params": [] + }, + { + "kind": "method", + "name": "acceptance", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "ProposalAcceptance", + "params": [] + }, + { + "kind": "method", + "name": "recipient", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "ProposalRecipient", + "params": [] + }, + { + "kind": "method", + "name": "attention", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "ProposalAttention", + "params": [] + }, + { + "kind": "method", + "name": "footer", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "ProposalFooter", + "params": [] + } + ] + }, + { + "name": "StructuredProposalData.Builder", + "binaryName": "com.demcha.compose.document.templates.data.proposal.StructuredProposalData$Builder", + "kind": "class", + "modifiers": [ + "final" + ], + "artifact": "graph-compose-templates", + "members": [ + { + "kind": "method", + "name": "brand", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "StructuredProposalData.Builder", + "params": [ + { + "type": "ProposalBrand", + "name": "brand" + } + ] + }, + { + "kind": "method", + "name": "title", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "StructuredProposalData.Builder", + "params": [ + { + "type": "ProposalTitleLines", + "name": "title" + } + ] + }, + { + "kind": "method", + "name": "meta", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "StructuredProposalData.Builder", + "params": [ + { + "type": "ProposalMetaLine", + "name": "meta" + } + ] + }, + { + "kind": "method", + "name": "executiveSummary", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "StructuredProposalData.Builder", + "params": [ + { + "type": "ProposalSummaryBlock", + "name": "executiveSummary" + } + ] + }, + { + "kind": "method", + "name": "glance", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "StructuredProposalData.Builder", + "params": [ + { + "type": "ProposalGlance", + "name": "glance" + } + ] + }, + { + "kind": "method", + "name": "goals", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "StructuredProposalData.Builder", + "params": [ + { + "type": "ProposalGoals", + "name": "goals" + } + ] + }, + { + "kind": "method", + "name": "scope", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "StructuredProposalData.Builder", + "params": [ + { + "type": "ProposalScope", + "name": "scope" + } + ] + }, + { + "kind": "method", + "name": "deliverables", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "StructuredProposalData.Builder", + "params": [ + { + "type": "ProposalDeliverables", + "name": "deliverables" + } + ] + }, + { + "kind": "method", + "name": "timeline", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "StructuredProposalData.Builder", + "params": [ + { + "type": "ProposalPhaseGrid", + "name": "timeline" + } + ] + }, + { + "kind": "method", + "name": "investment", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "StructuredProposalData.Builder", + "params": [ + { + "type": "ProposalInvestment", + "name": "investment" + } + ] + }, + { + "kind": "method", + "name": "terms", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "StructuredProposalData.Builder", + "params": [ + { + "type": "ProposalTermsBlock", + "name": "terms" + } + ] + }, + { + "kind": "method", + "name": "acceptance", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "StructuredProposalData.Builder", + "params": [ + { + "type": "ProposalAcceptance", + "name": "acceptance" + } + ] + }, + { + "kind": "method", + "name": "recipient", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "StructuredProposalData.Builder", + "params": [ + { + "type": "ProposalRecipient", + "name": "recipient" + } + ] + }, + { + "kind": "method", + "name": "attention", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "StructuredProposalData.Builder", + "params": [ + { + "type": "ProposalAttention", + "name": "attention" + } + ] + }, + { + "kind": "method", + "name": "footer", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "StructuredProposalData.Builder", + "params": [ + { + "type": "ProposalFooter", + "name": "footer" + } + ] + }, + { + "kind": "method", + "name": "build", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "StructuredProposalData", + "params": [] + } + ] + }, + { + "name": "StructuredProposalDocumentSpec", + "binaryName": "com.demcha.compose.document.templates.data.proposal.StructuredProposalDocumentSpec", + "kind": "record", + "modifiers": [ + "final" + ], + "artifact": "graph-compose-templates", + "members": [ + { + "kind": "constructor", + "name": "StructuredProposalDocumentSpec", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": null, + "params": [ + { + "type": "StructuredProposalData", + "name": null + } + ] + }, + { + "kind": "method", + "name": "from", + "static": true, + "origin": "source", + "typeParameters": null, + "returns": "StructuredProposalDocumentSpec", + "params": [ + { + "type": "StructuredProposalData", + "name": "proposal" + } + ] + }, + { + "kind": "method", + "name": "proposal", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "StructuredProposalData", + "params": [] + } + ] + } + ] + }, + { + "name": "com.demcha.compose.document.templates.data.receipt", + "types": [ + { + "name": "ReceiptData", + "binaryName": "com.demcha.compose.document.templates.data.receipt.ReceiptData", + "kind": "record", + "modifiers": [ + "final" + ], + "artifact": "graph-compose-templates", + "members": [ + { + "kind": "constructor", + "name": "ReceiptData", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": null, + "params": [ + { + "type": "String", + "name": null + }, + { + "type": "String", + "name": null + }, + { + "type": "String", + "name": null + }, + { + "type": "String", + "name": null + }, + { + "type": "String", + "name": null + }, + { + "type": "String", + "name": null + }, + { + "type": "String", + "name": null + }, + { + "type": "ReceiptStatus", + "name": null + }, + { + "type": "List", + "name": null + }, + { + "type": "String", + "name": null + }, + { + "type": "ReceiptParty", + "name": null + }, + { + "type": "String", + "name": null + }, + { + "type": "ReceiptParty", + "name": null + }, + { + "type": "List", + "name": null + }, + { + "type": "List", + "name": null + }, + { + "type": "List", + "name": null + }, + { + "type": "String", + "name": null + }, + { + "type": "String", + "name": null + }, + { + "type": "List", + "name": null + }, + { + "type": "String", + "name": null + } + ] + }, + { + "kind": "method", + "name": "hasPayer", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "boolean", + "params": [] + }, + { + "kind": "method", + "name": "hasBeneficiary", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "boolean", + "params": [] + }, + { + "kind": "method", + "name": "builder", + "static": true, + "origin": "source", + "typeParameters": null, + "returns": "ReceiptData.Builder", + "params": [] + }, + { + "kind": "method", + "name": "documentTitle", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "String", + "params": [] + }, + { + "kind": "method", + "name": "issuerName", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "String", + "params": [] + }, + { + "kind": "method", + "name": "generatedOn", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "String", + "params": [] + }, + { + "kind": "method", + "name": "reference", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "String", + "params": [] + }, + { + "kind": "method", + "name": "amountLabel", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "String", + "params": [] + }, + { + "kind": "method", + "name": "amount", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "String", + "params": [] + }, + { + "kind": "method", + "name": "amountCaption", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "String", + "params": [] + }, + { + "kind": "method", + "name": "status", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "ReceiptStatus", + "params": [] + }, + { + "kind": "method", + "name": "summaryFields", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "List", + "params": [] + }, + { + "kind": "method", + "name": "payerLabel", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "String", + "params": [] + }, + { + "kind": "method", + "name": "payer", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "ReceiptParty", + "params": [] + }, + { + "kind": "method", + "name": "beneficiaryLabel", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "String", + "params": [] + }, + { + "kind": "method", + "name": "beneficiary", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "ReceiptParty", + "params": [] + }, + { + "kind": "method", + "name": "detailGroups", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "List", + "params": [] + }, + { + "kind": "method", + "name": "timeline", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "List", + "params": [] + }, + { + "kind": "method", + "name": "notes", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "List", + "params": [] + }, + { + "kind": "method", + "name": "verificationUrl", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "String", + "params": [] + }, + { + "kind": "method", + "name": "verificationText", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "String", + "params": [] + }, + { + "kind": "method", + "name": "supportLines", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "List", + "params": [] + }, + { + "kind": "method", + "name": "legalNote", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "String", + "params": [] + } + ] + }, + { + "name": "ReceiptData.Builder", + "binaryName": "com.demcha.compose.document.templates.data.receipt.ReceiptData$Builder", + "kind": "class", + "modifiers": [ + "final" + ], + "artifact": "graph-compose-templates", + "members": [ + { + "kind": "method", + "name": "documentTitle", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "ReceiptData.Builder", + "params": [ + { + "type": "String", + "name": "documentTitle" + } + ] + }, + { + "kind": "method", + "name": "issuerName", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "ReceiptData.Builder", + "params": [ + { + "type": "String", + "name": "issuerName" + } + ] + }, + { + "kind": "method", + "name": "generatedOn", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "ReceiptData.Builder", + "params": [ + { + "type": "String", + "name": "generatedOn" + } + ] + }, + { + "kind": "method", + "name": "reference", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "ReceiptData.Builder", + "params": [ + { + "type": "String", + "name": "reference" + } + ] + }, + { + "kind": "method", + "name": "amount", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "ReceiptData.Builder", + "params": [ + { + "type": "String", + "name": "label" + }, + { + "type": "String", + "name": "amount" + } + ] + }, + { + "kind": "method", + "name": "amountCaption", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "ReceiptData.Builder", + "params": [ + { + "type": "String", + "name": "amountCaption" + } + ] + }, + { + "kind": "method", + "name": "status", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "ReceiptData.Builder", + "params": [ + { + "type": "ReceiptStatus", + "name": "status" + } + ] + }, + { + "kind": "method", + "name": "status", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "ReceiptData.Builder", + "params": [ + { + "type": "String", + "name": "label" + }, + { + "type": "ReceiptStatusTone", + "name": "tone" + } + ] + }, + { + "kind": "method", + "name": "summaryField", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "ReceiptData.Builder", + "params": [ + { + "type": "String", + "name": "label" + }, + { + "type": "String", + "name": "value" + } + ] + }, + { + "kind": "method", + "name": "payer", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "ReceiptData.Builder", + "params": [ + { + "type": "String", + "name": "label" + }, + { + "type": "Consumer", + "name": "spec" + } + ] + }, + { + "kind": "method", + "name": "beneficiary", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "ReceiptData.Builder", + "params": [ + { + "type": "String", + "name": "label" + }, + { + "type": "Consumer", + "name": "spec" + } + ] + }, + { + "kind": "method", + "name": "detailGroup", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "ReceiptData.Builder", + "params": [ + { + "type": "String", + "name": "title" + }, + { + "type": "Consumer", + "name": "spec" + } + ] + }, + { + "kind": "method", + "name": "addDetailGroup", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "ReceiptData.Builder", + "params": [ + { + "type": "ReceiptFieldGroup", + "name": "group" + } + ] + }, + { + "kind": "method", + "name": "event", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "ReceiptData.Builder", + "params": [ + { + "type": "String", + "name": "label" + }, + { + "type": "String", + "name": "timestamp" + }, + { + "type": "String", + "name": "detail" + } + ] + }, + { + "kind": "method", + "name": "note", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "ReceiptData.Builder", + "params": [ + { + "type": "String", + "name": "note" + } + ] + }, + { + "kind": "method", + "name": "verification", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "ReceiptData.Builder", + "params": [ + { + "type": "String", + "name": "url" + }, + { + "type": "String", + "name": "text" + } + ] + }, + { + "kind": "method", + "name": "supportLine", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "ReceiptData.Builder", + "params": [ + { + "type": "String", + "name": "line" + } + ] + }, + { + "kind": "method", + "name": "legalNote", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "ReceiptData.Builder", + "params": [ + { + "type": "String", + "name": "legalNote" + } + ] + }, + { + "kind": "method", + "name": "build", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "ReceiptData", + "params": [] + } + ] + }, + { + "name": "ReceiptDocumentSpec", + "binaryName": "com.demcha.compose.document.templates.data.receipt.ReceiptDocumentSpec", + "kind": "record", + "modifiers": [ + "final" + ], + "artifact": "graph-compose-templates", + "members": [ + { + "kind": "constructor", + "name": "ReceiptDocumentSpec", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": null, + "params": [ + { + "type": "ReceiptData", + "name": null + } + ] + }, + { + "kind": "method", + "name": "from", + "static": true, + "origin": "source", + "typeParameters": null, + "returns": "ReceiptDocumentSpec", + "params": [ + { + "type": "ReceiptData", + "name": "receipt" + } + ] + }, + { + "kind": "method", + "name": "of", + "static": true, + "origin": "source", + "typeParameters": null, + "returns": "ReceiptDocumentSpec", + "params": [ + { + "type": "Consumer", + "name": "spec" + } + ] + }, + { + "kind": "method", + "name": "receipt", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "ReceiptData", + "params": [] + } + ] + }, + { + "name": "ReceiptEvent", + "binaryName": "com.demcha.compose.document.templates.data.receipt.ReceiptEvent", + "kind": "record", + "modifiers": [ + "final" + ], + "artifact": "graph-compose-templates", + "members": [ + { + "kind": "constructor", + "name": "ReceiptEvent", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": null, + "params": [ + { + "type": "String", + "name": null + }, + { + "type": "String", + "name": null + }, + { + "type": "String", + "name": null + } + ] + }, + { + "kind": "method", + "name": "of", + "static": true, + "origin": "source", + "typeParameters": null, + "returns": "ReceiptEvent", + "params": [ + { + "type": "String", + "name": "label" + }, + { + "type": "String", + "name": "timestamp" + } + ] + }, + { + "kind": "method", + "name": "label", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "String", + "params": [] + }, + { + "kind": "method", + "name": "timestamp", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "String", + "params": [] + }, + { + "kind": "method", + "name": "detail", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "String", + "params": [] + } + ] + }, + { + "name": "ReceiptField", + "binaryName": "com.demcha.compose.document.templates.data.receipt.ReceiptField", + "kind": "record", + "modifiers": [ + "final" + ], + "artifact": "graph-compose-templates", + "members": [ + { + "kind": "constructor", + "name": "ReceiptField", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": null, + "params": [ + { + "type": "String", + "name": null + }, + { + "type": "String", + "name": null + }, + { + "type": "boolean", + "name": null + } + ] + }, + { + "kind": "method", + "name": "of", + "static": true, + "origin": "source", + "typeParameters": null, + "returns": "ReceiptField", + "params": [ + { + "type": "String", + "name": "label" + }, + { + "type": "String", + "name": "value" + } + ] + }, + { + "kind": "method", + "name": "emphasized", + "static": true, + "origin": "source", + "typeParameters": null, + "returns": "ReceiptField", + "params": [ + { + "type": "String", + "name": "label" + }, + { + "type": "String", + "name": "value" + } + ] + }, + { + "kind": "method", + "name": "label", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "String", + "params": [] + }, + { + "kind": "method", + "name": "value", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "String", + "params": [] + }, + { + "kind": "method", + "name": "emphasized", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "boolean", + "params": [] + } + ] + }, + { + "name": "ReceiptFieldGroup", + "binaryName": "com.demcha.compose.document.templates.data.receipt.ReceiptFieldGroup", + "kind": "record", + "modifiers": [ + "final" + ], + "artifact": "graph-compose-templates", + "members": [ + { + "kind": "constructor", + "name": "ReceiptFieldGroup", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": null, + "params": [ + { + "type": "String", + "name": null + }, + { + "type": "List", + "name": null + } + ] + }, + { + "kind": "method", + "name": "builder", + "static": true, + "origin": "source", + "typeParameters": null, + "returns": "ReceiptFieldGroup.Builder", + "params": [ + { + "type": "String", + "name": "title" + } + ] + }, + { + "kind": "method", + "name": "title", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "String", + "params": [] + }, + { + "kind": "method", + "name": "fields", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "List", + "params": [] + } + ] + }, + { + "name": "ReceiptFieldGroup.Builder", + "binaryName": "com.demcha.compose.document.templates.data.receipt.ReceiptFieldGroup$Builder", + "kind": "class", + "modifiers": [ + "final" + ], + "artifact": "graph-compose-templates", + "members": [ + { + "kind": "method", + "name": "field", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "ReceiptFieldGroup.Builder", + "params": [ + { + "type": "String", + "name": "label" + }, + { + "type": "String", + "name": "value" + } + ] + }, + { + "kind": "method", + "name": "emphasized", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "ReceiptFieldGroup.Builder", + "params": [ + { + "type": "String", + "name": "label" + }, + { + "type": "String", + "name": "value" + } + ] + }, + { + "kind": "method", + "name": "add", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "ReceiptFieldGroup.Builder", + "params": [ + { + "type": "ReceiptField", + "name": "field" + } + ] + }, + { + "kind": "method", + "name": "build", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "ReceiptFieldGroup", + "params": [] + } + ] + }, + { + "name": "ReceiptParty", + "binaryName": "com.demcha.compose.document.templates.data.receipt.ReceiptParty", + "kind": "record", + "modifiers": [ + "final" + ], + "artifact": "graph-compose-templates", + "members": [ + { + "kind": "constructor", + "name": "ReceiptParty", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": null, + "params": [ + { + "type": "String", + "name": null + }, + { + "type": "List", + "name": null + }, + { + "type": "List", + "name": null + } + ] + }, + { + "kind": "method", + "name": "builder", + "static": true, + "origin": "source", + "typeParameters": null, + "returns": "ReceiptParty.Builder", + "params": [] + }, + { + "kind": "method", + "name": "name", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "String", + "params": [] + }, + { + "kind": "method", + "name": "addressLines", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "List", + "params": [] + }, + { + "kind": "method", + "name": "fields", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "List", + "params": [] + } + ] + }, + { + "name": "ReceiptParty.Builder", + "binaryName": "com.demcha.compose.document.templates.data.receipt.ReceiptParty$Builder", + "kind": "class", + "modifiers": [ + "final" + ], + "artifact": "graph-compose-templates", + "members": [ + { + "kind": "method", + "name": "name", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "ReceiptParty.Builder", + "params": [ + { + "type": "String", + "name": "name" + } + ] + }, + { + "kind": "method", + "name": "addressLines", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "ReceiptParty.Builder", + "params": [ + { + "type": "String...", + "name": "lines" + } + ] + }, + { + "kind": "method", + "name": "field", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "ReceiptParty.Builder", + "params": [ + { + "type": "String", + "name": "label" + }, + { + "type": "String", + "name": "value" + } + ] + }, + { + "kind": "method", + "name": "build", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "ReceiptParty", + "params": [] + } + ] + }, + { + "name": "ReceiptStatus", + "binaryName": "com.demcha.compose.document.templates.data.receipt.ReceiptStatus", + "kind": "record", + "modifiers": [ + "final" + ], + "artifact": "graph-compose-templates", + "members": [ + { + "kind": "constructor", + "name": "ReceiptStatus", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": null, + "params": [ + { + "type": "String", + "name": null + }, + { + "type": "ReceiptStatusTone", + "name": null + } + ] + }, + { + "kind": "method", + "name": "settled", + "static": true, + "origin": "source", + "typeParameters": null, + "returns": "ReceiptStatus", + "params": [ + { + "type": "String", + "name": "label" + } + ] + }, + { + "kind": "method", + "name": "inProgress", + "static": true, + "origin": "source", + "typeParameters": null, + "returns": "ReceiptStatus", + "params": [ + { + "type": "String", + "name": "label" + } + ] + }, + { + "kind": "method", + "name": "hasLabel", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "boolean", + "params": [] + }, + { + "kind": "method", + "name": "label", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "String", + "params": [] + }, + { + "kind": "method", + "name": "tone", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "ReceiptStatusTone", + "params": [] + } + ] + }, + { + "name": "ReceiptStatusTone", + "binaryName": "com.demcha.compose.document.templates.data.receipt.ReceiptStatusTone", + "kind": "enum", + "modifiers": [ + "final" + ], + "artifact": "graph-compose-templates", + "members": [ + { + "kind": "constant", + "name": "SETTLED", + "static": true, + "origin": "generated", + "type": "ReceiptStatusTone" + }, + { + "kind": "constant", + "name": "IN_PROGRESS", + "static": true, + "origin": "generated", + "type": "ReceiptStatusTone" + }, + { + "kind": "constant", + "name": "ATTENTION", + "static": true, + "origin": "generated", + "type": "ReceiptStatusTone" + }, + { + "kind": "constant", + "name": "FAILED", + "static": true, + "origin": "generated", + "type": "ReceiptStatusTone" + } + ] + } + ] + }, + { + "name": "com.demcha.compose.document.templates.data.rota", + "types": [ + { + "name": "RotaCovers", + "binaryName": "com.demcha.compose.document.templates.data.rota.RotaCovers", + "kind": "record", + "modifiers": [ + "final" + ], + "artifact": "graph-compose-templates", + "members": [ + { + "kind": "constructor", + "name": "RotaCovers", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": null, + "params": [ + { + "type": "String", + "name": null + }, + { + "type": "String", + "name": null + } + ] + }, + { + "kind": "method", + "name": "isPresent", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "boolean", + "params": [] + }, + { + "kind": "method", + "name": "lunch", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "String", + "params": [] + }, + { + "kind": "method", + "name": "dinner", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "String", + "params": [] + } + ] + }, + { + "name": "RotaDay", + "binaryName": "com.demcha.compose.document.templates.data.rota.RotaDay", + "kind": "record", + "modifiers": [ + "final" + ], + "artifact": "graph-compose-templates", + "members": [ + { + "kind": "constructor", + "name": "RotaDay", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": null, + "params": [ + { + "type": "String", + "name": null + }, + { + "type": "String", + "name": null + }, + { + "type": "String", + "name": null + }, + { + "type": "String", + "name": null + }, + { + "type": "RotaCovers", + "name": null + } + ] + }, + { + "kind": "constructor", + "name": "RotaDay", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": null, + "params": [ + { + "type": "String", + "name": "name" + }, + { + "type": "String", + "name": "ordinal" + }, + { + "type": "String", + "name": "ordinalSuffix" + } + ] + }, + { + "kind": "method", + "name": "name", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "String", + "params": [] + }, + { + "kind": "method", + "name": "ordinal", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "String", + "params": [] + }, + { + "kind": "method", + "name": "ordinalSuffix", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "String", + "params": [] + }, + { + "kind": "method", + "name": "note", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "String", + "params": [] + }, + { + "kind": "method", + "name": "covers", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "RotaCovers", + "params": [] + } + ] + }, + { + "name": "RotaFooter", + "binaryName": "com.demcha.compose.document.templates.data.rota.RotaFooter", + "kind": "record", + "modifiers": [ + "final" + ], + "artifact": "graph-compose-templates", + "members": [ + { + "kind": "constructor", + "name": "RotaFooter", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": null, + "params": [ + { + "type": "String", + "name": null + } + ] + }, + { + "kind": "method", + "name": "isPresent", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "boolean", + "params": [] + }, + { + "kind": "method", + "name": "note", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "String", + "params": [] + } + ] + }, + { + "name": "RotaGroup", + "binaryName": "com.demcha.compose.document.templates.data.rota.RotaGroup", + "kind": "record", + "modifiers": [ + "final" + ], + "artifact": "graph-compose-templates", + "members": [ + { + "kind": "constructor", + "name": "RotaGroup", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": null, + "params": [ + { + "type": "String", + "name": null + }, + { + "type": "String", + "name": null + }, + { + "type": "List", + "name": null + } + ] + }, + { + "kind": "constructor", + "name": "RotaGroup", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": null, + "params": [ + { + "type": "String", + "name": "label" + }, + { + "type": "List", + "name": "staff" + } + ] + }, + { + "kind": "method", + "name": "label", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "String", + "params": [] + }, + { + "kind": "method", + "name": "icon", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "String", + "params": [] + }, + { + "kind": "method", + "name": "staff", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "List", + "params": [] + } + ] + }, + { + "name": "RotaLegend", + "binaryName": "com.demcha.compose.document.templates.data.rota.RotaLegend", + "kind": "record", + "modifiers": [ + "final" + ], + "artifact": "graph-compose-templates", + "members": [ + { + "kind": "constructor", + "name": "RotaLegend", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": null, + "params": [ + { + "type": "String", + "name": null + }, + { + "type": "String", + "name": null + }, + { + "type": "String", + "name": null + }, + { + "type": "String", + "name": null + }, + { + "type": "List", + "name": null + } + ] + }, + { + "kind": "constructor", + "name": "RotaLegend", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": null, + "params": [ + { + "type": "String", + "name": "label" + }, + { + "type": "List", + "name": "entries" + } + ] + }, + { + "kind": "method", + "name": "isPresent", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "boolean", + "params": [] + }, + { + "kind": "method", + "name": "label", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "String", + "params": [] + }, + { + "kind": "method", + "name": "coversLabel", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "String", + "params": [] + }, + { + "kind": "method", + "name": "coversLunchLabel", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "String", + "params": [] + }, + { + "kind": "method", + "name": "coversDinnerLabel", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "String", + "params": [] + }, + { + "kind": "method", + "name": "entries", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "List", + "params": [] + } + ] + }, + { + "name": "RotaLegend.Entry", + "binaryName": "com.demcha.compose.document.templates.data.rota.RotaLegend$Entry", + "kind": "record", + "modifiers": [ + "final" + ], + "artifact": "graph-compose-templates", + "members": [ + { + "kind": "constructor", + "name": "Entry", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": null, + "params": [ + { + "type": "String", + "name": null + }, + { + "type": "ShiftStatus", + "name": null + } + ] + }, + { + "kind": "method", + "name": "label", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "String", + "params": [] + }, + { + "kind": "method", + "name": "status", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "ShiftStatus", + "params": [] + } + ] + }, + { + "name": "RotaShift", + "binaryName": "com.demcha.compose.document.templates.data.rota.RotaShift", + "kind": "record", + "modifiers": [ + "final" + ], + "artifact": "graph-compose-templates", + "members": [ + { + "kind": "constructor", + "name": "RotaShift", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": null, + "params": [ + { + "type": "String", + "name": null + }, + { + "type": "ShiftStatus", + "name": null + }, + { + "type": "ShiftEmphasis", + "name": null + } + ] + }, + { + "kind": "method", + "name": "hours", + "static": true, + "origin": "source", + "typeParameters": null, + "returns": "RotaShift", + "params": [ + { + "type": "String", + "name": "text" + } + ] + }, + { + "kind": "method", + "name": "strong", + "static": true, + "origin": "source", + "typeParameters": null, + "returns": "RotaShift", + "params": [ + { + "type": "String", + "name": "text" + }, + { + "type": "ShiftStatus", + "name": "status" + } + ] + }, + { + "kind": "method", + "name": "soft", + "static": true, + "origin": "source", + "typeParameters": null, + "returns": "RotaShift", + "params": [ + { + "type": "String", + "name": "text" + }, + { + "type": "ShiftStatus", + "name": "status" + } + ] + }, + { + "kind": "method", + "name": "text", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "String", + "params": [] + }, + { + "kind": "method", + "name": "status", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "ShiftStatus", + "params": [] + }, + { + "kind": "method", + "name": "emphasis", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "ShiftEmphasis", + "params": [] + } + ] + }, + { + "name": "RotaStaff", + "binaryName": "com.demcha.compose.document.templates.data.rota.RotaStaff", + "kind": "record", + "modifiers": [ + "final" + ], + "artifact": "graph-compose-templates", + "members": [ + { + "kind": "constructor", + "name": "RotaStaff", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": null, + "params": [ + { + "type": "String", + "name": null + }, + { + "type": "List>", + "name": null + } + ] + }, + { + "kind": "method", + "name": "day", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "List", + "params": [ + { + "type": "int", + "name": "index" + } + ] + }, + { + "kind": "method", + "name": "name", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "String", + "params": [] + }, + { + "kind": "method", + "name": "days", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "List>", + "params": [] + } + ] + }, + { + "name": "RotaVenue", + "binaryName": "com.demcha.compose.document.templates.data.rota.RotaVenue", + "kind": "record", + "modifiers": [ + "final" + ], + "artifact": "graph-compose-templates", + "members": [ + { + "kind": "constructor", + "name": "RotaVenue", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": null, + "params": [ + { + "type": "String", + "name": null + }, + { + "type": "String", + "name": null + }, + { + "type": "String", + "name": null + } + ] + }, + { + "kind": "method", + "name": "isPresent", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "boolean", + "params": [] + }, + { + "kind": "method", + "name": "wordmark", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "String", + "params": [] + }, + { + "kind": "method", + "name": "wordmarkSub", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "String", + "params": [] + }, + { + "kind": "method", + "name": "footerName", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "String", + "params": [] + } + ] + }, + { + "name": "RotaWeek", + "binaryName": "com.demcha.compose.document.templates.data.rota.RotaWeek", + "kind": "record", + "modifiers": [ + "final" + ], + "artifact": "graph-compose-templates", + "members": [ + { + "kind": "constructor", + "name": "RotaWeek", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": null, + "params": [ + { + "type": "String", + "name": null + }, + { + "type": "String", + "name": null + } + ] + }, + { + "kind": "method", + "name": "isPresent", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "boolean", + "params": [] + }, + { + "kind": "method", + "name": "title", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "String", + "params": [] + }, + { + "kind": "method", + "name": "rangeLabel", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "String", + "params": [] + } + ] + }, + { + "name": "ShiftEmphasis", + "binaryName": "com.demcha.compose.document.templates.data.rota.ShiftEmphasis", + "kind": "enum", + "modifiers": [ + "final" + ], + "artifact": "graph-compose-templates", + "members": [ + { + "kind": "constant", + "name": "STRONG", + "static": true, + "origin": "generated", + "type": "ShiftEmphasis" + }, + { + "kind": "constant", + "name": "SOFT", + "static": true, + "origin": "generated", + "type": "ShiftEmphasis" + }, + { + "kind": "constant", + "name": "PLAIN", + "static": true, + "origin": "generated", + "type": "ShiftEmphasis" + } + ] + }, + { + "name": "ShiftStatus", + "binaryName": "com.demcha.compose.document.templates.data.rota.ShiftStatus", + "kind": "enum", + "modifiers": [ + "final" + ], + "artifact": "graph-compose-templates", + "members": [ + { + "kind": "constant", + "name": "NONE", + "static": true, + "origin": "generated", + "type": "ShiftStatus" + }, + { + "kind": "constant", + "name": "REQUEST", + "static": true, + "origin": "generated", + "type": "ShiftStatus" + }, + { + "kind": "constant", + "name": "OFF", + "static": true, + "origin": "generated", + "type": "ShiftStatus" + }, + { + "kind": "constant", + "name": "HOLIDAY", + "static": true, + "origin": "generated", + "type": "ShiftStatus" + }, + { + "kind": "constant", + "name": "STOCK", + "static": true, + "origin": "generated", + "type": "ShiftStatus" + }, + { + "kind": "constant", + "name": "STANDBY", + "static": true, + "origin": "generated", + "type": "ShiftStatus" + }, + { + "kind": "constant", + "name": "TRAINING", + "static": true, + "origin": "generated", + "type": "ShiftStatus" + }, + { + "kind": "constant", + "name": "SUPPORT", + "static": true, + "origin": "generated", + "type": "ShiftStatus" + } + ] + }, + { + "name": "StructuredRotaData", + "binaryName": "com.demcha.compose.document.templates.data.rota.StructuredRotaData", + "kind": "record", + "modifiers": [ + "final" + ], + "artifact": "graph-compose-templates", + "members": [ + { + "kind": "constructor", + "name": "StructuredRotaData", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": null, + "params": [ + { + "type": "RotaVenue", + "name": null + }, + { + "type": "RotaWeek", + "name": null + }, + { + "type": "List", + "name": null + }, + { + "type": "RotaLegend", + "name": null + }, + { + "type": "List", + "name": null + }, + { + "type": "RotaFooter", + "name": null + } + ] + }, + { + "kind": "method", + "name": "builder", + "static": true, + "origin": "source", + "typeParameters": null, + "returns": "StructuredRotaData.Builder", + "params": [] + }, + { + "kind": "method", + "name": "venue", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "RotaVenue", + "params": [] + }, + { + "kind": "method", + "name": "week", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "RotaWeek", + "params": [] + }, + { + "kind": "method", + "name": "days", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "List", + "params": [] + }, + { + "kind": "method", + "name": "legend", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "RotaLegend", + "params": [] + }, + { + "kind": "method", + "name": "groups", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "List", + "params": [] + }, + { + "kind": "method", + "name": "footer", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "RotaFooter", + "params": [] + } + ] + }, + { + "name": "StructuredRotaData.Builder", + "binaryName": "com.demcha.compose.document.templates.data.rota.StructuredRotaData$Builder", + "kind": "class", + "modifiers": [ + "final" + ], + "artifact": "graph-compose-templates", + "members": [ + { + "kind": "method", + "name": "venue", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "StructuredRotaData.Builder", + "params": [ + { + "type": "RotaVenue", + "name": "venue" + } + ] + }, + { + "kind": "method", + "name": "week", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "StructuredRotaData.Builder", + "params": [ + { + "type": "RotaWeek", + "name": "week" + } + ] + }, + { + "kind": "method", + "name": "days", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "StructuredRotaData.Builder", + "params": [ + { + "type": "List", + "name": "days" + } + ] + }, + { + "kind": "method", + "name": "legend", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "StructuredRotaData.Builder", + "params": [ + { + "type": "RotaLegend", + "name": "legend" + } + ] + }, + { + "kind": "method", + "name": "groups", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "StructuredRotaData.Builder", + "params": [ + { + "type": "List", + "name": "groups" + } + ] + }, + { + "kind": "method", + "name": "footer", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "StructuredRotaData.Builder", + "params": [ + { + "type": "RotaFooter", + "name": "footer" + } + ] + }, + { + "kind": "method", + "name": "build", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "StructuredRotaData", + "params": [] + } + ] + }, + { + "name": "StructuredRotaDocumentSpec", + "binaryName": "com.demcha.compose.document.templates.data.rota.StructuredRotaDocumentSpec", + "kind": "record", + "modifiers": [ + "final" + ], + "artifact": "graph-compose-templates", + "members": [ + { + "kind": "constructor", + "name": "StructuredRotaDocumentSpec", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": null, + "params": [ + { + "type": "StructuredRotaData", + "name": null + } + ] + }, + { + "kind": "method", + "name": "from", + "static": true, + "origin": "source", + "typeParameters": null, + "returns": "StructuredRotaDocumentSpec", + "params": [ + { + "type": "StructuredRotaData", + "name": "rota" + } + ] + }, + { + "kind": "method", + "name": "rota", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "StructuredRotaData", + "params": [] + } + ] + } + ] + }, + { + "name": "com.demcha.compose.document.templates.data.schedule", + "types": [ + { + "name": "ScheduleAssignment", + "binaryName": "com.demcha.compose.document.templates.data.schedule.ScheduleAssignment", + "kind": "record", + "modifiers": [ + "final" + ], + "artifact": "graph-compose-templates", + "members": [ + { + "kind": "constructor", + "name": "ScheduleAssignment", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": null, + "params": [ + { + "type": "String", + "name": null + }, + { + "type": "String", + "name": null + }, + { + "type": "String", + "name": null + }, + { + "type": "List", + "name": null + }, + { + "type": "String", + "name": null + } + ] + }, + { + "kind": "method", + "name": "builder", + "static": true, + "origin": "source", + "typeParameters": null, + "returns": "ScheduleAssignment.Builder", + "params": [] + }, + { + "kind": "method", + "name": "personId", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "String", + "params": [] + }, + { + "kind": "method", + "name": "dayId", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "String", + "params": [] + }, + { + "kind": "method", + "name": "categoryId", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "String", + "params": [] + }, + { + "kind": "method", + "name": "slots", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "List", + "params": [] + }, + { + "kind": "method", + "name": "note", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "String", + "params": [] + } + ] + }, + { + "name": "ScheduleAssignment.Builder", + "binaryName": "com.demcha.compose.document.templates.data.schedule.ScheduleAssignment$Builder", + "kind": "class", + "modifiers": [ + "final" + ], + "artifact": "graph-compose-templates", + "members": [ + { + "kind": "method", + "name": "personId", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "ScheduleAssignment.Builder", + "params": [ + { + "type": "String", + "name": "personId" + } + ] + }, + { + "kind": "method", + "name": "dayId", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "ScheduleAssignment.Builder", + "params": [ + { + "type": "String", + "name": "dayId" + } + ] + }, + { + "kind": "method", + "name": "categoryId", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "ScheduleAssignment.Builder", + "params": [ + { + "type": "String", + "name": "categoryId" + } + ] + }, + { + "kind": "method", + "name": "slots", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "ScheduleAssignment.Builder", + "params": [ + { + "type": "List", + "name": "slots" + } + ] + }, + { + "kind": "method", + "name": "slots", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "ScheduleAssignment.Builder", + "params": [ + { + "type": "ScheduleSlot...", + "name": "slots" + } + ] + }, + { + "kind": "method", + "name": "addSlot", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "ScheduleAssignment.Builder", + "params": [ + { + "type": "ScheduleSlot", + "name": "slot" + } + ] + }, + { + "kind": "method", + "name": "slot", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "ScheduleAssignment.Builder", + "params": [ + { + "type": "String", + "name": "start" + }, + { + "type": "String", + "name": "end" + } + ] + }, + { + "kind": "method", + "name": "note", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "ScheduleAssignment.Builder", + "params": [ + { + "type": "String", + "name": "note" + } + ] + }, + { + "kind": "method", + "name": "build", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "ScheduleAssignment", + "params": [] + } + ] + }, + { + "name": "ScheduleCategory", + "binaryName": "com.demcha.compose.document.templates.data.schedule.ScheduleCategory", + "kind": "record", + "modifiers": [ + "final" + ], + "artifact": "graph-compose-templates", + "members": [ + { + "kind": "constructor", + "name": "ScheduleCategory", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": null, + "params": [ + { + "type": "String", + "name": null + }, + { + "type": "String", + "name": null + }, + { + "type": "Color", + "name": null + }, + { + "type": "Color", + "name": null + }, + { + "type": "Color", + "name": null + } + ] + }, + { + "kind": "method", + "name": "of", + "static": true, + "origin": "source", + "typeParameters": null, + "returns": "ScheduleCategory", + "params": [ + { + "type": "String", + "name": "id" + }, + { + "type": "String", + "name": "label" + }, + { + "type": "Color", + "name": "fillColor" + }, + { + "type": "Color", + "name": "borderColor" + } + ] + }, + { + "kind": "method", + "name": "of", + "static": true, + "origin": "source", + "typeParameters": null, + "returns": "ScheduleCategory", + "params": [ + { + "type": "String", + "name": "id" + }, + { + "type": "String", + "name": "label" + }, + { + "type": "Color", + "name": "fillColor" + }, + { + "type": "Color", + "name": "textColor" + }, + { + "type": "Color", + "name": "borderColor" + } + ] + }, + { + "kind": "method", + "name": "builder", + "static": true, + "origin": "source", + "typeParameters": null, + "returns": "ScheduleCategory.Builder", + "params": [] + }, + { + "kind": "method", + "name": "id", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "String", + "params": [] + }, + { + "kind": "method", + "name": "label", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "String", + "params": [] + }, + { + "kind": "method", + "name": "fillColor", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "Color", + "params": [] + }, + { + "kind": "method", + "name": "textColor", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "Color", + "params": [] + }, + { + "kind": "method", + "name": "borderColor", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "Color", + "params": [] + } + ] + }, + { + "name": "ScheduleCategory.Builder", + "binaryName": "com.demcha.compose.document.templates.data.schedule.ScheduleCategory$Builder", + "kind": "class", + "modifiers": [ + "final" + ], + "artifact": "graph-compose-templates", + "members": [ + { + "kind": "method", + "name": "id", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "ScheduleCategory.Builder", + "params": [ + { + "type": "String", + "name": "id" + } + ] + }, + { + "kind": "method", + "name": "label", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "ScheduleCategory.Builder", + "params": [ + { + "type": "String", + "name": "label" + } + ] + }, + { + "kind": "method", + "name": "fillColor", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "ScheduleCategory.Builder", + "params": [ + { + "type": "Color", + "name": "fillColor" + } + ] + }, + { + "kind": "method", + "name": "textColor", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "ScheduleCategory.Builder", + "params": [ + { + "type": "Color", + "name": "textColor" + } + ] + }, + { + "kind": "method", + "name": "borderColor", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "ScheduleCategory.Builder", + "params": [ + { + "type": "Color", + "name": "borderColor" + } + ] + }, + { + "kind": "method", + "name": "build", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "ScheduleCategory", + "params": [] + } + ] + }, + { + "name": "ScheduleDay", + "binaryName": "com.demcha.compose.document.templates.data.schedule.ScheduleDay", + "kind": "record", + "modifiers": [ + "final" + ], + "artifact": "graph-compose-templates", + "members": [ + { + "kind": "constructor", + "name": "ScheduleDay", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": null, + "params": [ + { + "type": "String", + "name": null + }, + { + "type": "String", + "name": null + }, + { + "type": "String", + "name": null + }, + { + "type": "String", + "name": null + } + ] + }, + { + "kind": "method", + "name": "of", + "static": true, + "origin": "source", + "typeParameters": null, + "returns": "ScheduleDay", + "params": [ + { + "type": "String", + "name": "id" + }, + { + "type": "String", + "name": "label" + }, + { + "type": "String", + "name": "headerNote" + }, + { + "type": "String", + "name": "headerCategoryId" + } + ] + }, + { + "kind": "method", + "name": "builder", + "static": true, + "origin": "source", + "typeParameters": null, + "returns": "ScheduleDay.Builder", + "params": [] + }, + { + "kind": "method", + "name": "id", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "String", + "params": [] + }, + { + "kind": "method", + "name": "label", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "String", + "params": [] + }, + { + "kind": "method", + "name": "headerNote", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "String", + "params": [] + }, + { + "kind": "method", + "name": "headerCategoryId", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "String", + "params": [] + } + ] + }, + { + "name": "ScheduleDay.Builder", + "binaryName": "com.demcha.compose.document.templates.data.schedule.ScheduleDay$Builder", + "kind": "class", + "modifiers": [ + "final" + ], + "artifact": "graph-compose-templates", + "members": [ + { + "kind": "method", + "name": "id", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "ScheduleDay.Builder", + "params": [ + { + "type": "String", + "name": "id" + } + ] + }, + { + "kind": "method", + "name": "label", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "ScheduleDay.Builder", + "params": [ + { + "type": "String", + "name": "label" + } + ] + }, + { + "kind": "method", + "name": "headerNote", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "ScheduleDay.Builder", + "params": [ + { + "type": "String", + "name": "headerNote" + } + ] + }, + { + "kind": "method", + "name": "headerCategoryId", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "ScheduleDay.Builder", + "params": [ + { + "type": "String", + "name": "headerCategoryId" + } + ] + }, + { + "kind": "method", + "name": "build", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "ScheduleDay", + "params": [] + } + ] + }, + { + "name": "ScheduleMetricRow", + "binaryName": "com.demcha.compose.document.templates.data.schedule.ScheduleMetricRow", + "kind": "record", + "modifiers": [ + "final" + ], + "artifact": "graph-compose-templates", + "members": [ + { + "kind": "constructor", + "name": "ScheduleMetricRow", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": null, + "params": [ + { + "type": "String", + "name": null + }, + { + "type": "List", + "name": null + } + ] + }, + { + "kind": "method", + "name": "of", + "static": true, + "origin": "source", + "typeParameters": null, + "returns": "ScheduleMetricRow", + "params": [ + { + "type": "String", + "name": "label" + }, + { + "type": "String...", + "name": "dayValues" + } + ] + }, + { + "kind": "method", + "name": "builder", + "static": true, + "origin": "source", + "typeParameters": null, + "returns": "ScheduleMetricRow.Builder", + "params": [] + }, + { + "kind": "method", + "name": "label", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "String", + "params": [] + }, + { + "kind": "method", + "name": "dayValues", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "List", + "params": [] + } + ] + }, + { + "name": "ScheduleMetricRow.Builder", + "binaryName": "com.demcha.compose.document.templates.data.schedule.ScheduleMetricRow$Builder", + "kind": "class", + "modifiers": [ + "final" + ], + "artifact": "graph-compose-templates", + "members": [ + { + "kind": "method", + "name": "label", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "ScheduleMetricRow.Builder", + "params": [ + { + "type": "String", + "name": "label" + } + ] + }, + { + "kind": "method", + "name": "dayValues", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "ScheduleMetricRow.Builder", + "params": [ + { + "type": "List", + "name": "dayValues" + } + ] + }, + { + "kind": "method", + "name": "dayValues", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "ScheduleMetricRow.Builder", + "params": [ + { + "type": "String...", + "name": "dayValues" + } + ] + }, + { + "kind": "method", + "name": "addDayValue", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "ScheduleMetricRow.Builder", + "params": [ + { + "type": "String", + "name": "dayValue" + } + ] + }, + { + "kind": "method", + "name": "build", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "ScheduleMetricRow", + "params": [] + } + ] + }, + { + "name": "SchedulePerson", + "binaryName": "com.demcha.compose.document.templates.data.schedule.SchedulePerson", + "kind": "record", + "modifiers": [ + "final" + ], + "artifact": "graph-compose-templates", + "members": [ + { + "kind": "constructor", + "name": "SchedulePerson", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": null, + "params": [ + { + "type": "String", + "name": null + }, + { + "type": "String", + "name": null + }, + { + "type": "int", + "name": null + } + ] + }, + { + "kind": "method", + "name": "of", + "static": true, + "origin": "source", + "typeParameters": null, + "returns": "SchedulePerson", + "params": [ + { + "type": "String", + "name": "id" + }, + { + "type": "String", + "name": "displayName" + }, + { + "type": "int", + "name": "sortOrder" + } + ] + }, + { + "kind": "method", + "name": "builder", + "static": true, + "origin": "source", + "typeParameters": null, + "returns": "SchedulePerson.Builder", + "params": [] + }, + { + "kind": "method", + "name": "id", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "String", + "params": [] + }, + { + "kind": "method", + "name": "displayName", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "String", + "params": [] + }, + { + "kind": "method", + "name": "sortOrder", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "int", + "params": [] + } + ] + }, + { + "name": "SchedulePerson.Builder", + "binaryName": "com.demcha.compose.document.templates.data.schedule.SchedulePerson$Builder", + "kind": "class", + "modifiers": [ + "final" + ], + "artifact": "graph-compose-templates", + "members": [ + { + "kind": "method", + "name": "id", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "SchedulePerson.Builder", + "params": [ + { + "type": "String", + "name": "id" + } + ] + }, + { + "kind": "method", + "name": "displayName", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "SchedulePerson.Builder", + "params": [ + { + "type": "String", + "name": "displayName" + } + ] + }, + { + "kind": "method", + "name": "sortOrder", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "SchedulePerson.Builder", + "params": [ + { + "type": "int", + "name": "sortOrder" + } + ] + }, + { + "kind": "method", + "name": "build", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "SchedulePerson", + "params": [] + } + ] + }, + { + "name": "ScheduleSlot", + "binaryName": "com.demcha.compose.document.templates.data.schedule.ScheduleSlot", + "kind": "record", + "modifiers": [ + "final" + ], + "artifact": "graph-compose-templates", + "members": [ + { + "kind": "constructor", + "name": "ScheduleSlot", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": null, + "params": [ + { + "type": "String", + "name": null + }, + { + "type": "String", + "name": null + } + ] + }, + { + "kind": "method", + "name": "of", + "static": true, + "origin": "source", + "typeParameters": null, + "returns": "ScheduleSlot", + "params": [ + { + "type": "String", + "name": "start" + }, + { + "type": "String", + "name": "end" + } + ] + }, + { + "kind": "method", + "name": "builder", + "static": true, + "origin": "source", + "typeParameters": null, + "returns": "ScheduleSlot.Builder", + "params": [] + }, + { + "kind": "method", + "name": "displayText", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "String", + "params": [] + }, + { + "kind": "method", + "name": "start", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "String", + "params": [] + }, + { + "kind": "method", + "name": "end", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "String", + "params": [] + } + ] + }, + { + "name": "ScheduleSlot.Builder", + "binaryName": "com.demcha.compose.document.templates.data.schedule.ScheduleSlot$Builder", + "kind": "class", + "modifiers": [ + "final" + ], + "artifact": "graph-compose-templates", + "members": [ + { + "kind": "method", + "name": "start", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "ScheduleSlot.Builder", + "params": [ + { + "type": "String", + "name": "start" + } + ] + }, + { + "kind": "method", + "name": "end", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "ScheduleSlot.Builder", + "params": [ + { + "type": "String", + "name": "end" + } + ] + }, + { + "kind": "method", + "name": "build", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "ScheduleSlot", + "params": [] + } + ] + }, + { + "name": "WeeklyScheduleData", + "binaryName": "com.demcha.compose.document.templates.data.schedule.WeeklyScheduleData", + "kind": "record", + "modifiers": [ + "final" + ], + "artifact": "graph-compose-templates", + "members": [ + { + "kind": "constructor", + "name": "WeeklyScheduleData", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": null, + "params": [ + { + "type": "String", + "name": null + }, + { + "type": "String", + "name": null + }, + { + "type": "List", + "name": null + }, + { + "type": "List", + "name": null + }, + { + "type": "List", + "name": null + }, + { + "type": "List", + "name": null + }, + { + "type": "List", + "name": null + }, + { + "type": "List", + "name": null + } + ] + }, + { + "kind": "method", + "name": "builder", + "static": true, + "origin": "source", + "typeParameters": null, + "returns": "WeeklyScheduleData.Builder", + "params": [] + }, + { + "kind": "method", + "name": "title", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "String", + "params": [] + }, + { + "kind": "method", + "name": "weekLabel", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "String", + "params": [] + }, + { + "kind": "method", + "name": "days", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "List", + "params": [] + }, + { + "kind": "method", + "name": "categories", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "List", + "params": [] + }, + { + "kind": "method", + "name": "headerMetrics", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "List", + "params": [] + }, + { + "kind": "method", + "name": "people", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "List", + "params": [] + }, + { + "kind": "method", + "name": "assignments", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "List", + "params": [] + }, + { + "kind": "method", + "name": "footerNotes", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "List", + "params": [] + } + ] + }, + { + "name": "WeeklyScheduleData.Builder", + "binaryName": "com.demcha.compose.document.templates.data.schedule.WeeklyScheduleData$Builder", + "kind": "class", + "modifiers": [ + "final" + ], + "artifact": "graph-compose-templates", + "members": [ + { + "kind": "method", + "name": "title", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "WeeklyScheduleData.Builder", + "params": [ + { + "type": "String", + "name": "title" + } + ] + }, + { + "kind": "method", + "name": "weekLabel", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "WeeklyScheduleData.Builder", + "params": [ + { + "type": "String", + "name": "weekLabel" + } + ] + }, + { + "kind": "method", + "name": "days", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "WeeklyScheduleData.Builder", + "params": [ + { + "type": "List", + "name": "days" + } + ] + }, + { + "kind": "method", + "name": "addDay", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "WeeklyScheduleData.Builder", + "params": [ + { + "type": "ScheduleDay", + "name": "day" + } + ] + }, + { + "kind": "method", + "name": "day", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "WeeklyScheduleData.Builder", + "params": [ + { + "type": "Consumer", + "name": "spec" + } + ] + }, + { + "kind": "method", + "name": "day", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "WeeklyScheduleData.Builder", + "params": [ + { + "type": "String", + "name": "id" + }, + { + "type": "String", + "name": "label" + }, + { + "type": "String", + "name": "headerNote" + }, + { + "type": "String", + "name": "headerCategoryId" + } + ] + }, + { + "kind": "method", + "name": "categories", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "WeeklyScheduleData.Builder", + "params": [ + { + "type": "List", + "name": "categories" + } + ] + }, + { + "kind": "method", + "name": "addCategory", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "WeeklyScheduleData.Builder", + "params": [ + { + "type": "ScheduleCategory", + "name": "category" + } + ] + }, + { + "kind": "method", + "name": "category", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "WeeklyScheduleData.Builder", + "params": [ + { + "type": "Consumer", + "name": "spec" + } + ] + }, + { + "kind": "method", + "name": "category", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "WeeklyScheduleData.Builder", + "params": [ + { + "type": "String", + "name": "id" + }, + { + "type": "String", + "name": "label" + }, + { + "type": "Color", + "name": "fillColor" + }, + { + "type": "Color", + "name": "borderColor" + } + ] + }, + { + "kind": "method", + "name": "category", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "WeeklyScheduleData.Builder", + "params": [ + { + "type": "String", + "name": "id" + }, + { + "type": "String", + "name": "label" + }, + { + "type": "Color", + "name": "fillColor" + }, + { + "type": "Color", + "name": "textColor" + }, + { + "type": "Color", + "name": "borderColor" + } + ] + }, + { + "kind": "method", + "name": "headerMetrics", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "WeeklyScheduleData.Builder", + "params": [ + { + "type": "List", + "name": "headerMetrics" + } + ] + }, + { + "kind": "method", + "name": "addHeaderMetric", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "WeeklyScheduleData.Builder", + "params": [ + { + "type": "ScheduleMetricRow", + "name": "headerMetric" + } + ] + }, + { + "kind": "method", + "name": "headerMetric", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "WeeklyScheduleData.Builder", + "params": [ + { + "type": "Consumer", + "name": "spec" + } + ] + }, + { + "kind": "method", + "name": "headerMetric", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "WeeklyScheduleData.Builder", + "params": [ + { + "type": "String", + "name": "label" + }, + { + "type": "List", + "name": "dayValues" + } + ] + }, + { + "kind": "method", + "name": "headerMetric", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "WeeklyScheduleData.Builder", + "params": [ + { + "type": "String", + "name": "label" + }, + { + "type": "String...", + "name": "dayValues" + } + ] + }, + { + "kind": "method", + "name": "people", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "WeeklyScheduleData.Builder", + "params": [ + { + "type": "List", + "name": "people" + } + ] + }, + { + "kind": "method", + "name": "addPerson", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "WeeklyScheduleData.Builder", + "params": [ + { + "type": "SchedulePerson", + "name": "person" + } + ] + }, + { + "kind": "method", + "name": "person", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "WeeklyScheduleData.Builder", + "params": [ + { + "type": "Consumer", + "name": "spec" + } + ] + }, + { + "kind": "method", + "name": "person", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "WeeklyScheduleData.Builder", + "params": [ + { + "type": "String", + "name": "id" + }, + { + "type": "String", + "name": "displayName" + }, + { + "type": "int", + "name": "sortOrder" + } + ] + }, + { + "kind": "method", + "name": "assignments", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "WeeklyScheduleData.Builder", + "params": [ + { + "type": "List", + "name": "assignments" + } + ] + }, + { + "kind": "method", + "name": "addAssignment", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "WeeklyScheduleData.Builder", + "params": [ + { + "type": "ScheduleAssignment", + "name": "assignment" + } + ] + }, + { + "kind": "method", + "name": "assignment", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "WeeklyScheduleData.Builder", + "params": [ + { + "type": "Consumer", + "name": "spec" + } + ] + }, + { + "kind": "method", + "name": "assignment", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "WeeklyScheduleData.Builder", + "params": [ + { + "type": "String", + "name": "personId" + }, + { + "type": "String", + "name": "dayId" + }, + { + "type": "String", + "name": "categoryId" + }, + { + "type": "ScheduleSlot...", + "name": "slots" + } + ] + }, + { + "kind": "method", + "name": "assignment", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "WeeklyScheduleData.Builder", + "params": [ + { + "type": "String", + "name": "personId" + }, + { + "type": "String", + "name": "dayId" + }, + { + "type": "String", + "name": "categoryId" + }, + { + "type": "List", + "name": "slots" + }, + { + "type": "String", + "name": "note" + } + ] + }, + { + "kind": "method", + "name": "footerNotes", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "WeeklyScheduleData.Builder", + "params": [ + { + "type": "List", + "name": "footerNotes" + } + ] + }, + { + "kind": "method", + "name": "footerNote", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "WeeklyScheduleData.Builder", + "params": [ + { + "type": "String", + "name": "footerNote" + } + ] + }, + { + "kind": "method", + "name": "build", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "WeeklyScheduleData", + "params": [] + } + ] + }, + { + "name": "WeeklyScheduleDocumentSpec", + "binaryName": "com.demcha.compose.document.templates.data.schedule.WeeklyScheduleDocumentSpec", + "kind": "record", + "modifiers": [ + "final" + ], + "artifact": "graph-compose-templates", + "members": [ + { + "kind": "constructor", + "name": "WeeklyScheduleDocumentSpec", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": null, + "params": [ + { + "type": "WeeklyScheduleData", + "name": null + } + ] + }, + { + "kind": "method", + "name": "from", + "static": true, + "origin": "source", + "typeParameters": null, + "returns": "WeeklyScheduleDocumentSpec", + "params": [ + { + "type": "WeeklyScheduleData", + "name": "schedule" + } + ] + }, + { + "kind": "method", + "name": "builder", + "static": true, + "origin": "source", + "typeParameters": null, + "returns": "WeeklyScheduleDocumentSpec.Builder", + "params": [] + }, + { + "kind": "method", + "name": "schedule", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "WeeklyScheduleData", + "params": [] + } + ] + }, + { + "name": "WeeklyScheduleDocumentSpec.Builder", + "binaryName": "com.demcha.compose.document.templates.data.schedule.WeeklyScheduleDocumentSpec$Builder", + "kind": "class", + "modifiers": [ + "final" + ], + "artifact": "graph-compose-templates", + "members": [ + { + "kind": "method", + "name": "title", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "WeeklyScheduleDocumentSpec.Builder", + "params": [ + { + "type": "String", + "name": "title" + } + ] + }, + { + "kind": "method", + "name": "weekLabel", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "WeeklyScheduleDocumentSpec.Builder", + "params": [ + { + "type": "String", + "name": "weekLabel" + } + ] + }, + { + "kind": "method", + "name": "days", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "WeeklyScheduleDocumentSpec.Builder", + "params": [ + { + "type": "List", + "name": "days" + } + ] + }, + { + "kind": "method", + "name": "addDay", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "WeeklyScheduleDocumentSpec.Builder", + "params": [ + { + "type": "ScheduleDay", + "name": "day" + } + ] + }, + { + "kind": "method", + "name": "day", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "WeeklyScheduleDocumentSpec.Builder", + "params": [ + { + "type": "Consumer", + "name": "spec" + } + ] + }, + { + "kind": "method", + "name": "day", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "WeeklyScheduleDocumentSpec.Builder", + "params": [ + { + "type": "String", + "name": "id" + }, + { + "type": "String", + "name": "label" + }, + { + "type": "String", + "name": "headerNote" + }, + { + "type": "String", + "name": "headerCategoryId" + } + ] + }, + { + "kind": "method", + "name": "categories", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "WeeklyScheduleDocumentSpec.Builder", + "params": [ + { + "type": "List", + "name": "categories" + } + ] + }, + { + "kind": "method", + "name": "addCategory", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "WeeklyScheduleDocumentSpec.Builder", + "params": [ + { + "type": "ScheduleCategory", + "name": "category" + } + ] + }, + { + "kind": "method", + "name": "category", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "WeeklyScheduleDocumentSpec.Builder", + "params": [ + { + "type": "Consumer", + "name": "spec" + } + ] + }, + { + "kind": "method", + "name": "category", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "WeeklyScheduleDocumentSpec.Builder", + "params": [ + { + "type": "String", + "name": "id" + }, + { + "type": "String", + "name": "label" + }, + { + "type": "Color", + "name": "fillColor" + }, + { + "type": "Color", + "name": "borderColor" + } + ] + }, + { + "kind": "method", + "name": "category", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "WeeklyScheduleDocumentSpec.Builder", + "params": [ + { + "type": "String", + "name": "id" + }, + { + "type": "String", + "name": "label" + }, + { + "type": "Color", + "name": "fillColor" + }, + { + "type": "Color", + "name": "textColor" + }, + { + "type": "Color", + "name": "borderColor" + } + ] + }, + { + "kind": "method", + "name": "headerMetrics", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "WeeklyScheduleDocumentSpec.Builder", + "params": [ + { + "type": "List", + "name": "headerMetrics" + } + ] + }, + { + "kind": "method", + "name": "addHeaderMetric", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "WeeklyScheduleDocumentSpec.Builder", + "params": [ + { + "type": "ScheduleMetricRow", + "name": "headerMetric" + } + ] + }, + { + "kind": "method", + "name": "headerMetric", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "WeeklyScheduleDocumentSpec.Builder", + "params": [ + { + "type": "Consumer", + "name": "spec" + } + ] + }, + { + "kind": "method", + "name": "headerMetric", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "WeeklyScheduleDocumentSpec.Builder", + "params": [ + { + "type": "String", + "name": "label" + }, + { + "type": "List", + "name": "dayValues" + } + ] + }, + { + "kind": "method", + "name": "headerMetric", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "WeeklyScheduleDocumentSpec.Builder", + "params": [ + { + "type": "String", + "name": "label" + }, + { + "type": "String...", + "name": "dayValues" + } + ] + }, + { + "kind": "method", + "name": "people", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "WeeklyScheduleDocumentSpec.Builder", + "params": [ + { + "type": "List", + "name": "people" + } + ] + }, + { + "kind": "method", + "name": "addPerson", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "WeeklyScheduleDocumentSpec.Builder", + "params": [ + { + "type": "SchedulePerson", + "name": "person" + } + ] + }, + { + "kind": "method", + "name": "person", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "WeeklyScheduleDocumentSpec.Builder", + "params": [ + { + "type": "Consumer", + "name": "spec" + } + ] + }, + { + "kind": "method", + "name": "person", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "WeeklyScheduleDocumentSpec.Builder", + "params": [ + { + "type": "String", + "name": "id" + }, + { + "type": "String", + "name": "displayName" + }, + { + "type": "int", + "name": "sortOrder" + } + ] + }, + { + "kind": "method", + "name": "assignments", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "WeeklyScheduleDocumentSpec.Builder", + "params": [ + { + "type": "List", + "name": "assignments" + } + ] + }, + { + "kind": "method", + "name": "addAssignment", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "WeeklyScheduleDocumentSpec.Builder", + "params": [ + { + "type": "ScheduleAssignment", + "name": "assignment" + } + ] + }, + { + "kind": "method", + "name": "assignment", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "WeeklyScheduleDocumentSpec.Builder", + "params": [ + { + "type": "Consumer", + "name": "spec" + } + ] + }, + { + "kind": "method", + "name": "assignment", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "WeeklyScheduleDocumentSpec.Builder", + "params": [ + { + "type": "String", + "name": "personId" + }, + { + "type": "String", + "name": "dayId" + }, + { + "type": "String", + "name": "categoryId" + }, + { + "type": "ScheduleSlot...", + "name": "slots" + } + ] + }, + { + "kind": "method", + "name": "assignment", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "WeeklyScheduleDocumentSpec.Builder", + "params": [ + { + "type": "String", + "name": "personId" + }, + { + "type": "String", + "name": "dayId" + }, + { + "type": "String", + "name": "categoryId" + }, + { + "type": "List", + "name": "slots" + }, + { + "type": "String", + "name": "note" + } + ] + }, + { + "kind": "method", + "name": "footerNotes", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "WeeklyScheduleDocumentSpec.Builder", + "params": [ + { + "type": "List", + "name": "footerNotes" + } + ] + }, + { + "kind": "method", + "name": "footerNote", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "WeeklyScheduleDocumentSpec.Builder", + "params": [ + { + "type": "String", + "name": "footerNote" + } + ] + }, + { + "kind": "method", + "name": "build", + "static": false, + "origin": "source", + "typeParameters": null, + "returns": "WeeklyScheduleDocumentSpec", + "params": [] + } + ] + } + ] + }, + { + "name": "com.demcha.compose.document.templates.invoice.presets", + "types": [ + { + "name": "ClassicInvoice", + "binaryName": "com.demcha.compose.document.templates.invoice.presets.ClassicInvoice", + "kind": "class", + "modifiers": [ + "final" + ], + "artifact": "graph-compose-templates", + "members": [ + { + "kind": "constant", + "name": "ID", + "static": true, + "origin": "generated", + "type": "String" + }, + { + "kind": "constant", + "name": "DISPLAY_NAME", + "static": true, + "origin": "generated", + "type": "String" + }, + { + "kind": "constant", + "name": "RECOMMENDED_MARGIN", + "static": true, + "origin": "generated", + "type": "double" + }, + { + "kind": "method", + "name": "create", + "static": true, + "origin": "source", + "typeParameters": null, + "returns": "DocumentTemplate", + "params": [] + }, + { + "kind": "method", + "name": "create", + "static": true, + "origin": "source", + "typeParameters": null, + "returns": "DocumentTemplate", + "params": [ + { + "type": "BrandTheme", + "name": "theme" + } + ] + } + ] + }, + { + "name": "ConsultingInvoice", + "binaryName": "com.demcha.compose.document.templates.invoice.presets.ConsultingInvoice", + "kind": "class", + "modifiers": [ + "final" + ], + "artifact": "graph-compose-templates", + "members": [ + { + "kind": "constant", + "name": "ID", + "static": true, + "origin": "generated", + "type": "String" + }, + { + "kind": "constant", + "name": "DISPLAY_NAME", + "static": true, + "origin": "generated", + "type": "String" + }, + { + "kind": "constant", + "name": "RECOMMENDED_MARGIN", + "static": true, + "origin": "generated", + "type": "double" + }, + { + "kind": "method", + "name": "create", + "static": true, + "origin": "source", + "typeParameters": null, + "returns": "DocumentTemplate", + "params": [] + } + ] + }, + { + "name": "LumaStudioInvoice", + "binaryName": "com.demcha.compose.document.templates.invoice.presets.LumaStudioInvoice", + "kind": "class", + "modifiers": [ + "final" + ], + "artifact": "graph-compose-templates", + "members": [ + { + "kind": "constant", + "name": "ID", + "static": true, + "origin": "generated", + "type": "String" + }, + { + "kind": "constant", + "name": "DISPLAY_NAME", + "static": true, + "origin": "generated", + "type": "String" + }, + { + "kind": "method", + "name": "create", + "static": true, + "origin": "source", + "typeParameters": null, + "returns": "DocumentTemplate", + "params": [] + } + ] + }, + { + "name": "MerchantInvoice", + "binaryName": "com.demcha.compose.document.templates.invoice.presets.MerchantInvoice", + "kind": "class", + "modifiers": [ + "final" + ], + "artifact": "graph-compose-templates", + "members": [ + { + "kind": "constant", + "name": "ID", + "static": true, + "origin": "generated", + "type": "String" + }, + { + "kind": "constant", + "name": "DISPLAY_NAME", + "static": true, + "origin": "generated", + "type": "String" + }, + { + "kind": "method", + "name": "create", + "static": true, + "origin": "source", + "typeParameters": null, + "returns": "DocumentTemplate", + "params": [] + } + ] + }, + { + "name": "MeteredInvoice", + "binaryName": "com.demcha.compose.document.templates.invoice.presets.MeteredInvoice", + "kind": "class", + "modifiers": [ + "final" + ], + "artifact": "graph-compose-templates", + "members": [ + { + "kind": "constant", + "name": "ID", + "static": true, + "origin": "generated", + "type": "String" + }, + { + "kind": "constant", + "name": "DISPLAY_NAME", + "static": true, + "origin": "generated", + "type": "String" + }, + { + "kind": "method", + "name": "create", + "static": true, + "origin": "source", + "typeParameters": null, + "returns": "DocumentTemplate", + "params": [] + } + ] + }, + { + "name": "ModernInvoice", + "binaryName": "com.demcha.compose.document.templates.invoice.presets.ModernInvoice", + "kind": "class", + "modifiers": [ + "final" + ], + "artifact": "graph-compose-templates", + "members": [ + { + "kind": "constant", + "name": "ID", + "static": true, + "origin": "generated", + "type": "String" + }, + { + "kind": "constant", + "name": "DISPLAY_NAME", + "static": true, + "origin": "generated", + "type": "String" + }, + { + "kind": "constant", + "name": "RECOMMENDED_MARGIN", + "static": true, + "origin": "generated", + "type": "double" + }, + { + "kind": "method", + "name": "create", + "static": true, + "origin": "source", + "typeParameters": null, + "returns": "DocumentTemplate", + "params": [] + }, + { + "kind": "method", + "name": "create", + "static": true, + "origin": "source", + "typeParameters": null, + "returns": "DocumentTemplate", + "params": [ + { + "type": "BrandTheme", + "name": "theme" + } + ] + } + ] + }, + { + "name": "ObsidianInvoice", + "binaryName": "com.demcha.compose.document.templates.invoice.presets.ObsidianInvoice", + "kind": "class", + "modifiers": [ + "final" + ], + "artifact": "graph-compose-templates", + "members": [ + { + "kind": "constant", + "name": "ID", + "static": true, + "origin": "generated", + "type": "String" + }, + { + "kind": "constant", + "name": "DISPLAY_NAME", + "static": true, + "origin": "generated", + "type": "String" + }, + { + "kind": "method", + "name": "create", + "static": true, + "origin": "source", + "typeParameters": null, + "returns": "DocumentTemplate", + "params": [] + } + ] + }, + { + "name": "PaymentsInvoice", + "binaryName": "com.demcha.compose.document.templates.invoice.presets.PaymentsInvoice", + "kind": "class", + "modifiers": [ + "final" + ], + "artifact": "graph-compose-templates", + "members": [ + { + "kind": "constant", + "name": "ID", + "static": true, + "origin": "generated", + "type": "String" + }, + { + "kind": "constant", + "name": "DISPLAY_NAME", + "static": true, + "origin": "generated", + "type": "String" + }, + { + "kind": "method", + "name": "create", + "static": true, + "origin": "source", + "typeParameters": null, + "returns": "DocumentTemplate", + "params": [] + } + ] + }, + { + "name": "PlatformInvoice", + "binaryName": "com.demcha.compose.document.templates.invoice.presets.PlatformInvoice", + "kind": "class", + "modifiers": [ + "final" + ], + "artifact": "graph-compose-templates", + "members": [ + { + "kind": "constant", + "name": "ID", + "static": true, + "origin": "generated", + "type": "String" + }, + { + "kind": "constant", + "name": "DISPLAY_NAME", + "static": true, + "origin": "generated", + "type": "String" + }, + { + "kind": "method", + "name": "create", + "static": true, + "origin": "source", + "typeParameters": null, + "returns": "DocumentTemplate", + "params": [] + } + ] + }, + { + "name": "SubscriptionInvoice", + "binaryName": "com.demcha.compose.document.templates.invoice.presets.SubscriptionInvoice", + "kind": "class", + "modifiers": [ + "final" + ], + "artifact": "graph-compose-templates", + "members": [ + { + "kind": "constant", + "name": "ID", + "static": true, + "origin": "generated", + "type": "String" + }, + { + "kind": "constant", + "name": "DISPLAY_NAME", + "static": true, + "origin": "generated", + "type": "String" + }, + { + "kind": "method", + "name": "create", + "static": true, + "origin": "source", + "typeParameters": null, + "returns": "DocumentTemplate", + "params": [] + } + ] + }, + { + "name": "WorkspaceInvoice", + "binaryName": "com.demcha.compose.document.templates.invoice.presets.WorkspaceInvoice", + "kind": "class", + "modifiers": [ + "final" + ], + "artifact": "graph-compose-templates", + "members": [ + { + "kind": "constant", + "name": "ID", + "static": true, + "origin": "generated", + "type": "String" + }, + { + "kind": "constant", + "name": "DISPLAY_NAME", + "static": true, + "origin": "generated", + "type": "String" + }, + { + "kind": "method", + "name": "create", + "static": true, + "origin": "source", + "typeParameters": null, + "returns": "DocumentTemplate", + "params": [] + } + ] + } + ] + }, + { + "name": "com.demcha.compose.document.templates.proposal.presets", + "types": [ + { + "name": "EditorialProposal", + "binaryName": "com.demcha.compose.document.templates.proposal.presets.EditorialProposal", + "kind": "class", + "modifiers": [ + "final" + ], + "artifact": "graph-compose-templates", + "members": [ + { + "kind": "constant", + "name": "ID", + "static": true, + "origin": "generated", + "type": "String" + }, + { + "kind": "constant", + "name": "DISPLAY_NAME", + "static": true, + "origin": "generated", + "type": "String" + }, + { + "kind": "constant", + "name": "RECOMMENDED_MARGIN", + "static": true, + "origin": "generated", + "type": "double" + }, + { + "kind": "method", + "name": "create", + "static": true, + "origin": "source", + "typeParameters": null, + "returns": "DocumentTemplate", + "params": [] + } + ] + }, + { + "name": "IndigoProposal", + "binaryName": "com.demcha.compose.document.templates.proposal.presets.IndigoProposal", + "kind": "class", + "modifiers": [ + "final" + ], + "artifact": "graph-compose-templates", + "members": [ + { + "kind": "constant", + "name": "ID", + "static": true, + "origin": "generated", + "type": "String" + }, + { + "kind": "constant", + "name": "DISPLAY_NAME", + "static": true, + "origin": "generated", + "type": "String" + }, + { + "kind": "constant", + "name": "RECOMMENDED_MARGIN", + "static": true, + "origin": "generated", + "type": "double" + }, + { + "kind": "method", + "name": "create", + "static": true, + "origin": "source", + "typeParameters": null, + "returns": "DocumentTemplate", + "params": [] + } + ] + }, + { + "name": "ModernProposal", + "binaryName": "com.demcha.compose.document.templates.proposal.presets.ModernProposal", + "kind": "class", + "modifiers": [ + "final" + ], + "artifact": "graph-compose-templates", + "members": [ + { + "kind": "constant", + "name": "ID", + "static": true, + "origin": "generated", + "type": "String" + }, + { + "kind": "constant", + "name": "DISPLAY_NAME", + "static": true, + "origin": "generated", + "type": "String" + }, + { + "kind": "constant", + "name": "RECOMMENDED_MARGIN", + "static": true, + "origin": "generated", + "type": "double" + }, + { + "kind": "method", + "name": "create", + "static": true, + "origin": "source", + "typeParameters": null, + "returns": "DocumentTemplate", + "params": [] + }, + { + "kind": "method", + "name": "create", + "static": true, + "origin": "source", + "typeParameters": null, + "returns": "DocumentTemplate", + "params": [ + { + "type": "BrandTheme", + "name": "theme" + } + ] + } + ] + }, + { + "name": "NorthlineProposal", + "binaryName": "com.demcha.compose.document.templates.proposal.presets.NorthlineProposal", + "kind": "class", + "modifiers": [ + "final" + ], + "artifact": "graph-compose-templates", + "members": [ + { + "kind": "constant", + "name": "ID", + "static": true, + "origin": "generated", + "type": "String" + }, + { + "kind": "constant", + "name": "DISPLAY_NAME", + "static": true, + "origin": "generated", + "type": "String" + }, + { + "kind": "constant", + "name": "RECOMMENDED_MARGIN", + "static": true, + "origin": "generated", + "type": "double" + }, + { + "kind": "method", + "name": "create", + "static": true, + "origin": "source", + "typeParameters": null, + "returns": "DocumentTemplate", + "params": [] + } + ] + } + ] + }, + { + "name": "com.demcha.compose.document.templates.receipt.components", + "types": [ + { + "name": "FieldRowRenderer", + "binaryName": "com.demcha.compose.document.templates.receipt.components.FieldRowRenderer", + "kind": "class", + "modifiers": [ + "final" + ], + "artifact": "graph-compose-templates", + "members": [ + { + "kind": "method", + "name": "renderAll", + "static": true, "origin": "source", "typeParameters": null, - "returns": "WeeklyScheduleData.Builder", + "returns": "void", "params": [ { - "type": "Consumer", - "name": "spec" + "type": "SectionBuilder", + "name": "host" + }, + { + "type": "List", + "name": "fields" + }, + { + "type": "BrandTheme", + "name": "theme" } ] }, { "kind": "method", - "name": "assignment", - "static": false, + "name": "renderInline", + "static": true, "origin": "source", "typeParameters": null, - "returns": "WeeklyScheduleData.Builder", + "returns": "void", "params": [ { - "type": "String", - "name": "personId" + "type": "SectionBuilder", + "name": "host" }, { - "type": "String", - "name": "dayId" + "type": "List", + "name": "fields" }, { - "type": "String", - "name": "categoryId" + "type": "TextAlign", + "name": "align" }, { - "type": "ScheduleSlot...", - "name": "slots" + "type": "BrandTheme", + "name": "theme" } ] }, { "kind": "method", - "name": "assignment", - "static": false, + "name": "render", + "static": true, "origin": "source", "typeParameters": null, - "returns": "WeeklyScheduleData.Builder", + "returns": "void", "params": [ { - "type": "String", - "name": "personId" - }, - { - "type": "String", - "name": "dayId" - }, - { - "type": "String", - "name": "categoryId" + "type": "SectionBuilder", + "name": "host" }, { - "type": "List", - "name": "slots" + "type": "ReceiptField", + "name": "field" }, { - "type": "String", - "name": "note" + "type": "BrandTheme", + "name": "theme" } ] - }, + } + ] + }, + { + "name": "ReceiptStyles", + "binaryName": "com.demcha.compose.document.templates.receipt.components.ReceiptStyles", + "kind": "class", + "modifiers": [ + "final" + ], + "artifact": "graph-compose-templates", + "members": [ { "kind": "method", - "name": "footerNotes", - "static": false, + "name": "eyebrow", + "static": true, "origin": "source", "typeParameters": null, - "returns": "WeeklyScheduleData.Builder", + "returns": "DocumentTextStyle", "params": [ { - "type": "List", - "name": "footerNotes" + "type": "BrandTheme", + "name": "theme" } ] }, { "kind": "method", - "name": "footerNote", - "static": false, + "name": "groupTitle", + "static": true, "origin": "source", "typeParameters": null, - "returns": "WeeklyScheduleData.Builder", + "returns": "DocumentTextStyle", "params": [ { - "type": "String", - "name": "footerNote" + "type": "BrandTheme", + "name": "theme" } ] }, { "kind": "method", - "name": "build", - "static": false, + "name": "title", + "static": true, "origin": "source", "typeParameters": null, - "returns": "WeeklyScheduleData", - "params": [] - } - ] - }, - { - "name": "WeeklyScheduleDocumentSpec", - "binaryName": "com.demcha.compose.document.templates.data.schedule.WeeklyScheduleDocumentSpec", - "kind": "record", - "modifiers": [ - "final" - ], - "artifact": "graph-compose-templates", - "members": [ + "returns": "DocumentTextStyle", + "params": [ + { + "type": "BrandTheme", + "name": "theme" + } + ] + }, { - "kind": "constructor", - "name": "WeeklyScheduleDocumentSpec", - "static": false, - "origin": "generated", + "kind": "method", + "name": "caption", + "static": true, + "origin": "source", "typeParameters": null, - "returns": null, + "returns": "DocumentTextStyle", "params": [ { - "type": "WeeklyScheduleData", - "name": null + "type": "BrandTheme", + "name": "theme" } ] }, { "kind": "method", - "name": "from", + "name": "label", "static": true, "origin": "source", "typeParameters": null, - "returns": "WeeklyScheduleDocumentSpec", + "returns": "DocumentTextStyle", "params": [ { - "type": "WeeklyScheduleData", - "name": "schedule" + "type": "BrandTheme", + "name": "theme" } ] }, { "kind": "method", - "name": "builder", + "name": "value", "static": true, "origin": "source", "typeParameters": null, - "returns": "WeeklyScheduleDocumentSpec.Builder", - "params": [] + "returns": "DocumentTextStyle", + "params": [ + { + "type": "BrandTheme", + "name": "theme" + } + ] }, { "kind": "method", - "name": "schedule", - "static": false, - "origin": "generated", + "name": "valueStrong", + "static": true, + "origin": "source", "typeParameters": null, - "returns": "WeeklyScheduleData", - "params": [] - } - ] - }, - { - "name": "WeeklyScheduleDocumentSpec.Builder", - "binaryName": "com.demcha.compose.document.templates.data.schedule.WeeklyScheduleDocumentSpec$Builder", - "kind": "class", - "modifiers": [ - "final" - ], - "artifact": "graph-compose-templates", - "members": [ + "returns": "DocumentTextStyle", + "params": [ + { + "type": "BrandTheme", + "name": "theme" + } + ] + }, { "kind": "method", - "name": "title", - "static": false, + "name": "amount", + "static": true, "origin": "source", "typeParameters": null, - "returns": "WeeklyScheduleDocumentSpec.Builder", + "returns": "DocumentTextStyle", "params": [ { - "type": "String", - "name": "title" + "type": "BrandTheme", + "name": "theme" } ] }, { "kind": "method", - "name": "weekLabel", - "static": false, + "name": "partyName", + "static": true, "origin": "source", "typeParameters": null, - "returns": "WeeklyScheduleDocumentSpec.Builder", + "returns": "DocumentTextStyle", "params": [ { - "type": "String", - "name": "weekLabel" + "type": "BrandTheme", + "name": "theme" } ] }, { "kind": "method", - "name": "days", - "static": false, + "name": "body", + "static": true, "origin": "source", "typeParameters": null, - "returns": "WeeklyScheduleDocumentSpec.Builder", + "returns": "DocumentTextStyle", "params": [ { - "type": "List", - "name": "days" + "type": "BrandTheme", + "name": "theme" } ] }, { "kind": "method", - "name": "addDay", - "static": false, + "name": "smallPrint", + "static": true, "origin": "source", "typeParameters": null, - "returns": "WeeklyScheduleDocumentSpec.Builder", + "returns": "DocumentTextStyle", "params": [ { - "type": "ScheduleDay", - "name": "day" + "type": "BrandTheme", + "name": "theme" } ] }, { "kind": "method", - "name": "day", - "static": false, + "name": "pill", + "static": true, "origin": "source", "typeParameters": null, - "returns": "WeeklyScheduleDocumentSpec.Builder", + "returns": "DocumentTextStyle", "params": [ { - "type": "Consumer", - "name": "spec" + "type": "BrandTheme", + "name": "theme" + }, + { + "type": "DocumentColor", + "name": "color" } ] - }, + } + ] + }, + { + "name": "StatusPill", + "binaryName": "com.demcha.compose.document.templates.receipt.components.StatusPill", + "kind": "class", + "modifiers": [ + "final" + ], + "artifact": "graph-compose-templates", + "members": [ { "kind": "method", - "name": "day", - "static": false, + "name": "render", + "static": true, "origin": "source", "typeParameters": null, - "returns": "WeeklyScheduleDocumentSpec.Builder", + "returns": "void", "params": [ { - "type": "String", - "name": "id" + "type": "SectionBuilder", + "name": "host" }, { - "type": "String", - "name": "label" + "type": "ReceiptStatus", + "name": "status" + }, + { + "type": "DocumentColor", + "name": "accent" }, { - "type": "String", - "name": "headerNote" + "type": "TextAlign", + "name": "align" }, { - "type": "String", - "name": "headerCategoryId" + "type": "BrandTheme", + "name": "theme" } ] + } + ] + } + ] + }, + { + "name": "com.demcha.compose.document.templates.receipt.presets", + "types": [ + { + "name": "ModernReceipt", + "binaryName": "com.demcha.compose.document.templates.receipt.presets.ModernReceipt", + "kind": "class", + "modifiers": [ + "final" + ], + "artifact": "graph-compose-templates", + "members": [ + { + "kind": "constant", + "name": "ID", + "static": true, + "origin": "generated", + "type": "String" }, { - "kind": "method", - "name": "categories", - "static": false, - "origin": "source", - "typeParameters": null, - "returns": "WeeklyScheduleDocumentSpec.Builder", - "params": [ - { - "type": "List", - "name": "categories" - } - ] + "kind": "constant", + "name": "DISPLAY_NAME", + "static": true, + "origin": "generated", + "type": "String" + }, + { + "kind": "constant", + "name": "RECOMMENDED_MARGIN", + "static": true, + "origin": "generated", + "type": "double" }, { "kind": "method", - "name": "addCategory", - "static": false, + "name": "create", + "static": true, "origin": "source", "typeParameters": null, - "returns": "WeeklyScheduleDocumentSpec.Builder", - "params": [ - { - "type": "ScheduleCategory", - "name": "category" - } - ] + "returns": "DocumentTemplate", + "params": [] }, { "kind": "method", - "name": "category", - "static": false, + "name": "create", + "static": true, "origin": "source", "typeParameters": null, - "returns": "WeeklyScheduleDocumentSpec.Builder", + "returns": "DocumentTemplate", "params": [ { - "type": "Consumer", - "name": "spec" + "type": "BrandTheme", + "name": "theme" } ] }, { "kind": "method", - "name": "category", - "static": false, + "name": "create", + "static": true, "origin": "source", "typeParameters": null, - "returns": "WeeklyScheduleDocumentSpec.Builder", + "returns": "DocumentTemplate", "params": [ { - "type": "String", - "name": "id" - }, - { - "type": "String", - "name": "label" - }, - { - "type": "Color", - "name": "fillColor" + "type": "BrandTheme", + "name": "theme" }, { - "type": "Color", - "name": "borderColor" + "type": "ModernReceipt.Options", + "name": "options" } ] + } + ] + }, + { + "name": "ModernReceipt.Options", + "binaryName": "com.demcha.compose.document.templates.receipt.presets.ModernReceipt$Options", + "kind": "record", + "modifiers": [ + "final" + ], + "artifact": "graph-compose-templates", + "members": [ + { + "kind": "constant", + "name": "DEFAULT_LOGO_WIDTH", + "static": true, + "origin": "generated", + "type": "double" }, { - "kind": "method", - "name": "category", + "kind": "constant", + "name": "DEFAULT_TIMELINE_TITLE", + "static": true, + "origin": "generated", + "type": "String" + }, + { + "kind": "constructor", + "name": "Options", "static": false, - "origin": "source", + "origin": "generated", "typeParameters": null, - "returns": "WeeklyScheduleDocumentSpec.Builder", + "returns": null, "params": [ { - "type": "String", - "name": "id" + "type": "SvgGlyph", + "name": null }, { - "type": "String", - "name": "label" + "type": "double", + "name": null }, { - "type": "Color", - "name": "fillColor" + "type": "DocumentColor", + "name": null }, { - "type": "Color", - "name": "textColor" + "type": "DocumentColor", + "name": null }, { - "type": "Color", - "name": "borderColor" + "type": "String", + "name": null } ] }, { "kind": "method", - "name": "headerMetrics", - "static": false, + "name": "defaults", + "static": true, "origin": "source", "typeParameters": null, - "returns": "WeeklyScheduleDocumentSpec.Builder", - "params": [ - { - "type": "List", - "name": "headerMetrics" - } - ] + "returns": "ModernReceipt.Options", + "params": [] }, { "kind": "method", - "name": "addHeaderMetric", - "static": false, + "name": "branded", + "static": true, "origin": "source", "typeParameters": null, - "returns": "WeeklyScheduleDocumentSpec.Builder", + "returns": "ModernReceipt.Options", "params": [ { - "type": "ScheduleMetricRow", - "name": "headerMetric" + "type": "SvgGlyph", + "name": "logo" + }, + { + "type": "DocumentColor", + "name": "accent" } ] }, { "kind": "method", - "name": "headerMetric", + "name": "withLogoWidth", "static": false, "origin": "source", "typeParameters": null, - "returns": "WeeklyScheduleDocumentSpec.Builder", + "returns": "ModernReceipt.Options", "params": [ { - "type": "Consumer", - "name": "spec" + "type": "double", + "name": "width" } ] }, { "kind": "method", - "name": "headerMetric", + "name": "withLogoColor", "static": false, "origin": "source", "typeParameters": null, - "returns": "WeeklyScheduleDocumentSpec.Builder", + "returns": "ModernReceipt.Options", "params": [ { - "type": "String", - "name": "label" - }, - { - "type": "List", - "name": "dayValues" + "type": "DocumentColor", + "name": "color" } ] }, { "kind": "method", - "name": "headerMetric", + "name": "withTimelineTitle", "static": false, "origin": "source", "typeParameters": null, - "returns": "WeeklyScheduleDocumentSpec.Builder", + "returns": "ModernReceipt.Options", "params": [ { "type": "String", - "name": "label" - }, - { - "type": "String...", - "name": "dayValues" + "name": "title" } ] }, { "kind": "method", - "name": "people", + "name": "logo", "static": false, - "origin": "source", + "origin": "generated", "typeParameters": null, - "returns": "WeeklyScheduleDocumentSpec.Builder", - "params": [ - { - "type": "List", - "name": "people" - } - ] + "returns": "SvgGlyph", + "params": [] }, { "kind": "method", - "name": "addPerson", + "name": "logoWidth", "static": false, - "origin": "source", + "origin": "generated", "typeParameters": null, - "returns": "WeeklyScheduleDocumentSpec.Builder", - "params": [ - { - "type": "SchedulePerson", - "name": "person" - } - ] + "returns": "double", + "params": [] }, { "kind": "method", - "name": "person", + "name": "logoColor", "static": false, - "origin": "source", + "origin": "generated", "typeParameters": null, - "returns": "WeeklyScheduleDocumentSpec.Builder", - "params": [ - { - "type": "Consumer", - "name": "spec" - } - ] + "returns": "DocumentColor", + "params": [] }, { "kind": "method", - "name": "person", + "name": "accent", + "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "DocumentColor", + "params": [] + }, + { + "kind": "method", + "name": "timelineTitle", "static": false, + "origin": "generated", + "typeParameters": null, + "returns": "String", + "params": [] + } + ] + } + ] + }, + { + "name": "com.demcha.compose.document.templates.receipt.widgets", + "types": [ + { + "name": "AmountHero", + "binaryName": "com.demcha.compose.document.templates.receipt.widgets.AmountHero", + "kind": "class", + "modifiers": [ + "final" + ], + "artifact": "graph-compose-templates", + "members": [ + { + "kind": "method", + "name": "render", + "static": true, "origin": "source", "typeParameters": null, - "returns": "WeeklyScheduleDocumentSpec.Builder", + "returns": "void", "params": [ { - "type": "String", - "name": "id" + "type": "SectionBuilder", + "name": "host" }, { - "type": "String", - "name": "displayName" + "type": "ReceiptData", + "name": "data" }, { - "type": "int", - "name": "sortOrder" + "type": "DocumentColor", + "name": "accent" + }, + { + "type": "BrandTheme", + "name": "theme" } ] - }, + } + ] + }, + { + "name": "DetailGroup", + "binaryName": "com.demcha.compose.document.templates.receipt.widgets.DetailGroup", + "kind": "class", + "modifiers": [ + "final" + ], + "artifact": "graph-compose-templates", + "members": [ { "kind": "method", - "name": "assignments", - "static": false, + "name": "render", + "static": true, "origin": "source", "typeParameters": null, - "returns": "WeeklyScheduleDocumentSpec.Builder", + "returns": "void", "params": [ { - "type": "List", - "name": "assignments" + "type": "SectionBuilder", + "name": "host" + }, + { + "type": "ReceiptFieldGroup", + "name": "group" + }, + { + "type": "BrandTheme", + "name": "theme" } ] - }, + } + ] + }, + { + "name": "PartyPair", + "binaryName": "com.demcha.compose.document.templates.receipt.widgets.PartyPair", + "kind": "class", + "modifiers": [ + "final" + ], + "artifact": "graph-compose-templates", + "members": [ { "kind": "method", - "name": "addAssignment", - "static": false, + "name": "render", + "static": true, "origin": "source", "typeParameters": null, - "returns": "WeeklyScheduleDocumentSpec.Builder", + "returns": "void", "params": [ { - "type": "ScheduleAssignment", - "name": "assignment" + "type": "SectionBuilder", + "name": "host" + }, + { + "type": "ReceiptData", + "name": "data" + }, + { + "type": "DocumentColor", + "name": "accent" + }, + { + "type": "BrandTheme", + "name": "theme" } ] - }, + } + ] + }, + { + "name": "ReceiptFooter", + "binaryName": "com.demcha.compose.document.templates.receipt.widgets.ReceiptFooter", + "kind": "class", + "modifiers": [ + "final" + ], + "artifact": "graph-compose-templates", + "members": [ { "kind": "method", - "name": "assignment", - "static": false, + "name": "hasContent", + "static": true, "origin": "source", "typeParameters": null, - "returns": "WeeklyScheduleDocumentSpec.Builder", + "returns": "boolean", "params": [ { - "type": "Consumer", - "name": "spec" + "type": "ReceiptData", + "name": "data" } ] }, { "kind": "method", - "name": "assignment", - "static": false, + "name": "render", + "static": true, "origin": "source", "typeParameters": null, - "returns": "WeeklyScheduleDocumentSpec.Builder", + "returns": "void", "params": [ { - "type": "String", - "name": "personId" - }, - { - "type": "String", - "name": "dayId" + "type": "SectionBuilder", + "name": "host" }, { - "type": "String", - "name": "categoryId" + "type": "ReceiptData", + "name": "data" }, { - "type": "ScheduleSlot...", - "name": "slots" + "type": "BrandTheme", + "name": "theme" } ] - }, + } + ] + }, + { + "name": "ReceiptMasthead", + "binaryName": "com.demcha.compose.document.templates.receipt.widgets.ReceiptMasthead", + "kind": "class", + "modifiers": [ + "final" + ], + "artifact": "graph-compose-templates", + "members": [ { "kind": "method", - "name": "assignment", - "static": false, + "name": "render", + "static": true, "origin": "source", "typeParameters": null, - "returns": "WeeklyScheduleDocumentSpec.Builder", + "returns": "void", "params": [ { - "type": "String", - "name": "personId" + "type": "SectionBuilder", + "name": "host" }, { - "type": "String", - "name": "dayId" + "type": "ReceiptData", + "name": "data" }, { - "type": "String", - "name": "categoryId" + "type": "SvgGlyph", + "name": "logo" }, { - "type": "List", - "name": "slots" + "type": "double", + "name": "logoWidth" }, { - "type": "String", - "name": "note" - } - ] - }, - { - "kind": "method", - "name": "footerNotes", - "static": false, - "origin": "source", - "typeParameters": null, - "returns": "WeeklyScheduleDocumentSpec.Builder", - "params": [ - { - "type": "List", - "name": "footerNotes" - } - ] - }, - { - "kind": "method", - "name": "footerNote", - "static": false, - "origin": "source", - "typeParameters": null, - "returns": "WeeklyScheduleDocumentSpec.Builder", - "params": [ + "type": "DocumentColor", + "name": "logoColor" + }, { - "type": "String", - "name": "footerNote" + "type": "BrandTheme", + "name": "theme" } ] - }, - { - "kind": "method", - "name": "build", - "static": false, - "origin": "source", - "typeParameters": null, - "returns": "WeeklyScheduleDocumentSpec", - "params": [] } ] - } - ] - }, - { - "name": "com.demcha.compose.document.templates.invoice.presets", - "types": [ + }, { - "name": "ModernInvoice", - "binaryName": "com.demcha.compose.document.templates.invoice.presets.ModernInvoice", + "name": "StatusTrail", + "binaryName": "com.demcha.compose.document.templates.receipt.widgets.StatusTrail", "kind": "class", "modifiers": [ "final" ], "artifact": "graph-compose-templates", "members": [ - { - "kind": "constant", - "name": "ID", - "static": true, - "origin": "generated", - "type": "String" - }, - { - "kind": "constant", - "name": "DISPLAY_NAME", - "static": true, - "origin": "generated", - "type": "String" - }, - { - "kind": "constant", - "name": "RECOMMENDED_MARGIN", - "static": true, - "origin": "generated", - "type": "double" - }, - { - "kind": "method", - "name": "create", - "static": true, - "origin": "source", - "typeParameters": null, - "returns": "DocumentTemplate", - "params": [] - }, { "kind": "method", - "name": "create", + "name": "render", "static": true, "origin": "source", "typeParameters": null, - "returns": "DocumentTemplate", + "returns": "void", "params": [ + { + "type": "SectionBuilder", + "name": "host" + }, + { + "type": "String", + "name": "title" + }, + { + "type": "List", + "name": "events" + }, + { + "type": "DocumentColor", + "name": "accent" + }, { "type": "BrandTheme", "name": "theme" @@ -16422,11 +25358,11 @@ ] }, { - "name": "com.demcha.compose.document.templates.proposal.presets", + "name": "com.demcha.compose.document.templates.rota.presets", "types": [ { - "name": "ModernProposal", - "binaryName": "com.demcha.compose.document.templates.proposal.presets.ModernProposal", + "name": "CobaltRota", + "binaryName": "com.demcha.compose.document.templates.rota.presets.CobaltRota", "kind": "class", "modifiers": [ "final" @@ -16460,22 +25396,8 @@ "static": true, "origin": "source", "typeParameters": null, - "returns": "DocumentTemplate", + "returns": "DocumentTemplate", "params": [] - }, - { - "kind": "method", - "name": "create", - "static": true, - "origin": "source", - "typeParameters": null, - "returns": "DocumentTemplate", - "params": [ - { - "type": "BrandTheme", - "name": "theme" - } - ] } ] } diff --git a/knowledge/api/templates.md b/knowledge/api/templates.md index 52bbde25b..bc2bd0e79 100644 --- a/knowledge/api/templates.md +++ b/knowledge/api/templates.md @@ -28,7 +28,7 @@ note: "Generated from the pinned artifact's class files. Authoritative closed se **GraphCompose version:** 2.4.0-SNAPSHOT -Types: 161 · methods: 954 · constants: 118 · compiler-generated members: 403 +Types: 266 · methods: 1475 · constants: 201 · compiler-generated members: 807 ## com.demcha.compose.document.templates.api @@ -59,6 +59,14 @@ Types: 161 · methods: 954 · constants: 118 · compiler-generated members: 403 ### ContactLine.Order (enum) - constants: `PHONE_FIRST`, `ADDRESS_FIRST` +### ContactUri (class) +- `String tel(String phone)` +- `DocumentLinkOptions telLink(String phone)` +- `DocumentLinkOptions mailLink(String email)` +- `String web(String website)` +- `DocumentLinkOptions webLink(String website)` +- `DocumentLinkOptions channelLink(String contact)` + ### Headline (class) - `void spacedCentered(SectionBuilder host, String name, BrandTheme theme)` - `void uppercaseCentered(SectionBuilder host, String name, BrandTheme theme)` @@ -112,6 +120,7 @@ Types: 161 · methods: 954 · constants: 118 · compiler-generated members: 403 ### SvgGlyph (class) - `SvgGlyph fromResource(String resourcePath)` +- `SvgGlyph fromFile(Path file)` - `ShapeOutline outline(double width)` - `double aspectRatio()` @@ -164,6 +173,7 @@ Types: 161 · methods: 954 · constants: 118 · compiler-generated members: 403 - `BrandTheme mintEditorial()` - `BrandTheme invoiceModern()` - `BrandTheme proposalModern()` +- `BrandTheme receiptModern()` - `DocumentTextStyle headlineStyle()` - `DocumentTextStyle bannerStyle()` - `DocumentTextStyle contactStyle()` @@ -205,6 +215,7 @@ Types: 161 · methods: 954 · constants: 118 · compiler-generated members: 403 - `Palette executive()` - `Palette mintEditorial()` - `Palette invoiceModern()` +- `Palette receiptModern()` - `DocumentColor ink()` - `DocumentColor muted()` - `DocumentColor rule()` @@ -230,6 +241,7 @@ Types: 161 · methods: 954 · constants: 118 · compiler-generated members: 403 - `Spacing executive()` - `Spacing mintEditorial()` - `Spacing invoiceModern()` +- `Spacing receiptModern()` - `double pageFlowSpacing()` - `double sectionBodySpacing()` - `DocumentInsets sectionBodyPadding()` @@ -264,6 +276,7 @@ Types: 161 · methods: 954 · constants: 118 · compiler-generated members: 403 - `Typography mintEditorial()` - `Typography invoiceModern()` - `Typography proposalModern()` +- `Typography receiptModern()` - `FontName headlineFont()` - `FontName bodyFont()` - `double sizeHeadline()` @@ -587,14 +600,31 @@ Types: 161 · methods: 954 · constants: 118 · compiler-generated members: 403 - `CvSection section()` ### CvEntry (record) -- `new CvEntry(String, String, String, String)` +- `new CvEntry(String, String, String, String, String, String, String)` +- `new CvEntry(String title, String subtitle, String date, String body, String place, String icon)` +- `new CvEntry(String title, String subtitle, String date, String body)` +- `CvEntry.Builder builder(String title)` - `String title()` - `String subtitle()` - `String date()` - `String body()` +- `String place()` +- `String icon()` +- `String link()` + +### CvEntry.Builder (class) +- `CvEntry.Builder subtitle(String value)` +- `CvEntry.Builder date(String value)` +- `CvEntry.Builder body(String value)` +- `CvEntry.Builder body(List lines)` +- `CvEntry.Builder place(String value)` +- `CvEntry.Builder icon(String value)` +- `CvEntry.Builder link(String value)` +- `CvEntry build()` ### CvIdentity (record) -- `new CvIdentity(CvName, String, Contact, List)` +- `new CvIdentity(CvName, String, Contact, List, Optional)` +- `new CvIdentity(CvName name, String jobTitle, Contact contact, List links)` - `new CvIdentity(CvName name, Contact contact, List links)` - `String displayName()` - `String tagline()` @@ -603,6 +633,7 @@ Types: 161 · methods: 954 · constants: 118 · compiler-generated members: 403 - `String jobTitle()` - `Contact contact()` - `List links()` +- `Optional portrait()` ### CvIdentity.Builder (class) - `CvIdentity.Builder name(CvName value)` @@ -613,6 +644,7 @@ Types: 161 · methods: 954 · constants: 118 · compiler-generated members: 403 - `CvIdentity.Builder contact(String phone, String email, String address)` - `CvIdentity.Builder link(Link link)` - `CvIdentity.Builder link(String label, String url)` +- `CvIdentity.Builder portrait(DocumentImageData value)` - `CvIdentity build()` ### CvName (record) @@ -633,11 +665,14 @@ Types: 161 · methods: 954 · constants: 118 · compiler-generated members: 403 - `String title()` ### CvSkill (record) -- `new CvSkill(String, OptionalDouble)` +- `new CvSkill(String, OptionalDouble, String)` +- `new CvSkill(String name, OptionalDouble level)` - `CvSkill of(String name)` - `CvSkill of(String name, double level)` +- `CvSkill of(String name, double level, String note)` - `String name()` - `OptionalDouble level()` +- `String note()` ### EntriesSection (record) - `new EntriesSection(String, List)` @@ -713,6 +748,10 @@ Types: 161 · methods: 954 · constants: 118 · compiler-generated members: 403 - `DocumentTemplate create(BrandTheme theme)` - constants: `ID`, `DISPLAY_NAME`, `RECOMMENDED_MARGIN` +### CharcoalGold (class) +- `DocumentTemplate create()` +- constants: `ID`, `DISPLAY_NAME`, `RECOMMENDED_MARGIN` + ### ClassicSerif (class) - `DocumentTemplate create()` - `DocumentTemplate create(BrandTheme theme)` @@ -738,6 +777,10 @@ Types: 161 · methods: 954 · constants: 118 · compiler-generated members: 403 - `DocumentTemplate create(BrandTheme theme)` - constants: `ID`, `DISPLAY_NAME`, `RECOMMENDED_MARGIN` +### MidnightNavy (class) +- `DocumentTemplate create()` +- constants: `ID`, `DISPLAY_NAME` + ### MinimalUnderlined (class) - `DocumentTemplate create()` - `DocumentTemplate create(BrandTheme theme)` @@ -793,6 +836,10 @@ Types: 161 · methods: 954 · constants: 118 · compiler-generated members: 403 - `MonogramSidebar.Options.Builder monogramRingColor(DocumentColor value)` - `MonogramSidebar.Options build()` +### NavySidebar (class) +- `DocumentTemplate create()` +- constants: `ID`, `DISPLAY_NAME`, `RECOMMENDED_MARGIN` + ### NordicClean (class) - `DocumentTemplate create()` - `DocumentTemplate create(BrandTheme theme)` @@ -818,11 +865,23 @@ Types: 161 · methods: 954 · constants: 118 · compiler-generated members: 403 ### NordicClean.RailSide (enum) - constants: `LEFT`, `RIGHT` +### OrangeOps (class) +- `DocumentTemplate create()` +- constants: `ID`, `DISPLAY_NAME`, `DISPLAY_FONT` + ### Panel (class) - `DocumentTemplate create()` - `DocumentTemplate create(BrandTheme theme)` - constants: `ID`, `DISPLAY_NAME`, `RECOMMENDED_MARGIN` +### ProfessionalSidebar (class) +- `DocumentTemplate create()` +- constants: `ID`, `DISPLAY_NAME`, `RECOMMENDED_MARGIN` + +### SerifHeadline (class) +- `DocumentTemplate create()` +- constants: `ID`, `DISPLAY_NAME`, `RECOMMENDED_MARGIN` + ### SidebarPortrait (class) - `DocumentTemplate create()` - `DocumentTemplate create(BrandTheme theme)` @@ -843,11 +902,27 @@ Types: 161 · methods: 954 · constants: 118 · compiler-generated members: 403 - `SidebarPortrait.Options.Builder accentColor(DocumentColor value)` - `SidebarPortrait.Options build()` +### SlateOrange (class) +- `DocumentTemplate create()` +- constants: `ID`, `DISPLAY_NAME` + +### TealPulse (class) +- `DocumentTemplate create()` +- constants: `ID`, `DISPLAY_NAME` + +### TerracottaRail (class) +- `DocumentTemplate create()` +- constants: `ID`, `DISPLAY_NAME`, `RECOMMENDED_MARGIN` + ### TimelineMinimal (class) - `DocumentTemplate create()` - `DocumentTemplate create(BrandTheme theme)` - constants: `ID`, `DISPLAY_NAME`, `RECOMMENDED_MARGIN` +### VioletGrid (class) +- `DocumentTemplate create()` +- constants: `ID`, `DISPLAY_NAME` + ## com.demcha.compose.document.templates.cv.widgets ### FlowSectionHeader (class) @@ -922,6 +997,33 @@ Types: 161 · methods: 954 · constants: 118 · compiler-generated members: 403 ## com.demcha.compose.document.templates.data.invoice +### InvoiceBrand (record) +- `new InvoiceBrand(DocumentImageData, String, String, String, String, String)` +- `new InvoiceBrand(DocumentImageData logo, String name, String qualifier, String tagline)` +- `boolean hasMonogram()` +- `boolean hasLogo()` +- `DocumentImageData logo()` +- `String name()` +- `String qualifier()` +- `String tagline()` +- `String monogramTop()` +- `String monogramBottom()` + +### InvoiceContactBlock (record) +- `new InvoiceContactBlock(String, List, String, String, String, String, String, String, String, String)` +- `new InvoiceContactBlock(String legalName, List addressLines, String phone, String email, String website, String registrationLabel, String registrationNumber, String taxRegistrationLabel, String taxRegistrationNumber)` +- `new InvoiceContactBlock(String legalName, List addressLines, String phone, String email, String website, String registrationLabel, String registrationNumber)` +- `String legalName()` +- `List addressLines()` +- `String phone()` +- `String email()` +- `String website()` +- `String registrationLabel()` +- `String registrationNumber()` +- `String taxRegistrationLabel()` +- `String taxRegistrationNumber()` +- `String legalFootnote()` + ### InvoiceData (record) - `new InvoiceData(String, String, String, String, String, String, InvoiceParty, InvoiceParty, List, List, List, List, String)` - `InvoiceData.Builder builder()` @@ -1016,6 +1118,24 @@ Types: 161 · methods: 954 · constants: 118 · compiler-generated members: 403 - `InvoiceLineItem.Builder amount(String amount)` - `InvoiceLineItem build()` +### InvoiceMasthead (record) +- `new InvoiceMasthead(String, List)` +- `String title()` +- `List entries()` + +### InvoiceMasthead.Entry (record) +- `new Entry(String, String, boolean)` +- `String label()` +- `String value()` +- `boolean emphasized()` + +### InvoiceNotesBlock (record) +- `new InvoiceNotesBlock(String, List, String, String)` +- `String heading()` +- `List paragraphs()` +- `String contactEmail()` +- `String contactPhone()` + ### InvoiceParty (record) - `new InvoiceParty(String, List, String, String, String)` - `InvoiceParty.Builder builder()` @@ -1035,6 +1155,76 @@ Types: 161 · methods: 954 · constants: 118 · compiler-generated members: 403 - `InvoiceParty.Builder taxId(String taxId)` - `InvoiceParty build()` +### InvoicePaymentBlock (record) +- `new InvoicePaymentBlock(String, List, String, String, String, String, String)` +- `new InvoicePaymentBlock(String heading, List fields, String instruction, String dueNotice, String dueNoticeEmphasis)` +- `String heading()` +- `List fields()` +- `String instruction()` +- `String dueNotice()` +- `String dueNoticeEmphasis()` +- `String accountHolder()` +- `String signOff()` + +### InvoicePaymentBlock.Field (record) +- `new Field(String, String)` +- `String label()` +- `String value()` + +### InvoiceRecipient (record) +- `new InvoiceRecipient(String, String, String, List, String, String, String, String)` +- `new InvoiceRecipient(String heading, String name, String subline, List addressLines, String emailLabel, String email)` +- `boolean hasRegistration()` +- `String heading()` +- `String name()` +- `String subline()` +- `List addressLines()` +- `String emailLabel()` +- `String email()` +- `String registrationLabel()` +- `String registrationNumber()` + +### InvoiceServiceLines (record) +- `new InvoiceServiceLines(InvoiceServiceLines.Columns, List)` +- `InvoiceServiceLines.Columns columns()` +- `List lines()` + +### InvoiceServiceLines.Columns (record) +- `new Columns(String, String, String, String, String, String, String, String)` +- `new Columns(String index, String description, String servicePeriod, String quantity, String unitPrice, String amount, String vat)` +- `new Columns(String index, String description, String servicePeriod, String quantity, String unitPrice, String amount)` +- `String index()` +- `String description()` +- `String servicePeriod()` +- `String quantity()` +- `String unitPrice()` +- `String amount()` +- `String vat()` +- `String region()` + +### InvoiceServiceLines.Line (record) +- `new Line(int, String, String, String, BigDecimal, String, BigDecimal, BigDecimal, String, String, String)` +- `new Line(int lineNumber, String title, String description, String servicePeriod, BigDecimal quantity, String unit, BigDecimal unitPrice, BigDecimal amount, String vatRate, String icon)` +- `new Line(int lineNumber, String title, String description, String servicePeriod, BigDecimal quantity, String unit, BigDecimal unitPrice, BigDecimal amount, String vatRate)` +- `new Line(int lineNumber, String title, String description, String servicePeriod, BigDecimal quantity, String unit, BigDecimal unitPrice, BigDecimal amount)` +- `int lineNumber()` +- `String title()` +- `String description()` +- `String servicePeriod()` +- `BigDecimal quantity()` +- `String unit()` +- `BigDecimal unitPrice()` +- `BigDecimal amount()` +- `String vatRate()` +- `String icon()` +- `String region()` + +### InvoiceSummaryBlock (record) +- `new InvoiceSummaryBlock(String, String, String)` +- `String heading()` +- `String intro()` +- `String servicePeriod()` + ### InvoiceSummaryRow (record) - `new InvoiceSummaryRow(String, String, boolean)` - `InvoiceSummaryRow.Builder builder()` @@ -1048,8 +1238,79 @@ Types: 161 · methods: 954 · constants: 118 · compiler-generated members: 403 - `InvoiceSummaryRow.Builder emphasized(boolean emphasized)` - `InvoiceSummaryRow build()` +### InvoiceTotalsBlock (record) +- `new InvoiceTotalsBlock(List, String, BigDecimal)` +- `List rows()` +- `String totalLabel()` +- `BigDecimal totalAmount()` + +### InvoiceTotalsBlock.Row (record) +- `new Row(String, BigDecimal)` +- `String label()` +- `BigDecimal amount()` + +### StructuredInvoiceData (record) +- `new StructuredInvoiceData(InvoiceBrand, InvoiceContactBlock, InvoiceMasthead, InvoiceRecipient, InvoiceRecipient, InvoiceSummaryBlock, InvoiceServiceLines, InvoiceTotalsBlock, InvoicePaymentBlock, InvoiceNotesBlock, String)` +- `new StructuredInvoiceData(InvoiceBrand brand, InvoiceContactBlock supplier, InvoiceMasthead masthead, InvoiceRecipient billTo, InvoiceSummaryBlock summary, InvoiceServiceLines serviceLines, InvoiceTotalsBlock totals, InvoicePaymentBlock payment, InvoiceNotesBlock notes, String currencyCode)` +- `StructuredInvoiceData.Builder builder()` +- `InvoiceBrand brand()` +- `InvoiceContactBlock supplier()` +- `InvoiceMasthead masthead()` +- `InvoiceRecipient billTo()` +- `InvoiceRecipient shipTo()` +- `InvoiceSummaryBlock summary()` +- `InvoiceServiceLines serviceLines()` +- `InvoiceTotalsBlock totals()` +- `InvoicePaymentBlock payment()` +- `InvoiceNotesBlock notes()` +- `String currencyCode()` + +### StructuredInvoiceData.Builder (class) +- `StructuredInvoiceData.Builder brand(InvoiceBrand brand)` +- `StructuredInvoiceData.Builder supplier(InvoiceContactBlock supplier)` +- `StructuredInvoiceData.Builder masthead(InvoiceMasthead masthead)` +- `StructuredInvoiceData.Builder billTo(InvoiceRecipient billTo)` +- `StructuredInvoiceData.Builder shipTo(InvoiceRecipient shipTo)` +- `StructuredInvoiceData.Builder summary(InvoiceSummaryBlock summary)` +- `StructuredInvoiceData.Builder serviceLines(InvoiceServiceLines serviceLines)` +- `StructuredInvoiceData.Builder totals(InvoiceTotalsBlock totals)` +- `StructuredInvoiceData.Builder payment(InvoicePaymentBlock payment)` +- `StructuredInvoiceData.Builder notes(InvoiceNotesBlock notes)` +- `StructuredInvoiceData.Builder currencyCode(String currencyCode)` +- `StructuredInvoiceData build()` + +### StructuredInvoiceDocumentSpec (record) +- `new StructuredInvoiceDocumentSpec(StructuredInvoiceData)` +- `StructuredInvoiceDocumentSpec from(StructuredInvoiceData invoice)` +- `StructuredInvoiceData invoice()` + ## com.demcha.compose.document.templates.data.proposal +### ProposalAcceptance (record) +- `new ProposalAcceptance(String, String, String, List)` +- `String heading()` +- `String icon()` +- `String statement()` +- `List fields()` + +### ProposalAttention (record) +- `new ProposalAttention(String, String, String, String, String)` +- `boolean isPresent()` +- `String label()` +- `String name()` +- `String role()` +- `String email()` +- `String phone()` + +### ProposalBrand (record) +- `new ProposalBrand(String, String, String, String, String, String)` +- `String monogram()` +- `String nameLine1()` +- `String nameLine2()` +- `String documentLabel()` +- `String website()` +- `String footerName()` + ### ProposalData (record) - `new ProposalData(String, String, String, String, String, String, ProposalParty, ProposalParty, List, List, List, List, String)` - `ProposalData.Builder builder()` @@ -1096,6 +1357,13 @@ Types: 161 · methods: 954 · constants: 118 · compiler-generated members: 403 - `ProposalData.Builder footerNote(String footerNote)` - `ProposalData build()` +### ProposalDeliverables (record) +- `new ProposalDeliverables(String, String, List, List)` +- `String heading()` +- `String icon()` +- `List leftColumn()` +- `List rightColumn()` + ### ProposalDocumentSpec (record) - `new ProposalDocumentSpec(ProposalData)` - `ProposalDocumentSpec from(ProposalData proposal)` @@ -1131,6 +1399,75 @@ Types: 161 · methods: 954 · constants: 118 · compiler-generated members: 403 - `ProposalDocumentSpec.Builder footerNote(String footerNote)` - `ProposalDocumentSpec build()` +### ProposalFooter (record) +- `new ProposalFooter(String, List, List, String)` +- `boolean isPresent()` +- `String name()` +- `List addressLines()` +- `List contacts()` +- `String confidentiality()` + +### ProposalGlance (record) +- `new ProposalGlance(String, List, String)` +- `new ProposalGlance(String heading, List facts)` +- `String heading()` +- `List facts()` +- `String intro()` + +### ProposalGlance.Fact (record) +- `new Fact(String, String, String, String)` +- `String icon()` +- `String label()` +- `String value()` +- `String note()` + +### ProposalGoals (record) +- `new ProposalGoals(String, String, List, String)` +- `new ProposalGoals(String heading, String icon, List items)` +- `String heading()` +- `String icon()` +- `List items()` +- `String intro()` + +### ProposalGoals.Goal (record) +- `new Goal(String, String)` +- `String icon()` +- `String text()` + +### ProposalInvestment (record) +- `new ProposalInvestment(String, String, String, String, List, String, String)` +- `String heading()` +- `String icon()` +- `String itemHeader()` +- `String amountHeader()` +- `List rows()` +- `String totalLabel()` +- `String totalAmount()` + +### ProposalInvestment.Role (enum) +- constants: `NONE`, `SUBTOTAL`, `OPTIONAL` + +### ProposalInvestment.Row (record) +- `new Row(String, String, ProposalInvestment.Role)` +- `String label()` +- `String amount()` +- `ProposalInvestment.Role role()` + +### ProposalMetaLine (record) +- `new ProposalMetaLine(String, String, String, List)` +- `new ProposalMetaLine(String preparedFor, String preparedBy, String date)` +- `new ProposalMetaLine(List entries)` +- `String preparedFor()` +- `String preparedBy()` +- `String date()` +- `List entries()` + +### ProposalMetaLine.Entry (record) +- `new Entry(String, String, String)` +- `String icon()` +- `String label()` +- `String value()` + ### ProposalParty (record) - `new ProposalParty(String, List, String, String, String)` - `ProposalParty.Builder builder()` @@ -1150,6 +1487,21 @@ Types: 161 · methods: 954 · constants: 118 · compiler-generated members: 403 - `ProposalParty.Builder website(String website)` - `ProposalParty build()` +### ProposalPhaseGrid (record) +- `new ProposalPhaseGrid(String, String, List, List)` +- `String heading()` +- `String icon()` +- `List columnHeaders()` +- `List phases()` + +### ProposalPhaseGrid.Phase (record) +- `new Phase(String, String, String, String, String)` +- `String number()` +- `String name()` +- `String focus()` +- `String duration()` +- `String output()` + ### ProposalPricingRow (record) - `new ProposalPricingRow(String, String, String, boolean)` - `ProposalPricingRow.Builder builder()` @@ -1165,6 +1517,27 @@ Types: 161 · methods: 954 · constants: 118 · compiler-generated members: 403 - `ProposalPricingRow.Builder emphasized(boolean emphasized)` - `ProposalPricingRow build()` +### ProposalRecipient (record) +- `new ProposalRecipient(String, String, List)` +- `boolean isPresent()` +- `String label()` +- `String name()` +- `List addressLines()` + +### ProposalScope (record) +- `new ProposalScope(String, String, List, String)` +- `new ProposalScope(String heading, String icon, List items)` +- `String heading()` +- `String icon()` +- `List items()` +- `String intro()` + +### ProposalScope.Item (record) +- `new Item(String, String, String)` +- `String number()` +- `String title()` +- `String description()` + ### ProposalSection (record) - `new ProposalSection(String, List)` - `ProposalSection.Builder builder()` @@ -1178,6 +1551,18 @@ Types: 161 · methods: 954 · constants: 118 · compiler-generated members: 403 - `ProposalSection.Builder addParagraph(String paragraph)` - `ProposalSection build()` +### ProposalSummaryBlock (record) +- `new ProposalSummaryBlock(String, String, List)` +- `String heading()` +- `String icon()` +- `List paragraphs()` + +### ProposalTermsBlock (record) +- `new ProposalTermsBlock(String, String, List)` +- `String heading()` +- `String icon()` +- `List items()` + ### ProposalTimelineItem (record) - `new ProposalTimelineItem(String, String, String)` - `ProposalTimelineItem.Builder builder()` @@ -1191,6 +1576,268 @@ Types: 161 · methods: 954 · constants: 118 · compiler-generated members: 403 - `ProposalTimelineItem.Builder details(String details)` - `ProposalTimelineItem build()` +### ProposalTitleLines (record) +- `new ProposalTitleLines(String, String, String, String, List, List)` +- `new ProposalTitleLines(String lead, String second, String third)` +- `ProposalTitleLines of(String eyebrow, List lines, List standfirst)` +- `String lead()` +- `String second()` +- `String third()` +- `String eyebrow()` +- `List lines()` +- `List standfirst()` + +### StructuredProposalData (record) +- `new StructuredProposalData(ProposalBrand, ProposalTitleLines, ProposalMetaLine, ProposalSummaryBlock, ProposalGlance, ProposalGoals, ProposalScope, ProposalDeliverables, ProposalPhaseGrid, ProposalInvestment, ProposalTermsBlock, ProposalAcceptance, ProposalRecipient, ProposalAttention, ProposalFooter)` +- `new StructuredProposalData(ProposalBrand brand, ProposalTitleLines title, ProposalMetaLine meta, ProposalSummaryBlock executiveSummary, ProposalGlance glance, ProposalGoals goals, ProposalScope scope, ProposalDeliverables deliverables, ProposalPhaseGrid timeline, ProposalInvestment investment, ProposalTermsBlock terms, ProposalAcceptance acceptance)` +- `StructuredProposalData.Builder builder()` +- `ProposalBrand brand()` +- `ProposalTitleLines title()` +- `ProposalMetaLine meta()` +- `ProposalSummaryBlock executiveSummary()` +- `ProposalGlance glance()` +- `ProposalGoals goals()` +- `ProposalScope scope()` +- `ProposalDeliverables deliverables()` +- `ProposalPhaseGrid timeline()` +- `ProposalInvestment investment()` +- `ProposalTermsBlock terms()` +- `ProposalAcceptance acceptance()` +- `ProposalRecipient recipient()` +- `ProposalAttention attention()` +- `ProposalFooter footer()` + +### StructuredProposalData.Builder (class) +- `StructuredProposalData.Builder brand(ProposalBrand brand)` +- `StructuredProposalData.Builder title(ProposalTitleLines title)` +- `StructuredProposalData.Builder meta(ProposalMetaLine meta)` +- `StructuredProposalData.Builder executiveSummary(ProposalSummaryBlock executiveSummary)` +- `StructuredProposalData.Builder glance(ProposalGlance glance)` +- `StructuredProposalData.Builder goals(ProposalGoals goals)` +- `StructuredProposalData.Builder scope(ProposalScope scope)` +- `StructuredProposalData.Builder deliverables(ProposalDeliverables deliverables)` +- `StructuredProposalData.Builder timeline(ProposalPhaseGrid timeline)` +- `StructuredProposalData.Builder investment(ProposalInvestment investment)` +- `StructuredProposalData.Builder terms(ProposalTermsBlock terms)` +- `StructuredProposalData.Builder acceptance(ProposalAcceptance acceptance)` +- `StructuredProposalData.Builder recipient(ProposalRecipient recipient)` +- `StructuredProposalData.Builder attention(ProposalAttention attention)` +- `StructuredProposalData.Builder footer(ProposalFooter footer)` +- `StructuredProposalData build()` + +### StructuredProposalDocumentSpec (record) +- `new StructuredProposalDocumentSpec(StructuredProposalData)` +- `StructuredProposalDocumentSpec from(StructuredProposalData proposal)` +- `StructuredProposalData proposal()` + +## com.demcha.compose.document.templates.data.receipt + +### ReceiptData (record) +- `new ReceiptData(String, String, String, String, String, String, String, ReceiptStatus, List, String, ReceiptParty, String, ReceiptParty, List, List, List, String, String, List, String)` +- `boolean hasPayer()` +- `boolean hasBeneficiary()` +- `ReceiptData.Builder builder()` +- `String documentTitle()` +- `String issuerName()` +- `String generatedOn()` +- `String reference()` +- `String amountLabel()` +- `String amount()` +- `String amountCaption()` +- `ReceiptStatus status()` +- `List summaryFields()` +- `String payerLabel()` +- `ReceiptParty payer()` +- `String beneficiaryLabel()` +- `ReceiptParty beneficiary()` +- `List detailGroups()` +- `List timeline()` +- `List notes()` +- `String verificationUrl()` +- `String verificationText()` +- `List supportLines()` +- `String legalNote()` + +### ReceiptData.Builder (class) +- `ReceiptData.Builder documentTitle(String documentTitle)` +- `ReceiptData.Builder issuerName(String issuerName)` +- `ReceiptData.Builder generatedOn(String generatedOn)` +- `ReceiptData.Builder reference(String reference)` +- `ReceiptData.Builder amount(String label, String amount)` +- `ReceiptData.Builder amountCaption(String amountCaption)` +- `ReceiptData.Builder status(ReceiptStatus status)` +- `ReceiptData.Builder status(String label, ReceiptStatusTone tone)` +- `ReceiptData.Builder summaryField(String label, String value)` +- `ReceiptData.Builder payer(String label, Consumer spec)` +- `ReceiptData.Builder beneficiary(String label, Consumer spec)` +- `ReceiptData.Builder detailGroup(String title, Consumer spec)` +- `ReceiptData.Builder addDetailGroup(ReceiptFieldGroup group)` +- `ReceiptData.Builder event(String label, String timestamp, String detail)` +- `ReceiptData.Builder note(String note)` +- `ReceiptData.Builder verification(String url, String text)` +- `ReceiptData.Builder supportLine(String line)` +- `ReceiptData.Builder legalNote(String legalNote)` +- `ReceiptData build()` + +### ReceiptDocumentSpec (record) +- `new ReceiptDocumentSpec(ReceiptData)` +- `ReceiptDocumentSpec from(ReceiptData receipt)` +- `ReceiptDocumentSpec of(Consumer spec)` +- `ReceiptData receipt()` + +### ReceiptEvent (record) +- `new ReceiptEvent(String, String, String)` +- `ReceiptEvent of(String label, String timestamp)` +- `String label()` +- `String timestamp()` +- `String detail()` + +### ReceiptField (record) +- `new ReceiptField(String, String, boolean)` +- `ReceiptField of(String label, String value)` +- `ReceiptField emphasized(String label, String value)` +- `String label()` +- `String value()` +- `boolean emphasized()` + +### ReceiptFieldGroup (record) +- `new ReceiptFieldGroup(String, List)` +- `ReceiptFieldGroup.Builder builder(String title)` +- `String title()` +- `List fields()` + +### ReceiptFieldGroup.Builder (class) +- `ReceiptFieldGroup.Builder field(String label, String value)` +- `ReceiptFieldGroup.Builder emphasized(String label, String value)` +- `ReceiptFieldGroup.Builder add(ReceiptField field)` +- `ReceiptFieldGroup build()` + +### ReceiptParty (record) +- `new ReceiptParty(String, List, List)` +- `ReceiptParty.Builder builder()` +- `String name()` +- `List addressLines()` +- `List fields()` + +### ReceiptParty.Builder (class) +- `ReceiptParty.Builder name(String name)` +- `ReceiptParty.Builder addressLines(String... lines)` +- `ReceiptParty.Builder field(String label, String value)` +- `ReceiptParty build()` + +### ReceiptStatus (record) +- `new ReceiptStatus(String, ReceiptStatusTone)` +- `ReceiptStatus settled(String label)` +- `ReceiptStatus inProgress(String label)` +- `boolean hasLabel()` +- `String label()` +- `ReceiptStatusTone tone()` + +### ReceiptStatusTone (enum) +- constants: `SETTLED`, `IN_PROGRESS`, `ATTENTION`, `FAILED` + +## com.demcha.compose.document.templates.data.rota + +### RotaCovers (record) +- `new RotaCovers(String, String)` +- `boolean isPresent()` +- `String lunch()` +- `String dinner()` + +### RotaDay (record) +- `new RotaDay(String, String, String, String, RotaCovers)` +- `new RotaDay(String name, String ordinal, String ordinalSuffix)` +- `String name()` +- `String ordinal()` +- `String ordinalSuffix()` +- `String note()` +- `RotaCovers covers()` + +### RotaFooter (record) +- `new RotaFooter(String)` +- `boolean isPresent()` +- `String note()` + +### RotaGroup (record) +- `new RotaGroup(String, String, List)` +- `new RotaGroup(String label, List staff)` +- `String label()` +- `String icon()` +- `List staff()` + +### RotaLegend (record) +- `new RotaLegend(String, String, String, String, List)` +- `new RotaLegend(String label, List entries)` +- `boolean isPresent()` +- `String label()` +- `String coversLabel()` +- `String coversLunchLabel()` +- `String coversDinnerLabel()` +- `List entries()` + +### RotaLegend.Entry (record) +- `new Entry(String, ShiftStatus)` +- `String label()` +- `ShiftStatus status()` + +### RotaShift (record) +- `new RotaShift(String, ShiftStatus, ShiftEmphasis)` +- `RotaShift hours(String text)` +- `RotaShift strong(String text, ShiftStatus status)` +- `RotaShift soft(String text, ShiftStatus status)` +- `String text()` +- `ShiftStatus status()` +- `ShiftEmphasis emphasis()` + +### RotaStaff (record) +- `new RotaStaff(String, List>)` +- `List day(int index)` +- `String name()` +- `List> days()` + +### RotaVenue (record) +- `new RotaVenue(String, String, String)` +- `boolean isPresent()` +- `String wordmark()` +- `String wordmarkSub()` +- `String footerName()` + +### RotaWeek (record) +- `new RotaWeek(String, String)` +- `boolean isPresent()` +- `String title()` +- `String rangeLabel()` + +### ShiftEmphasis (enum) +- constants: `STRONG`, `SOFT`, `PLAIN` + +### ShiftStatus (enum) +- constants: `NONE`, `REQUEST`, `OFF`, `HOLIDAY`, `STOCK`, `STANDBY`, `TRAINING`, `SUPPORT` + +### StructuredRotaData (record) +- `new StructuredRotaData(RotaVenue, RotaWeek, List, RotaLegend, List, RotaFooter)` +- `StructuredRotaData.Builder builder()` +- `RotaVenue venue()` +- `RotaWeek week()` +- `List days()` +- `RotaLegend legend()` +- `List groups()` +- `RotaFooter footer()` + +### StructuredRotaData.Builder (class) +- `StructuredRotaData.Builder venue(RotaVenue venue)` +- `StructuredRotaData.Builder week(RotaWeek week)` +- `StructuredRotaData.Builder days(List days)` +- `StructuredRotaData.Builder legend(RotaLegend legend)` +- `StructuredRotaData.Builder groups(List groups)` +- `StructuredRotaData.Builder footer(RotaFooter footer)` +- `StructuredRotaData build()` + +### StructuredRotaDocumentSpec (record) +- `new StructuredRotaDocumentSpec(StructuredRotaData)` +- `StructuredRotaDocumentSpec from(StructuredRotaData rota)` +- `StructuredRotaData rota()` + ## com.demcha.compose.document.templates.data.schedule ### ScheduleAssignment (record) @@ -1369,14 +2016,140 @@ Types: 161 · methods: 954 · constants: 118 · compiler-generated members: 403 ## com.demcha.compose.document.templates.invoice.presets +### ClassicInvoice (class) +- `DocumentTemplate create()` +- `DocumentTemplate create(BrandTheme theme)` +- constants: `ID`, `DISPLAY_NAME`, `RECOMMENDED_MARGIN` + +### ConsultingInvoice (class) +- `DocumentTemplate create()` +- constants: `ID`, `DISPLAY_NAME`, `RECOMMENDED_MARGIN` + +### LumaStudioInvoice (class) +- `DocumentTemplate create()` +- constants: `ID`, `DISPLAY_NAME` + +### MerchantInvoice (class) +- `DocumentTemplate create()` +- constants: `ID`, `DISPLAY_NAME` + +### MeteredInvoice (class) +- `DocumentTemplate create()` +- constants: `ID`, `DISPLAY_NAME` + ### ModernInvoice (class) - `DocumentTemplate create()` - `DocumentTemplate create(BrandTheme theme)` - constants: `ID`, `DISPLAY_NAME`, `RECOMMENDED_MARGIN` +### ObsidianInvoice (class) +- `DocumentTemplate create()` +- constants: `ID`, `DISPLAY_NAME` + +### PaymentsInvoice (class) +- `DocumentTemplate create()` +- constants: `ID`, `DISPLAY_NAME` + +### PlatformInvoice (class) +- `DocumentTemplate create()` +- constants: `ID`, `DISPLAY_NAME` + +### SubscriptionInvoice (class) +- `DocumentTemplate create()` +- constants: `ID`, `DISPLAY_NAME` + +### WorkspaceInvoice (class) +- `DocumentTemplate create()` +- constants: `ID`, `DISPLAY_NAME` + ## com.demcha.compose.document.templates.proposal.presets +### EditorialProposal (class) +- `DocumentTemplate create()` +- constants: `ID`, `DISPLAY_NAME`, `RECOMMENDED_MARGIN` + +### IndigoProposal (class) +- `DocumentTemplate create()` +- constants: `ID`, `DISPLAY_NAME`, `RECOMMENDED_MARGIN` + ### ModernProposal (class) - `DocumentTemplate create()` - `DocumentTemplate create(BrandTheme theme)` - constants: `ID`, `DISPLAY_NAME`, `RECOMMENDED_MARGIN` + +### NorthlineProposal (class) +- `DocumentTemplate create()` +- constants: `ID`, `DISPLAY_NAME`, `RECOMMENDED_MARGIN` + +## com.demcha.compose.document.templates.receipt.components + +### FieldRowRenderer (class) +- `void renderAll(SectionBuilder host, List fields, BrandTheme theme)` +- `void renderInline(SectionBuilder host, List fields, TextAlign align, BrandTheme theme)` +- `void render(SectionBuilder host, ReceiptField field, BrandTheme theme)` + +### ReceiptStyles (class) +- `DocumentTextStyle eyebrow(BrandTheme theme)` +- `DocumentTextStyle groupTitle(BrandTheme theme)` +- `DocumentTextStyle title(BrandTheme theme)` +- `DocumentTextStyle caption(BrandTheme theme)` +- `DocumentTextStyle label(BrandTheme theme)` +- `DocumentTextStyle value(BrandTheme theme)` +- `DocumentTextStyle valueStrong(BrandTheme theme)` +- `DocumentTextStyle amount(BrandTheme theme)` +- `DocumentTextStyle partyName(BrandTheme theme)` +- `DocumentTextStyle body(BrandTheme theme)` +- `DocumentTextStyle smallPrint(BrandTheme theme)` +- `DocumentTextStyle pill(BrandTheme theme, DocumentColor color)` + +### StatusPill (class) +- `void render(SectionBuilder host, ReceiptStatus status, DocumentColor accent, TextAlign align, BrandTheme theme)` + +## com.demcha.compose.document.templates.receipt.presets + +### ModernReceipt (class) +- `DocumentTemplate create()` +- `DocumentTemplate create(BrandTheme theme)` +- `DocumentTemplate create(BrandTheme theme, ModernReceipt.Options options)` +- constants: `ID`, `DISPLAY_NAME`, `RECOMMENDED_MARGIN` + +### ModernReceipt.Options (record) +- `new Options(SvgGlyph, double, DocumentColor, DocumentColor, String)` +- `ModernReceipt.Options defaults()` +- `ModernReceipt.Options branded(SvgGlyph logo, DocumentColor accent)` +- `ModernReceipt.Options withLogoWidth(double width)` +- `ModernReceipt.Options withLogoColor(DocumentColor color)` +- `ModernReceipt.Options withTimelineTitle(String title)` +- `SvgGlyph logo()` +- `double logoWidth()` +- `DocumentColor logoColor()` +- `DocumentColor accent()` +- `String timelineTitle()` +- constants: `DEFAULT_LOGO_WIDTH`, `DEFAULT_TIMELINE_TITLE` + +## com.demcha.compose.document.templates.receipt.widgets + +### AmountHero (class) +- `void render(SectionBuilder host, ReceiptData data, DocumentColor accent, BrandTheme theme)` + +### DetailGroup (class) +- `void render(SectionBuilder host, ReceiptFieldGroup group, BrandTheme theme)` + +### PartyPair (class) +- `void render(SectionBuilder host, ReceiptData data, DocumentColor accent, BrandTheme theme)` + +### ReceiptFooter (class) +- `boolean hasContent(ReceiptData data)` +- `void render(SectionBuilder host, ReceiptData data, BrandTheme theme)` + +### ReceiptMasthead (class) +- `void render(SectionBuilder host, ReceiptData data, SvgGlyph logo, double logoWidth, DocumentColor logoColor, BrandTheme theme)` + +### StatusTrail (class) +- `void render(SectionBuilder host, String title, List events, DocumentColor accent, BrandTheme theme)` + +## com.demcha.compose.document.templates.rota.presets + +### CobaltRota (class) +- `DocumentTemplate create()` +- constants: `ID`, `DISPLAY_NAME`, `RECOMMENDED_MARGIN` diff --git a/qa/src/test/java/com/demcha/compose/document/api/ListDrawnMarkerTest.java b/qa/src/test/java/com/demcha/compose/document/api/ListDrawnMarkerTest.java new file mode 100644 index 000000000..7a22f724b --- /dev/null +++ b/qa/src/test/java/com/demcha/compose/document/api/ListDrawnMarkerTest.java @@ -0,0 +1,383 @@ +package com.demcha.compose.document.api; + +import com.demcha.compose.GraphCompose; +import com.demcha.compose.document.dsl.ListBuilder; +import com.demcha.compose.document.layout.LayoutGraph; +import com.demcha.compose.document.layout.PlacedFragment; +import com.demcha.compose.document.layout.payloads.ParagraphFragmentPayload; +import com.demcha.compose.document.layout.payloads.ParagraphLine; +import com.demcha.compose.document.layout.payloads.ParagraphShapeSpan; +import com.demcha.compose.document.layout.payloads.ParagraphSpan; +import com.demcha.compose.document.layout.payloads.ParagraphSvgSpan; +import com.demcha.compose.document.layout.payloads.ParagraphTextSpan; +import com.demcha.compose.document.node.ListMarker; +import com.demcha.compose.document.style.DocumentColor; +import com.demcha.compose.document.style.DocumentInsets; +import com.demcha.compose.document.style.DocumentTextStyle; +import com.demcha.compose.document.svg.SvgIcon; +import org.junit.jupiter.api.Test; + +import java.util.List; +import java.util.function.Consumer; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.assertj.core.api.Assertions.within; + +/** + * A list marker that is drawn rather than typed — a coloured disc, an icon, a + * glyph in a colour of its own — on the same measured marker column a text + * marker gets. + * + *

What made this a gap rather than a preference: the marker's one span was + * built in the list's own text style, so a design with an accent mark beside + * near-black copy could not be written as a list at all. The templates that + * wanted it built a paragraph per item instead — a drawn mark, then a run of + * spaces standing in for the gap, then the text — which buys the mark and loses + * the column. Measured on that construction at a width that wraps: the first + * line's text starts at 25.246 and every line after it at 12.000, because a + * paragraph has no marker column to hang under. The spaces are a second cost; + * rounded to a whole count and then measured at each item's own type size, one + * declared 9.175pt gap came out as 9.308pt in one column of a real CV and + * 8.356pt in another.

+ * + *

Numbers below are at the default face, 9pt, on a page with a 12pt margin, + * so an item starts at x=12.

+ */ +class ListDrawnMarkerTest { + + private static final double EPS = 0.001; + private static final DocumentColor ACCENT = DocumentColor.rgb(0x00, 0x88, 0x88); + private static final DocumentTextStyle BODY = DocumentTextStyle.builder().size(9.0).build(); + + private static final String LONG = + "Coordinate care plans with doctors, therapists and support staff to improve " + + "outcomes across the whole ward and its discharge pathway."; + + // --- the marker is measured as what it draws ---------------------------- + + @Test + void aDiscMarkerTakesItsDeclaredDiameterAndTheGapAfterItIsPoints() throws Exception { + Rendered rendered = render(200, 240, l -> l + .textStyle(BODY) + .marker(m -> m.dot(4.0, ACCENT)) + .hangingIndent(true) + .markerGap(9.175) + .items("Patient assessment")); + + List fragments = rendered.fragments(); + assertThat(fragments).as("one marker, one content block").hasSize(2); + PlacedFragment marker = fragments.get(0); + PlacedFragment content = fragments.get(1); + + assertThat(marker.x()).isEqualTo(12.000, within(EPS)); + assertThat(marker.width()) + .as("the column is the disc's own diameter, not the width of any text") + .isEqualTo(4.000, within(EPS)); + assertThat(content.x()) + .as("markerX + diameter + the gap, exactly as asked for") + .isEqualTo(12.000 + 4.000 + 9.175, within(EPS)); + + // Drawn, not typed: the marker's fragment carries a shape. + assertThat(spans(marker)).hasSize(1); + assertThat(spans(marker).get(0)).isInstanceOf(ParagraphShapeSpan.class); + assertThat(((ParagraphShapeSpan) spans(marker).get(0)).width()).isEqualTo(4.000, within(EPS)); + assertThat(texts(marker)) + .as("and nothing typed leaks into the text layer in its place") + .containsExactly(""); + } + + @Test + void aTextMarkerCanCarryAColourTheItemsTextDoesNot() throws Exception { + Rendered rendered = render(200, 240, l -> l + .textStyle(BODY) + .marker(m -> m.color("•", ACCENT)) + .hangingIndent(true) + .items("Patient assessment")); + + ParagraphTextSpan markerSpan = (ParagraphTextSpan) spans(rendered.fragments().get(0)).get(0); + ParagraphTextSpan contentSpan = (ParagraphTextSpan) spans(rendered.fragments().get(1)).get(0); + + assertThat(markerSpan.text()).isEqualTo("•"); + assertThat(markerSpan.textStyle().color()).isEqualTo(ACCENT.color()); + assertThat(contentSpan.textStyle().color()) + .as("the item keeps the list's colour, which is the whole point") + .isNotEqualTo(markerSpan.textStyle().color()); + + // And it is still a measured marker column, not a coloured prefix inside + // the text: the two are separate fragments at separate x. + assertThat(rendered.fragments()).hasSize(2); + assertThat(rendered.fragments().get(1).x()).isGreaterThan(rendered.fragments().get(0).x()); + } + + @Test + void anIconMarkerTakesItsIconsBox() throws Exception { + Rendered rendered = render(200, 240, l -> l + .textStyle(BODY) + .marker(m -> m.svgIcon(TICK, 8.0)) + .hangingIndent(true) + .markerGap(4.0) + .items("Patient assessment")); + + PlacedFragment marker = rendered.fragments().get(0); + assertThat(marker.width()).isEqualTo(8.000, within(EPS)); + assertThat(spans(marker).get(0)).isInstanceOf(ParagraphSvgSpan.class); + assertThat(rendered.fragments().get(1).x()).isEqualTo(12.000 + 8.000 + 4.000, within(EPS)); + } + + @Test + void aDrawnMarkerIsMeasuredOnceHoweverManyItemsShowIt() throws Exception { + // Same marker on every row means the same column on every row — the + // property that makes a list a list, and the one the per-item paragraph + // construction cannot promise. + Rendered rendered = render(200, 240, l -> l + .textStyle(BODY) + .marker(m -> m.dot(4.0, ACCENT)) + .hangingIndent(true) + .markerGap(9.175) + .items("Alpha", "Beta", "Gamma")); + + assertThat(rendered.fragments()).hasSize(6); + for (int row = 0; row < 3; row++) { + assertThat(rendered.fragments().get(row * 2).x()) + .as("row %d markerX", row).isEqualTo(12.000, within(EPS)); + assertThat(rendered.fragments().get(row * 2 + 1).x()) + .as("row %d contentX", row).isEqualTo(25.175, within(EPS)); + } + } + + // --- and it is the marker column, so everything else holds -------------- + + @Test + void everyLineOfAWrappedItemHangsUnderTheTextAndNotUnderTheDisc() throws Exception { + Rendered rendered = render(220, 300, l -> l + .textStyle(BODY) + .marker(m -> m.dot(3.238, ACCENT)) + .hangingIndent(true) + .markerGap(9.175) + .items(LONG)); + + PlacedFragment content = rendered.fragments().get(1); + assertThat(content.x()).isEqualTo(12.000 + 3.238 + 9.175, within(EPS)); + assertThat(texts(content)).as("it wraps").hasSizeGreaterThanOrEqualTo(3); + assertThat(lineWidths(content)) + .allSatisfy(w -> assertThat(w).isLessThanOrEqualTo(content.width() + EPS)); + + // The claim the workaround cannot make: one origin for every line. + assertThat(rendered.glyphs()) + .allSatisfy(g -> assertThat(g.x()).isEqualTo(24.413, within(EPS))); + } + + @Test + void aDiscIsDrawnOnceWhenItsItemCrossesPages() throws Exception { + Rendered rendered = render(150, 70, l -> l + .textStyle(BODY) + .marker(m -> m.dot(4.0, ACCENT)) + .hangingIndent(true) + .markerGap(4.0) + .items(LONG + " " + LONG)); + + assertThat(rendered.graph().totalPages()).isGreaterThanOrEqualTo(2); + assertThat(rendered.fragments().stream().filter(f -> !spans(f).isEmpty() + && spans(f).get(0) instanceof ParagraphShapeSpan)) + .as("the disc belongs to the item, not to each page of it") + .hasSize(1); + + for (int page = 0; page < rendered.graph().totalPages(); page++) { + int onPage = page; + PlacedFragment content = rendered.fragments().stream() + .filter(f -> f.pageIndex() == onPage) + .filter(f -> !texts(f).equals(List.of(""))) + .findFirst() + .orElseThrow(); + assertThat(content.x()).as("page %d contentX", page).isEqualTo(20.000, within(EPS)); + } + } + + @Test + void aDrawnMarkerNestsTheWayATypedOneDoes() throws Exception { + Rendered rendered = render(220, 240, l -> l + .textStyle(BODY) + .hangingIndent(true) + .markerGap(4.0) + .marker(m -> m.dot(4.0, ACCENT)) + .markerFor(0, ListMarker.ofRuns( + com.demcha.compose.document.dsl.RichText.empty().dot(4.0, ACCENT).runs())) + .markerFor(1, ListMarker.custom("-")) + .addItem("Top", c -> c.addItem("Child"))); + + List fragments = rendered.fragments(); + assertThat(fragments).hasSize(4); + assertThat(fragments.get(0).x()).isEqualTo(12.000, within(EPS)); + assertThat(fragments.get(1).x()).isEqualTo(20.000, within(EPS)); + assertThat(fragments.get(2).x()) + .as("the child's marker starts at the parent's text, disc or no disc") + .isEqualTo(fragments.get(1).x(), within(EPS)); + assertThat(spans(fragments.get(0)).get(0)).isInstanceOf(ParagraphShapeSpan.class); + assertThat(texts(fragments.get(2))).containsExactly("-"); + } + + @Test + void aDrawnMarkerDoesNotMakeItsRowTaller() throws Exception { + // The marker rides the item's first baseline, which is what keeps a + // list's rows evenly pitched whatever their markers are. + double withDisc = listNode(render(200, 240, l -> l.textStyle(BODY) + .marker(m -> m.dot(4.0, ACCENT)).hangingIndent(true).items("Item")).graph()) + .placementHeight(); + double withBullet = listNode(render(200, 240, l -> l.textStyle(BODY) + .bullet().hangingIndent(true).items("Item")).graph()) + .placementHeight(); + assertThat(withDisc).isEqualTo(withBullet, within(EPS)); + } + + @Test + void aDiscMarkerReachesThePageAndNotJustTheGeometry() throws Exception { + // Everything else here measures where the marker is. This asks whether it + // is there at all: a drawn marker contributes no glyphs, so no text-layer + // or span assertion can tell a painted disc from a described one. + long withDisc = inkedPixels(pdf(200, 120, l -> l + .textStyle(BODY) + .marker(m -> m.dot(6.0, ACCENT)) + .hangingIndent(true) + .markerGap(4.0) + .items("Alpha", "Beta", "Gamma"))); + long withoutMarker = inkedPixels(pdf(200, 120, l -> l + .textStyle(BODY) + .noMarker() + .hangingIndent(true) + .items("Alpha", "Beta", "Gamma"))); + + assertThat(withDisc) + .as("three 6pt discs of ink the markerless list does not have") + .isGreaterThan(withoutMarker + 3 * 20); + } + + // --- refusals ----------------------------------------------------------- + + @Test + void aDrawnMarkerWithoutTheOptInSaysSoInsteadOfVanishing() throws Exception { + // Its plain reading is empty, so the legacy layout would render the list + // with no marker at all and no signal. + assertThatThrownBy(() -> render(200, 240, l -> l + .textStyle(BODY) + .marker(m -> m.dot(4.0, ACCENT)) + .items("Patient assessment"))) + .isInstanceOf(IllegalStateException.class) + .hasMessageContaining("hangingIndent(true)") + .hasMessageContaining("not a drawing"); + } + + @Test + void aPerItemDrawnMarkerWithoutTheOptInSaysSoToo() throws Exception { + assertThatThrownBy(() -> render(200, 240, l -> l + .textStyle(BODY) + .addItem("Top", c -> c.addItem("Child")) + .markerFor(1, ListMarker.ofRuns( + com.demcha.compose.document.dsl.RichText.empty().dot(4.0, ACCENT).runs())))) + .isInstanceOf(IllegalStateException.class) + .hasMessageContaining("hangingIndent(true)"); + } + + // ------------------------------------------------------------------ + + private record Rendered(LayoutGraph graph, List glyphs) { + List fragments() { + return graph.fragments().stream() + .filter(f -> f.payload() instanceof ParagraphFragmentPayload) + .toList(); + } + } + + private record Glyph(int page, double x, String text) { + } + + private static Rendered render(double width, double height, Consumer spec) + throws Exception { + try (DocumentSession session = GraphCompose.document() + .pageSize(width, height) + .margin(DocumentInsets.of(12)) + .create()) { + session.pageFlow().name("Root").addList(spec).build(); + return new Rendered(session.layoutGraph(), glyphs(session.toPdfBytes())); + } + } + + private static byte[] pdf(double width, double height, Consumer spec) + throws Exception { + try (DocumentSession session = GraphCompose.document() + .pageSize(width, height) + .margin(DocumentInsets.of(12)) + .create()) { + session.pageFlow().name("Root").addList(spec).build(); + return session.toPdfBytes(); + } + } + + /** Pixels darker than paper on page 0, rendered at 72 dpi. */ + private static long inkedPixels(byte[] pdf) throws Exception { + try (org.apache.pdfbox.pdmodel.PDDocument document = org.apache.pdfbox.Loader.loadPDF(pdf)) { + java.awt.image.BufferedImage image = + new org.apache.pdfbox.rendering.PDFRenderer(document).renderImage(0, 1.0f); + long inked = 0; + for (int y = 0; y < image.getHeight(); y++) { + for (int x = 0; x < image.getWidth(); x++) { + int rgb = image.getRGB(x, y); + int luma = ((rgb >> 16 & 0xFF) + (rgb >> 8 & 0xFF) + (rgb & 0xFF)) / 3; + if (luma < 200) { + inked++; + } + } + } + return inked; + } + } + + private static List spans(PlacedFragment fragment) { + return ((ParagraphFragmentPayload) fragment.payload()).lines().stream() + .map(ParagraphLine::spans) + .flatMap(List::stream) + .toList(); + } + + private static List texts(PlacedFragment fragment) { + return ((ParagraphFragmentPayload) fragment.payload()).lines().stream() + .map(ParagraphLine::text) + .toList(); + } + + private static List lineWidths(PlacedFragment fragment) { + return ((ParagraphFragmentPayload) fragment.payload()).lines().stream() + .map(ParagraphLine::width) + .toList(); + } + + private static com.demcha.compose.document.layout.PlacedNode listNode(LayoutGraph graph) { + return graph.nodes().stream() + .filter(n -> "ListNode".equals(n.nodeKind())) + .findFirst() + .orElseThrow(); + } + + private static List glyphs(byte[] pdf) throws Exception { + List rows = new java.util.ArrayList<>(); + try (org.apache.pdfbox.pdmodel.PDDocument document = org.apache.pdfbox.Loader.loadPDF(pdf)) { + org.apache.pdfbox.text.PDFTextStripper stripper = new org.apache.pdfbox.text.PDFTextStripper() { + @Override + protected void writeString(String text, + List positions) { + if (positions.isEmpty()) { + return; + } + rows.add(new Glyph(getCurrentPageNo() - 1, positions.get(0).getXDirAdj(), text)); + } + }; + stripper.setSortByPosition(true); + stripper.getText(document); + } + return rows; + } + + private static final SvgIcon TICK = SvgIcon.parse( + "" + + ""); +} diff --git a/qa/src/test/java/com/demcha/compose/document/api/ListHangingIndentTest.java b/qa/src/test/java/com/demcha/compose/document/api/ListHangingIndentTest.java index c57f55a6b..e3edb3d88 100644 --- a/qa/src/test/java/com/demcha/compose/document/api/ListHangingIndentTest.java +++ b/qa/src/test/java/com/demcha/compose/document/api/ListHangingIndentTest.java @@ -7,8 +7,12 @@ import com.demcha.compose.document.layout.PlacedNode; import com.demcha.compose.document.layout.payloads.ParagraphFragmentPayload; import com.demcha.compose.document.layout.payloads.ParagraphLine; +import com.demcha.compose.document.layout.payloads.ParagraphShapeSpan; +import com.demcha.compose.document.layout.payloads.ParagraphTextSpan; import com.demcha.compose.document.node.TextAlign; +import com.demcha.compose.document.style.DocumentColor; import com.demcha.compose.document.style.DocumentInsets; +import com.demcha.compose.engine.components.content.text.TextDecoration; import org.apache.pdfbox.Loader; import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.text.PDFTextStripper; @@ -21,6 +25,7 @@ import java.util.function.Consumer; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.assertj.core.api.Assertions.within; /** @@ -171,6 +176,188 @@ void aMarkerOnlyRowKeepsItsPlaceItsMarkerAndItsHeight() throws Exception { assertThat(firstToSecond).isEqualTo(secondToThird, within(EPS)); } + // --- rich items --------------------------------------------------------- + + @Test + void aRichItemSitsInTheSameMarkerColumnAndContentOriginAsAPlainOne() throws Exception { + // The same reading, authored twice: once as a string, once as runs. The + // point of expressing styled content as inline runs rather than as a + // second rich-text model is that nothing about the row's geometry knows + // the difference. + Rendered plain = render(200, 240, l -> l.bullet().hangingIndent(true) + .addItem("Status: pending")); + Rendered rich = render(200, 240, l -> l.bullet().hangingIndent(true) + .addItem(t -> t.bold("Status: ").plain("pending"))); + + assertThat(rich.fragments()).as("one marker, one content block").hasSize(2); + PlacedFragment marker = rich.fragments().get(0); + PlacedFragment content = rich.fragments().get(1); + + assertThat(texts(marker)).containsExactly("•"); + assertThat(marker.x()).isEqualTo(12.000, within(EPS)); + assertThat(marker.width()).as("the marker is measured, not skipped") + .isEqualTo(4.900, within(EPS)); + assertThat(content.x()).isEqualTo(20.900, within(EPS)); + assertThat(content.x()).as("the same origin a plain item gets") + .isEqualTo(plain.fragments().get(1).x(), within(EPS)); + assertThat(content.width()).as("and the same content column") + .isEqualTo(plain.fragments().get(1).width(), within(EPS)); + + // One row, not two: the runs are content of a single item. + assertThat(texts(content)).containsExactly("Status: pending"); + assertThat(rich.glyphs()).allSatisfy(g -> assertThat(g.page()).isZero()); + } + + @Test + void eachRunOfARichItemKeepsItsOwnStyle() throws Exception { + Rendered rich = render(200, 240, l -> l.bullet().hangingIndent(true) + .addItem(t -> t.bold("Status: ").plain("pending"))); + + // Word spans are what the wrapper produces, so the authored runs are + // read back by their text rather than by a span count. + List words = textSpans(rich.fragments().get(1)).stream() + .filter(s -> !s.text().isBlank()) + .toList(); + assertThat(words).hasSize(2); + assertThat(words.get(0).text()).isEqualTo("Status:"); + assertThat(words.get(0).textStyle().decoration()).isEqualTo(TextDecoration.BOLD); + assertThat(words.get(1).text()).isEqualTo("pending"); + assertThat(words.get(1).textStyle().decoration()) + .as("a run with no style of its own inherits the list's") + .isEqualTo(TextDecoration.DEFAULT); + + // And the styles are load-bearing: the bold half measures wider than the + // same characters would have if the item were one face throughout. + Rendered flat = render(200, 240, l -> l.bullet().hangingIndent(true) + .addItem("Status: pending")); + assertThat(lineWidths(rich.fragments().get(1)).get(0)) + .as("bold glyphs are wider, so the mixed line is the wider line") + .isGreaterThan(lineWidths(flat.fragments().get(1)).get(0) + EPS); + } + + @Test + void aRichItemWrapsInsideItsContentColumnAndEveryLineStartsAtTheOrigin() throws Exception { + Rendered rendered = render(165, 240, l -> l.bullet().hangingIndent(true) + .addItem(t -> t.bold("Lead: ").plain( + "the rest of this item is long enough to wrap across several visual " + + "lines while the item keeps one bullet."))); + + PlacedFragment content = rendered.fragments().get(1); + assertThat(content.x()).isEqualTo(20.900, within(EPS)); + assertThat(content.width()).isEqualTo(132.100, within(EPS)); + assertThat(texts(content)).hasSizeGreaterThanOrEqualTo(3); + + // Wrapped inside the content column, not inside the row. + assertThat(lineWidths(content)) + .allSatisfy(w -> assertThat(w).isLessThanOrEqualTo(content.width() + EPS)); + assertThat(lineWidths(content).stream().mapToDouble(Double::doubleValue).max().orElseThrow()) + .isGreaterThan(content.width() * 0.75); + + // The bold run is on the first line only; the continuation is the plain + // run, and it starts at the same origin rather than under the marker. + assertThat(textSpans(content).get(0).textStyle().decoration()).isEqualTo(TextDecoration.BOLD); + assertThat(rendered.glyphs().stream().filter(g -> !g.text().equals("•"))) + .allSatisfy(g -> assertThat(g.x()).isEqualTo(20.900, within(EPS))); + } + + @Test + void aRichItemCrossingAPageDrawsItsMarkerOnceAndKeepsItsContentOrigin() throws Exception { + Rendered rendered = render(150, 70, l -> l.bullet().hangingIndent(true) + .addItem(t -> t.bold("Lead: ").plain( + "the rest of this item is long enough to wrap across many visual lines " + + "so that it has to cross a page boundary and continue on the following " + + "page with its indent intact and unchanged."))); + + assertThat(rendered.graph().totalPages()).isGreaterThanOrEqualTo(2); + assertThat(markerCount(rendered)) + .as("the marker belongs to the item, not to each page of it") + .isEqualTo(1); + + for (int page = 0; page < rendered.graph().totalPages(); page++) { + PlacedFragment content = onPage(rendered, page).stream() + .filter(f -> !texts(f).equals(List.of("•"))) + .findFirst() + .orElseThrow(); + assertThat(content.x()).as("page %d contentX", page).isEqualTo(20.900, within(EPS)); + assertThat(content.width()).as("page %d contentWidth", page).isEqualTo(117.100, within(EPS)); + } + assertThat(rendered.glyphs().stream().filter(g -> !g.text().equals("•"))) + .allSatisfy(g -> assertThat(g.x()).isEqualTo(20.900, within(EPS))); + } + + @Test + void aRichParentsChildrenHangUnderItsText() throws Exception { + Rendered rendered = render(220, 240, l -> l.hangingIndent(true) + .addItem(t -> t.bold("Top"), c -> c.addItem("Child"))); + + List fragments = rendered.fragments(); + assertThat(fragments).hasSize(4); + assertThat(fragments.get(0).x()).isEqualTo(12.000, within(EPS)); + assertThat(fragments.get(1).x()).isEqualTo(20.900, within(EPS)); + assertThat(fragments.get(2).x()).as("the child's marker starts at the parent's text") + .isEqualTo(fragments.get(1).x(), within(EPS)); + assertThat(fragments.get(3).x()).isEqualTo(32.684, within(EPS)); + assertThat(textSpans(fragments.get(1)).get(0).textStyle().decoration()) + .as("a styled label still heads the sub-tree") + .isEqualTo(TextDecoration.BOLD); + } + + @Test + void aRichItemThatDrawsNoTextIsStillARowOfItsOwn() throws Exception { + // A row whose runs are a shape and nothing else reads as "", which is + // exactly the reading an item that draws nothing has — and with no + // marker to keep it either, the two are indistinguishable by text. What + // separates them is the runs, so that is what decides whether the row + // survives normalization. + Rendered rendered = render(200, 240, l -> l.noMarker().hangingIndent(true) + .addItem("Alpha") + .addItem(t -> t.dot(6.0, DocumentColor.rgb(0x33, 0x66, 0x99))) + .addItem("Beta")); + + List fragments = rendered.fragments(); + assertThat(fragments).as("three rows, content only — a markerless list draws no marker") + .hasSize(3); + assertThat(texts(fragments.get(1))).as("no invented filler text").containsExactly(""); + assertThat(fragments.get(1).height()).as("a real row with a real height").isPositive(); + + // The dot is a span of the row, so the row is as tall as the dot needs. + assertThat(((ParagraphFragmentPayload) fragments.get(1).payload()).lines().get(0).spans()) + .hasAtLeastOneElementOfType(ParagraphShapeSpan.class); + + // Rows stay evenly spaced, so the shape row occupies one of them. + double firstToSecond = fragments.get(0).y() - fragments.get(1).y(); + double secondToThird = fragments.get(1).y() - fragments.get(2).y(); + assertThat(firstToSecond).isEqualTo(secondToThird, within(EPS)); + } + + @Test + void aFlatListsOwnMarkerStillAppliesWhenOneOfItsItemsIsRich() throws Exception { + // A rich item makes the list carry an item tree, because runs do not fit + // a list of labels. That is a representation change and must not become + // a marker change: the author asked for dashes. + Rendered rendered = render(200, 240, l -> l.dash().hangingIndent(true) + .addItem("Plain") + .addItem(t -> t.bold("Rich"))); + + assertThat(texts(rendered.fragments().get(0))).containsExactly("-"); + assertThat(texts(rendered.fragments().get(2))) + .as("the rich row takes the same marker as the plain one") + .containsExactly("-"); + assertThat(rendered.fragments().get(2).x()) + .isEqualTo(rendered.fragments().get(0).x(), within(EPS)); + assertThat(rendered.fragments().get(3).x()) + .isEqualTo(rendered.fragments().get(1).x(), within(EPS)); + } + + @Test + void aRichItemWithoutTheOptInSaysSoInsteadOfDroppingItsStyles() throws Exception { + assertThatThrownBy(() -> render(200, 240, l -> l.bullet() + .addItem(t -> t.bold("Status: ").plain("pending")))) + .isInstanceOf(IllegalStateException.class) + .hasMessageContaining("hangingIndent(true)") + .hasMessageContaining("Status: pending"); + } + // --- nesting ----------------------------------------------------------- @Test @@ -443,6 +630,15 @@ private static List lineWidths(PlacedFragment fragment) { .toList(); } + private static List textSpans(PlacedFragment fragment) { + return ((ParagraphFragmentPayload) fragment.payload()).lines().stream() + .map(ParagraphLine::spans) + .flatMap(List::stream) + .filter(ParagraphTextSpan.class::isInstance) + .map(ParagraphTextSpan.class::cast) + .toList(); + } + private static List texts(PlacedFragment fragment) { return ((ParagraphFragmentPayload) fragment.payload()).lines().stream() .map(ParagraphLine::text) diff --git a/qa/src/test/java/com/demcha/compose/document/api/TimelineColumnModelTest.java b/qa/src/test/java/com/demcha/compose/document/api/TimelineColumnModelTest.java new file mode 100644 index 000000000..e4ae5b69e --- /dev/null +++ b/qa/src/test/java/com/demcha/compose/document/api/TimelineColumnModelTest.java @@ -0,0 +1,226 @@ +package com.demcha.compose.document.api; + +import com.demcha.compose.GraphCompose; +import com.demcha.compose.document.dsl.TimelineBuilder; +import com.demcha.compose.document.dsl.TimelineMarker; +import com.demcha.compose.document.layout.LayoutGraph; +import com.demcha.compose.document.layout.PlacedFragment; +import com.demcha.compose.document.layout.PlacedNode; +import com.demcha.compose.document.style.DocumentColor; +import com.demcha.compose.document.style.DocumentInsets; +import com.demcha.compose.document.style.DocumentRowColumn; +import org.junit.jupiter.api.Test; + +import java.util.function.Consumer; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.within; + +/** + * What a three-column timeline can and cannot be asked for. + * + *

A row spaces every pair of its columns by one number. With a leading column that made + * three columns share two gaps of the same size, and the arithmetic then decided the leading + * width for the author: writing {@code x0} for where the columns start, {@code L} for the + * leading width and {@code A} for the axis, one gap {@code s} puts the rail at + * {@code x0 + L + s + A/2} and the content at {@code x0 + L + 2s + A}, so + * {@code L = (rail − x0) − (s + A/2)} whatever {@code A} and {@code s} are. A design that + * states where its dates are, where its rail is and where its copy starts — which is what a + * dated timeline is — could not have all three.

+ * + *

The gaps are separate now, so {@code L} is the caller's. Unset, the leading gap is the + * marker gap and the layout is the one-gap layout exactly, which is the first thing below.

+ */ +class TimelineColumnModelTest { + + private static final DocumentColor RAIL = DocumentColor.rgb(150, 158, 172); + private static final DocumentColor INK = DocumentColor.rgb(20, 40, 70); + private static final double MARGIN = 20.0; + private static final double AXIS = 10.0; + + @Test + void unsetTheLeadingGapIsTheMarkerGapAndNothingMoved() throws Exception { + // The compatibility half. x0 is the page margin plus the gutter, and with one gap + // the two positions are the formulae above — so this pins the layout a three-column + // timeline had before there were two gaps. + double gutter = 6.0; + double leading = 40.0; + double gap = 9.0; + LayoutGraph graph = timeline(t -> t + .markerOnRail() + .gutter(gutter) + .leadingColumn(DocumentRowColumn.fixed(leading)) + .axisWidth(AXIS) + .markerGap(gap)); + + double x0 = MARGIN + gutter; + assertThat(railX(graph)) + .as("rail at x0 + L + s + A/2") + .isEqualTo(x0 + leading + gap + AXIS / 2.0, within(1e-9)); + assertThat(node(graph, "Copy").placementX()) + .as("content at x0 + L + 2s + A") + .isEqualTo(x0 + leading + 2 * gap + AXIS, within(1e-9)); + } + + @Test + void theLeadingWidthIsFreeOnceTheTwoGapsAre() throws Exception { + // The capability, stated as the thing that was impossible: two different leading + // widths, the same rail and the same content start. Only the sum L + leadingGap is + // pinned by the geometry, so the author chooses how it is divided. + double gutter = 6.0; + double markerGap = 9.0; + LayoutGraph narrow = timeline(t -> t + .markerOnRail().gutter(gutter) + .leadingColumn(DocumentRowColumn.fixed(40.0)).leadingGap(19.0) + .axisWidth(AXIS).markerGap(markerGap)); + LayoutGraph wide = timeline(t -> t + .markerOnRail().gutter(gutter) + .leadingColumn(DocumentRowColumn.fixed(52.0)).leadingGap(7.0) + .axisWidth(AXIS).markerGap(markerGap)); + + assertThat(railX(wide)) + .as("a leading column 12pt wider, and the rail has not moved") + .isEqualTo(railX(narrow), within(1e-9)); + assertThat(node(wide, "Copy").placementX()) + .as("nor has the content") + .isEqualTo(node(narrow, "Copy").placementX(), within(1e-9)); + assertThat(node(wide, "Copy").placementWidth()) + .as("so the weighted content column measures the same too") + .isEqualTo(node(narrow, "Copy").placementWidth(), within(1e-9)); + } + + @Test + void theLeadingColumnIsTheWidthItsContentMeasuresAgainst() throws Exception { + // The point of wanting L free: what goes in the leading column has L to fit in. The + // same text in a column too narrow for it wraps to two lines and in a wide enough + // one does not — and that is the difference between a date set on one line and a + // date broken across two. + Consumer common = t -> t + .markerOnRail().gutter(6.0).axisWidth(AXIS).markerGap(9.0); + LayoutGraph tight = timeline(t -> { + common.accept(t); + t.leadingColumn(DocumentRowColumn.fixed(26.0)).leadingGap(33.0); + }); + LayoutGraph roomy = timeline(t -> { + common.accept(t); + t.leadingColumn(DocumentRowColumn.fixed(52.0)).leadingGap(7.0); + }); + + double oneLine = node(roomy, "Date").placementHeight(); + double twoLines = node(tight, "Date").placementHeight(); + assertThat(twoLines) + .as("the narrow column wraps the date, the wide one does not") + .isGreaterThan(oneLine * 1.5); + assertThat(railX(roomy)) + .as("and widening the column to stop that moved neither the rail") + .isEqualTo(railX(tight), within(1e-9)); + assertThat(node(roomy, "Copy").placementX()) + .as("nor the copy") + .isEqualTo(node(tight, "Copy").placementX(), within(1e-9)); + } + + @Test + void aTimelineWithNoLeadingColumnIsUntouched() throws Exception { + // Two columns share one gap and always did. This is the released layout and the + // leading gap has nothing to say about it. + double gutter = 6.0; + double gap = 9.0; + LayoutGraph plain = timelineWithoutLeading(t -> t + .markerOnRail().gutter(gutter).axisWidth(AXIS).markerGap(gap)); + LayoutGraph asked = timelineWithoutLeading(t -> t + .markerOnRail().gutter(gutter).axisWidth(AXIS).markerGap(gap).leadingGap(40.0)); + + double x0 = MARGIN + gutter; + assertThat(railX(plain)) + .as("rail at x0 + A/2") + .isEqualTo(x0 + AXIS / 2.0, within(1e-9)); + assertThat(railX(asked)) + .as("and a leading gap on a timeline with no leading column changes nothing") + .isEqualTo(railX(plain), within(1e-9)); + assertThat(node(asked, "Copy").placementX()) + .isEqualTo(node(plain, "Copy").placementX(), within(1e-9)); + } + + @Test + void aWeightedLeadingColumnStillResolvesAgainstTheRow() throws Exception { + // Fixed is what a measured design uses, but the column takes a weight too and the + // gaps must not stop it resolving: a weighted leading column plus two fixed gaps + // plus a fixed axis plus a weighted content share the row. + LayoutGraph graph = timeline(t -> t + .markerOnRail().gutter(6.0) + .leadingColumn(DocumentRowColumn.weight(0.25)).leadingGap(7.0) + .axisWidth(AXIS).markerGap(9.0)); + + PlacedNode copy = node(graph, "Copy"); + assertThat(node(graph, "Date").placementWidth()) + .as("the weighted leading column took a share of the row") + .isGreaterThan(0.0); + assertThat(copy.placementX()) + .as("and the content still starts right of the axis") + .isGreaterThan(railX(graph) + AXIS / 2.0); + assertThat(copy.placementWidth()).isGreaterThan(0.0); + } + + // --- helpers --------------------------------------------------------------- + + /** The rail's x, which is where the marker anchor put it. */ + private static double railX(LayoutGraph graph) { + return graph.fragments().stream() + .filter(f -> "@timeline-rail".equals(f.path())) + .mapToDouble(PlacedFragment::x) + .findFirst().orElseThrow(() -> new AssertionError("no rail")); + } + + private static PlacedNode node(LayoutGraph graph, String name) { + return graph.nodes().stream() + .filter(n -> name.equals(n.semanticName())) + .findFirst().orElseThrow(() -> new AssertionError("no node named " + name)); + } + + /** The same two entries with nothing in a leading column, which there isn't one of. */ + private static LayoutGraph timelineWithoutLeading(Consumer spec) + throws Exception { + try (DocumentSession session = GraphCompose.document() + .pageSize(320, 400).margin(DocumentInsets.of(MARGIN)).create()) { + session.pageFlow().addTimeline(t -> { + t.connector(RAIL, 1.5); + spec.accept(t); + t.entry(TimelineMarker.dot(8, INK), e -> e + .content(column -> column.addParagraph(p -> p + .name("Copy").text("The copy beside the marker.") + .margin(DocumentInsets.zero())))); + t.entry(TimelineMarker.dot(8, INK), e -> e + .content(column -> column.addParagraph(p -> p + .name("Copy2").text("More copy beside a marker.") + .margin(DocumentInsets.zero())))); + }).build(); + return session.layoutGraph(); + } + } + + /** Two entries, each with a named date in the leading column and named copy beside. */ + private static LayoutGraph timeline(Consumer spec) throws Exception { + try (DocumentSession session = GraphCompose.document() + .pageSize(320, 400).margin(DocumentInsets.of(MARGIN)).create()) { + session.pageFlow().addTimeline(t -> { + t.connector(RAIL, 1.5); + spec.accept(t); + t.entry(TimelineMarker.dot(8, INK), e -> e + .leading(column -> column.addParagraph(p -> p + .name("Date").text("2022 - Present") + .margin(DocumentInsets.zero()))) + .content(column -> column.addParagraph(p -> p + .name("Copy").text("The copy beside the marker.") + .margin(DocumentInsets.zero())))); + t.entry(TimelineMarker.dot(8, INK), e -> e + .leading(column -> column.addParagraph(p -> p + .name("Date2").text("2020 - 2022") + .margin(DocumentInsets.zero()))) + .content(column -> column.addParagraph(p -> p + .name("Copy2").text("More copy beside a marker.") + .margin(DocumentInsets.zero())))); + }).build(); + return session.layoutGraph(); + } + } +} diff --git a/qa/src/test/java/com/demcha/compose/document/api/TimelineCompatibilityTest.java b/qa/src/test/java/com/demcha/compose/document/api/TimelineCompatibilityTest.java index 2b70f78a8..1ee51e292 100644 --- a/qa/src/test/java/com/demcha/compose/document/api/TimelineCompatibilityTest.java +++ b/qa/src/test/java/com/demcha/compose/document/api/TimelineCompatibilityTest.java @@ -5,7 +5,7 @@ import com.demcha.compose.document.dsl.SectionBuilder; import com.demcha.compose.document.dsl.TimelineBuilder; import com.demcha.compose.document.dsl.TimelineMarker; -import com.demcha.compose.document.dsl.TimelineRailExtent; +import com.demcha.compose.document.dsl.TimelineRailEnd; import com.demcha.compose.document.layout.LayoutGraph; import com.demcha.compose.document.layout.PlacedFragment; import com.demcha.compose.document.layout.ResolvedLayoutAnchor; @@ -152,8 +152,8 @@ void everyRailFragmentBelongsToOnePageAndStaysInsideIt() { void nothingInTheDocumentMovesBecauseTheRailChoseADifferentExtent() { // The rail is drawing, not layout. Two graphs of one document, ENTRY_BOUNDS and // MARKER_TO_MARKER: the rail differs and every anchor, and the page count, do not. - LayoutGraph bounded = paginatedScene(TimelineRailExtent.ENTRY_BOUNDS); - LayoutGraph trimmed = paginatedScene(TimelineRailExtent.MARKER_TO_MARKER); + LayoutGraph bounded = paginatedScene(TimelineRailEnd.ENTRY_BOUND); + LayoutGraph trimmed = paginatedScene(TimelineRailEnd.MARKER); assertThat(trimmed.totalPages()).isEqualTo(bounded.totalPages()); assertThat(box(markers(trimmed))).as("markers").isEqualTo(box(markers(bounded))); @@ -386,11 +386,11 @@ private static LayoutGraph paginatedScene() { return paginatedScene(null); } - private static LayoutGraph paginatedScene(TimelineRailExtent extent) { + private static LayoutGraph paginatedScene(TimelineRailEnd both) { return timeline(320, 170, t -> { t.spacing(14); - if (extent != null) { - t.rail(rail -> rail.extent(extent)); + if (both != null) { + t.rail(rail -> rail.from(both).to(both)); } t.entry(TimelineMarker.dot(8, INK), e -> e.title("Runs on").body(longBody(30))) .entry(TimelineMarker.dot(8, INK), e -> e.title("And ends here")); diff --git a/qa/src/test/java/com/demcha/compose/document/api/TimelineLeadingColumnLayoutTest.java b/qa/src/test/java/com/demcha/compose/document/api/TimelineLeadingColumnLayoutTest.java index 6eb9cd399..67f2a7346 100644 --- a/qa/src/test/java/com/demcha/compose/document/api/TimelineLeadingColumnLayoutTest.java +++ b/qa/src/test/java/com/demcha/compose/document/api/TimelineLeadingColumnLayoutTest.java @@ -43,15 +43,19 @@ void everyEntrysColumnsStartAtTheSameXWhateverItsDateSays() throws Exception { LayoutGraph graph = timeline(DocumentRowColumn.fixed(56)); Map> byColumn = columnPositions(graph); - assertThat(byColumn).as("three columns, four entries").hasSize(3); + // Five columns: the leading one, the marker, the content, and a gap either side of + // the marker. Each gap is a column of its own because a row spaces every pair of + // its columns by one number, and the two gaps have to be free to differ. + assertThat(byColumn).as("five columns, four entries").hasSize(5); assertThat(byColumn.get(0)).as("every leading column").hasSize(4); byColumn.forEach((index, positions) -> assertThat(positions) .as("column %d starts at one x in every entry, not one per date length", index) .containsOnly(positions.get(0))); - // And they are three distinct columns, not one collapsed on top of another. - assertThat(List.of(byColumn.get(0).get(0), byColumn.get(1).get(0), byColumn.get(2).get(0))) + // And the three that carry something are distinct columns in order, not one + // collapsed on top of another. + assertThat(List.of(byColumn.get(0).get(0), byColumn.get(2).get(0), byColumn.get(4).get(0))) .isSorted() .doesNotHaveDuplicates(); } diff --git a/qa/src/test/java/com/demcha/compose/document/api/TimelineRailGeometryTest.java b/qa/src/test/java/com/demcha/compose/document/api/TimelineRailGeometryTest.java index 234fd0b31..85989784d 100644 --- a/qa/src/test/java/com/demcha/compose/document/api/TimelineRailGeometryTest.java +++ b/qa/src/test/java/com/demcha/compose/document/api/TimelineRailGeometryTest.java @@ -3,7 +3,7 @@ import com.demcha.compose.GraphCompose; import com.demcha.compose.document.dsl.TimelineBuilder; import com.demcha.compose.document.dsl.TimelineMarker; -import com.demcha.compose.document.dsl.TimelineRailExtent; +import com.demcha.compose.document.dsl.TimelineRailEnd; import com.demcha.compose.document.layout.LayoutGraph; import com.demcha.compose.document.layout.PlacedFragment; import com.demcha.compose.document.layout.ResolvedLayoutAnchor; @@ -38,6 +38,9 @@ class TimelineRailGeometryTest { private static final DocumentColor RAIL = DocumentColor.rgb(150, 158, 172); private static final DocumentColor INK = DocumentColor.rgb(20, 40, 70); + /** The margin {@link #timeline(double, double, Consumer)} sets, so the content band is known. */ + private static final double PAGE_MARGIN = 20.0; + // --- extent --------------------------------------------------------------- @Test @@ -77,7 +80,7 @@ void markerToMarkerStopsAtTheMarkersAndEntryBoundsDoesNot() throws Exception { PlacedFragment bounded = rails(timeline(320, 300, entries)).get(0); PlacedFragment betweenMarkers = rails(timeline(320, 300, t -> { - t.rail(rail -> rail.extent(TimelineRailExtent.MARKER_TO_MARKER)); + t.rail(rail -> rail.from(TimelineRailEnd.MARKER).to(TimelineRailEnd.MARKER)); entries.accept(t); })).get(0); @@ -95,7 +98,7 @@ void markerToMarkerStopsAtTheMarkersAndEntryBoundsDoesNot() throws Exception { @Test void markerToMarkerRunsBetweenTheAnchorPointsItIsNamedFor() throws Exception { LayoutGraph graph = timeline(320, 300, t -> t - .rail(rail -> rail.extent(TimelineRailExtent.MARKER_TO_MARKER)) + .rail(rail -> rail.from(TimelineRailEnd.MARKER).to(TimelineRailEnd.MARKER)) .entry(TimelineMarker.dot(8, INK), e -> e.title("First").body("Body one.")) .entry(TimelineMarker.dot(8, INK), e -> e.title("Second").body("Body two."))); @@ -112,7 +115,7 @@ void oneEntryWithMarkerToMarkerEmitsNoRailAtAll() throws Exception { // A rail of no length is not a shorter rail: nothing reaches a backend, rather // than a zero- or negative-height fragment for one to cope with. LayoutGraph graph = timeline(320, 300, t -> t - .rail(rail -> rail.extent(TimelineRailExtent.MARKER_TO_MARKER)) + .rail(rail -> rail.from(TimelineRailEnd.MARKER).to(TimelineRailEnd.MARKER)) .entry(TimelineMarker.dot(8, INK), e -> e.title("Only").body("Body."))); assertThat(rails(graph)).isEmpty(); @@ -127,13 +130,95 @@ void oneEntryWithEntryBoundsStillDrawsItsRail() throws Exception { .hasSize(1); } + // The timeline-bounds case went with the constant that named it. It was defined and not + // implemented, threw when asked for, and was indistinguishable from the entries' bound on + // one page — so with the ends chosen separately there is nothing for it to mean, and a + // throwing constant is not API worth releasing. + @Test - void timelineBoundsIsRejectedRatherThanResolvedToItsNeighbour() throws Exception { - assertThatIllegalArgumentException() - .isThrownBy(() -> timeline(320, 300, t -> t - .rail(rail -> rail.extent(TimelineRailExtent.TIMELINE_BOUNDS)) - .entry(TimelineMarker.dot(8, INK), e -> e.title("x")))) - .withMessageContaining("not implemented"); + void theFourEndCombinationsAreFourDifferentLines() throws Exception { + // The whole point of choosing the ends separately: the two mixed lines are the ones + // that could not be asked for before, and each shares exactly one end with each of + // the symmetric pair. + Consumer entries = t -> t + .spacing(14) + .entry(TimelineMarker.dot(8, INK), e -> e.title("First").body("Body one.")) + .entry(TimelineMarker.dot(8, INK), e -> e.title("Second").body("Body two.")); + + PlacedFragment boundToBound = railOf(entries, TimelineRailEnd.ENTRY_BOUND, + TimelineRailEnd.ENTRY_BOUND); + PlacedFragment markerToMarker = railOf(entries, TimelineRailEnd.MARKER, + TimelineRailEnd.MARKER); + PlacedFragment markerToBound = railOf(entries, TimelineRailEnd.MARKER, + TimelineRailEnd.ENTRY_BOUND); + PlacedFragment boundToMarker = railOf(entries, TimelineRailEnd.ENTRY_BOUND, + TimelineRailEnd.MARKER); + + // Each end is whichever of the two it was asked for, and nothing else changes it. + assertThat(top(markerToBound)) + .as("marker -> bound starts where marker -> marker starts") + .isEqualTo(top(markerToMarker), within(1e-9)); + assertThat(markerToBound.y()) + .as("and ends where bound -> bound ends") + .isEqualTo(boundToBound.y(), within(1e-9)); + assertThat(top(boundToMarker)) + .as("bound -> marker starts where bound -> bound starts") + .isEqualTo(top(boundToBound), within(1e-9)); + assertThat(boundToMarker.y()) + .as("and ends where marker -> marker ends") + .isEqualTo(markerToMarker.y(), within(1e-9)); + + // Four distinct lines, not two dressed up: the mixed pair is longer than + // marker-to-marker and shorter than bound-to-bound at one end each. + assertThat(markerToBound.height()).isGreaterThan(markerToMarker.height()); + assertThat(markerToBound.height()).isLessThan(boundToBound.height()); + assertThat(boundToMarker.height()).isGreaterThan(markerToMarker.height()); + assertThat(boundToMarker.height()).isLessThan(boundToBound.height()); + + // And no end moved the line sideways. + assertThat(List.of(markerToMarker.x(), markerToBound.x(), boundToMarker.x())) + .allSatisfy(x -> assertThat(x).isEqualTo(boundToBound.x(), within(1e-9))); + } + + @Test + void eachEndTrimsOnlyItsOwnPageWhenTheTimelineCrossesOne() throws Exception { + // Pagination, per end. The start trims the page the first marker is on and the end + // trims the page the last is on; a page holding neither is bounded by its entries at + // both ends whatever was asked for. marker -> bound is the case ProfessionalSidebar + // wants: begin at the first dot, run to the foot of the last entry. + Consumer entries = t -> { + t.spacing(10).keepEntriesTogether(); + for (int i = 0; i < 7; i++) { + String title = "Entry " + i; + t.entry(TimelineMarker.dot(8, INK), e -> e + .title(title).body("One body line of this entry.")); + } + }; + + LayoutGraph boundToBound = scene(220, entries, TimelineRailEnd.ENTRY_BOUND, + TimelineRailEnd.ENTRY_BOUND); + LayoutGraph markerToBound = scene(220, entries, TimelineRailEnd.MARKER, + TimelineRailEnd.ENTRY_BOUND); + assertThat(boundToBound.totalPages()).isEqualTo(2); + assertThat(markerToBound.totalPages()).isEqualTo(2); + + PlacedFragment firstOfBoth = railOnPage(boundToBound, 0); + PlacedFragment firstOfMixed = railOnPage(markerToBound, 0); + PlacedFragment lastOfBoth = railOnPage(boundToBound, 1); + PlacedFragment lastOfMixed = railOnPage(markerToBound, 1); + + assertThat(top(firstOfMixed)) + .as("page 0 is trimmed to the first marker, because the start asked for it") + .isLessThan(top(firstOfBoth)); + assertThat(firstOfMixed.y()) + .as("but its foot is the page's own entries, not an end") + .isEqualTo(firstOfBoth.y(), within(1e-9)); + assertThat(top(lastOfMixed)) + .as("page 1 is not trimmed at the top: the first marker is not on it") + .isEqualTo(top(lastOfBoth), within(1e-9)); + assertThat(lastOfMixed.y()) + .as("nor at the foot, because the end asked for the entries' bound") + .isEqualTo(lastOfBoth.y(), within(1e-9)); } // --- the marker anchor, which is a different question ---------------------- @@ -253,18 +338,18 @@ void theLeftEdgeAnchorStillLeavesMarkersWhereTheyHaveAlwaysBeen() throws Excepti void markerOnRailHoldsForEveryShapeAndForBothExtents() throws Exception { // The extent decides the two ends and must not touch the x. Same three markers, // both extents, one answer. - for (TimelineRailExtent extent : List.of(TimelineRailExtent.ENTRY_BOUNDS, - TimelineRailExtent.MARKER_TO_MARKER)) { + for (TimelineRailEnd both : List.of(TimelineRailEnd.ENTRY_BOUND, + TimelineRailEnd.MARKER)) { LayoutGraph graph = timeline(360, 320, t -> t .markerOnRail() - .rail(rail -> rail.extent(extent)) + .rail(rail -> rail.from(both).to(both)) .axisWidth(28) .entry(TimelineMarker.dot(12, INK), e -> e.title("Dot").body("Body.")) .entry(TimelineMarker.square(12, INK), e -> e.title("Square").body("Body."))); double railX = rails(graph).get(0).x(); assertThat(markerAnchors(graph)).allSatisfy(marker -> assertThat(railX) - .as("%s must not move the rail sideways", extent) + .as("%s must not move the rail sideways", both) .isEqualTo(marker.x() + marker.width() / 2, within(1e-9))); } } @@ -292,7 +377,7 @@ void markerOnRailWithALeadingColumnStillPutsTheMarkerOnTheLine() throws Exceptio void markerOnRailWithMarkerToMarkerRunsBetweenTheMarkerCentres() throws Exception { LayoutGraph graph = timeline(360, 320, t -> t .markerOnRail() - .rail(rail -> rail.extent(TimelineRailExtent.MARKER_TO_MARKER)) + .rail(rail -> rail.from(TimelineRailEnd.MARKER).to(TimelineRailEnd.MARKER)) .axisWidth(28) .entry(TimelineMarker.dot(12, INK), e -> e.title("First").body("Body.")) .entry(TimelineMarker.dot(12, INK), e -> e.title("Second").body("Body."))); @@ -397,6 +482,61 @@ void aTimelineCrossingPagesGivesOneFragmentPerPageBoundedByThatPage() throws Exc .hasSize(graph.totalPages()); } + @Test + void aRailStopsAtTheLastEntryOnThePageAndNotAtTheBandBelowIt() throws Exception { + // The case the test above cannot see. Its entry is splittable, so it genuinely + // occupies the foot of the page and the rail is right to reach it. Here every entry + // is held whole, and the one that will not fit moves — leaving a gap at the foot of + // page 0 that nothing occupies. The rail has to stop where the entries stop. + LayoutGraph graph = timeline(320, 220, t -> { + t.spacing(10).keepEntriesTogether(); + for (int i = 0; i < 7; i++) { + String title = "Entry " + i; + t.entry(TimelineMarker.dot(8, INK), e -> e + .title(title).body("One body line of this entry.")); + } + }); + + assertThat(graph.totalPages()).isEqualTo(2); + List rails = rails(graph); + assertThat(rails).as("one fragment per page").hasSize(2); + + PlacedFragment first = rails.stream() + .filter(r -> r.pageIndex() == 0).findFirst().orElseThrow(); + List onFirstPage = entryAnchors(graph).stream() + .filter(a -> a.pageIndex() == 0).toList(); + double lastEntryFoot = onFirstPage.stream() + .mapToDouble(ResolvedLayoutAnchor::y).min().orElseThrow(); + + assertThat(first.y()) + .as("the rail ends at the last entry the page actually holds") + .isEqualTo(lastEntryFoot, within(1e-9)); + + // The assertion that tells an entry-derived extent from a band-derived one. + // Comparing the rail against the anchors alone cannot: when an entry that moved + // leaves a slice behind on the page it skipped, the rail follows it down and still + // agrees with them. The page's own content band is the independent reference — the + // margin this helper sets — and a gap has to remain between it and the rail's foot. + assertThat(first.y()) + .as("and stops short of the band, which is where it used to run to") + .isGreaterThan(PAGE_MARGIN + 1.0); + + // Nothing of the entry that moved is left on the page it skipped. + assertThat(onFirstPage).as("only the entries that fit").hasSize(5); + assertThat(entryAnchors(graph).stream().filter(a -> a.pageIndex() == 1).toList()) + .as("and the rest are on the page they moved to").hasSize(2); + + // The continuation page still opens at its first entry: an intermediate page's rail + // is bounded by the entries on it at both ends, head as well as foot. + PlacedFragment second = rails.stream() + .filter(r -> r.pageIndex() == 1).findFirst().orElseThrow(); + double secondTop = entryAnchors(graph).stream().filter(a -> a.pageIndex() == 1) + .mapToDouble(a -> a.pointY(1.0)).max().orElseThrow(); + assertThat(second.y() + second.height()) + .as("page 1 starts at its first entry") + .isEqualTo(secondTop, within(1e-9)); + } + @Test void theRailIsDrawnBeneathTheMarkersItPassesUnder() throws Exception { // There is no z in this engine — draw order is list order — so "under" is a @@ -449,6 +589,33 @@ private static List rails(LayoutGraph graph) { return graph.fragments().stream().filter(f -> "@timeline-rail".equals(f.path())).toList(); } + /** A rail's top, which is the edge the start end decides. */ + private static double top(PlacedFragment rail) { + return rail.y() + rail.height(); + } + + /** The one rail of a single-page scene with those two ends. */ + private static PlacedFragment railOf(Consumer entries, + TimelineRailEnd start, + TimelineRailEnd end) throws Exception { + return rails(scene(300, entries, start, end)).get(0); + } + + private static PlacedFragment railOnPage(LayoutGraph graph, int page) { + return rails(graph).stream().filter(r -> r.pageIndex() == page) + .findFirst().orElseThrow(() -> new AssertionError("no rail on page " + page)); + } + + private static LayoutGraph scene(double height, + Consumer entries, + TimelineRailEnd start, + TimelineRailEnd end) throws Exception { + return timeline(320, height, t -> { + t.rail(rail -> rail.from(start).to(end)); + entries.accept(t); + }); + } + private static List entryAnchors(LayoutGraph graph) { return ResolvedLayoutMetadata.from(graph).anchors().stream() .filter(a -> "ENTRY".equals(a.id().kind().toString())).toList(); diff --git a/qa/src/test/java/com/demcha/compose/document/api/TimelineVisualScenarioGeometryTest.java b/qa/src/test/java/com/demcha/compose/document/api/TimelineVisualScenarioGeometryTest.java index b0f365848..6c7330fea 100644 --- a/qa/src/test/java/com/demcha/compose/document/api/TimelineVisualScenarioGeometryTest.java +++ b/qa/src/test/java/com/demcha/compose/document/api/TimelineVisualScenarioGeometryTest.java @@ -3,7 +3,7 @@ import com.demcha.compose.GraphCompose; import com.demcha.compose.document.dsl.TimelineBuilder; import com.demcha.compose.document.dsl.TimelineMarker; -import com.demcha.compose.document.dsl.TimelineRailExtent; +import com.demcha.compose.document.dsl.TimelineRailEnd; import com.demcha.compose.document.layout.LayoutGraph; import com.demcha.compose.document.layout.PlacedFragment; import com.demcha.compose.document.layout.ResolvedLayoutAnchor; @@ -68,18 +68,18 @@ void oneTimelineHasOneRailXHoweverManyPagesItCrossesAndWhicheverExtent() throws // The rail is one logical line. Across pages it arrives as several fragments, and // "one line" means those fragments agree about x — including under MARKER_TO_MARKER, // which changes both ends on the outer pages and must change nothing else. - for (TimelineRailExtent extent : List.of(TimelineRailExtent.ENTRY_BOUNDS, - TimelineRailExtent.MARKER_TO_MARKER)) { - LayoutGraph graph = paginated(extent); + for (TimelineRailEnd both : List.of(TimelineRailEnd.ENTRY_BOUND, + TimelineRailEnd.MARKER)) { + LayoutGraph graph = paginated(both); assertThat(graph.totalPages()) .as("the premise: it does cross pages") .isGreaterThanOrEqualTo(3); assertThat(rails(graph)) - .as("%s: one fragment per page", extent) + .as("%s: one fragment per page", both) .hasSize(graph.totalPages()); assertThat(rails(graph).stream().map(PlacedFragment::x).distinct()) - .as("%s: and one x between them", extent) + .as("%s: and one x between them", both) .hasSize(1); } } @@ -89,7 +89,7 @@ void noRailFragmentReachesOutsideThePageItIsOn() throws Exception { // Scenario 7's hard invariant. A rail derived from anchors could in principle be // handed a box belonging to another page and paint into the margin; every fragment // is checked against the band its own page actually has. - LayoutGraph graph = paginated(TimelineRailExtent.ENTRY_BOUNDS); + LayoutGraph graph = paginated(TimelineRailEnd.ENTRY_BOUND); double width = graph.canvas().width(); double height = graph.canvas().height(); assertThat(graph.canvas().innerHeight()) @@ -114,8 +114,8 @@ void theTwoExtentsDifferInWhereTheyStopAndInNothingElse() throws Exception { // draw: the same page, the same three entries, one argument different. Scenarios 5 // and 6, asserted against each other so that "shorter" is a comparison and not an // impression. - PlacedFragment bounded = rails(extentScene(TimelineRailExtent.ENTRY_BOUNDS)).get(0); - LayoutGraph trimmed = extentScene(TimelineRailExtent.MARKER_TO_MARKER); + PlacedFragment bounded = rails(extentScene(TimelineRailEnd.ENTRY_BOUND)).get(0); + LayoutGraph trimmed = extentScene(TimelineRailEnd.MARKER); PlacedFragment betweenMarkers = rails(trimmed).get(0); List markers = markerAnchors(trimmed); @@ -138,7 +138,7 @@ void entryBoundsCoversTheSpacingBetweenEntriesOfVeryDifferentHeights() throws Ex // 16pt of spacing between them — and one unbroken line, because an entry's spacing // is padding inside its own slice rather than a gap between two of them. If it were // a gap, this is the scene where a rail assembled per entry would show it. - LayoutGraph graph = extentScene(TimelineRailExtent.ENTRY_BOUNDS); + LayoutGraph graph = extentScene(TimelineRailEnd.ENTRY_BOUND); List entries = entryAnchors(graph); PlacedFragment rail = rails(graph).get(0); @@ -164,7 +164,7 @@ void markerToMarkerAcrossPagesStartsAtTheFirstMarkerAndEndsAtTheLast() throws Ex // about the other. The first page is trimmed at the top, the last at the bottom, // and the page between them carries no marker at all, so it is trimmed at neither // end and runs the whole band its entry occupies. - LayoutGraph graph = paginated(TimelineRailExtent.MARKER_TO_MARKER); + LayoutGraph graph = paginated(TimelineRailEnd.MARKER); List rails = rails(graph); List markers = markerAnchors(graph); List entries = entryAnchors(graph); @@ -291,10 +291,10 @@ void twoTimelinesWithDifferentAnchorsDoNotShareARail() throws Exception { // --- scenes ------------------------------------------------------------------ /** The scene both extent baselines draw; they differ by this argument and nothing else. */ - private static LayoutGraph extentScene(TimelineRailExtent extent) throws Exception { + private static LayoutGraph extentScene(TimelineRailEnd both) throws Exception { return timeline(300, 250, t -> t .spacing(16) - .rail(rail -> rail.extent(extent)) + .rail(rail -> rail.from(both).to(both)) .entry(TimelineMarker.dot(9, INK), e -> e .title("Tall entry").meta("2023 - Present") .body("A body long enough to run to three lines on a page this " @@ -305,12 +305,12 @@ private static LayoutGraph extentScene(TimelineRailExtent extent) throws Excepti } /** The scene timeline-dsl/paginated-marker-to-marker draws: two entries, three pages. */ - private static LayoutGraph paginated(TimelineRailExtent extent) throws Exception { + private static LayoutGraph paginated(TimelineRailEnd both) throws Exception { return timeline(300, 150, t -> t .spacing(14) .markerOnRail() .axisWidth(24) - .rail(rail -> rail.extent(extent)) + .rail(rail -> rail.from(both).to(both)) .entry(TimelineMarker.dot(10, INK), e -> e.title("Runs on").body(longBody())) .entry(TimelineMarker.dot(10, INK), e -> e.title("And ends here"))); } diff --git a/qa/src/test/java/com/demcha/compose/document/dsl/BoxExtentAcrossPagesTest.java b/qa/src/test/java/com/demcha/compose/document/dsl/BoxExtentAcrossPagesTest.java new file mode 100644 index 000000000..6d133fa53 --- /dev/null +++ b/qa/src/test/java/com/demcha/compose/document/dsl/BoxExtentAcrossPagesTest.java @@ -0,0 +1,138 @@ +package com.demcha.compose.document.dsl; + +import com.demcha.compose.GraphCompose; +import com.demcha.compose.document.api.DocumentSession; +import com.demcha.compose.document.layout.LayoutGraph; +import com.demcha.compose.document.layout.PlacedFragment; +import com.demcha.compose.document.layout.PlacedNode; +import com.demcha.compose.document.style.DocumentColor; +import com.demcha.compose.document.style.DocumentInsets; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * A box does not claim a page none of its content reaches. + * + *

A node's box is recorded where the flow stands when the node is compiled, and its + * children are placed afterwards. When the first thing inside it cannot start in the space + * left — because it asked to be kept whole, or because it is indivisible and too tall — the + * child moves to the next page and the box is left spanning a page it holds nothing on. Every + * consumer of that box then believes it: a section's accent border paints a stub at the foot of + * the page beside nothing at all, and anything deriving an extent from the box, a timeline's + * rail included, runs down to the bottom margin.

+ * + *

So the box has to open where its first indivisible unit can actually start. That is the + * same rule {@code keepTogether()} already applies to a whole node, asked of its leading unit + * instead — and it fires only in the case that was broken, because a box whose first unit does + * fit is unaffected.

+ * + *

The last test is the other half of the contract: a box whose content genuinely does span + * pages must keep a slice on every one of them. The rule must not turn a real span into a + * relocation.

+ */ +class BoxExtentAcrossPagesTest { + + private static final DocumentColor EDGE = DocumentColor.rgb(200, 60, 60); + + @Test + void aBoxDoesNotOpenOnAPageItsFirstUnitCannotStartOn() throws Exception { + LayoutGraph graph = hostWhoseChildIsKeptWhole(); + + PlacedNode host = node(graph, "Host"); + assertThat(host.startPage()) + .as("the host opens where its content is, not where the flow happened to stand") + .isEqualTo(1); + assertThat(host.endPage()) + .as("and it does not span a page it holds nothing on") + .isEqualTo(1); + + // The border is the visible half of it: two slices means a stub painted at the foot + // of page 0 with no content beside it. + assertThat(fragments(graph, "Host")) + .as("one slice, on the page the content reached") + .hasSize(1); + assertThat(fragments(graph, "Host").get(0).pageIndex()).isEqualTo(1); + } + + @Test + void theChildStillLandsWhereItDidAndNothingIsLost() throws Exception { + // The fix moves the box, not the content: the child was already on page 1. + LayoutGraph graph = hostWhoseChildIsKeptWhole(); + PlacedNode child = node(graph, "KeptWhole"); + assertThat(child.startPage()).isEqualTo(1); + assertThat(child.endPage()).isEqualTo(1); + assertThat(graph.totalPages()).isEqualTo(2); + } + + @Test + void aBoxWhoseContentGenuinelySpansKeepsASliceOnEveryPage() throws Exception { + // The other half of the contract. Nothing here asks to be kept whole and the + // content is splittable, so the host really does cross the boundary and has to + // keep saying so on both pages. + LayoutGraph graph; + try (DocumentSession session = GraphCompose.document() + .pageSize(320, 220).margin(DocumentInsets.of(20)).create()) { + PageFlowBuilder flow = session.pageFlow(); + flow.addSection("Filler", block -> { + for (int i = 0; i < 8; i++) { + block.addParagraph(p -> p.text("Filler line.").margin(DocumentInsets.zero())); + } + }); + flow.addSection("Spanner", block -> { + block.accentLeft(EDGE, 1.5); + for (int i = 0; i < 12; i++) { + block.addParagraph(p -> p.text("Spanning line.").margin(DocumentInsets.zero())); + } + }); + flow.build(); + graph = session.layoutGraph(); + } + + PlacedNode spanner = node(graph, "Spanner"); + assertThat(spanner.startPage()).as("it starts where the flow stood").isEqualTo(0); + assertThat(spanner.endPage()).as("and genuinely crosses").isEqualTo(1); + assertThat(fragments(graph, "Spanner")) + .as("a slice on each page it crosses, so the border draws on both") + .hasSize(2); + } + + /** A bordered host with no keep-together of its own, holding one child that has it. */ + private static LayoutGraph hostWhoseChildIsKeptWhole() throws Exception { + try (DocumentSession session = GraphCompose.document() + .pageSize(320, 220).margin(DocumentInsets.of(20)).create()) { + PageFlowBuilder flow = session.pageFlow(); + flow.addSection("Filler", block -> { + for (int i = 0; i < 12; i++) { + block.addParagraph(p -> p.text("Filler line.").margin(DocumentInsets.zero())); + } + }); + flow.addSection("Host", block -> { + block.accentLeft(EDGE, 1.5); + block.addSection("KeptWhole", inner -> { + inner.keepTogether(); + for (int i = 0; i < 4; i++) { + inner.addParagraph(p -> p.text("Inner line.") + .margin(DocumentInsets.zero())); + } + }); + }); + flow.build(); + return session.layoutGraph(); + } + } + + private static PlacedNode node(LayoutGraph graph, String name) { + return graph.nodes().stream() + .filter(n -> name.equals(n.semanticName())) + .findFirst().orElseThrow(() -> new AssertionError("no node named " + name)); + } + + private static List fragments(LayoutGraph graph, String name) { + return graph.fragments().stream() + .filter(f -> f.path() != null && f.path().endsWith(name + "[1]")) + .toList(); + } +} diff --git a/qa/src/test/java/com/demcha/compose/document/dsl/SectionKeepTogetherTest.java b/qa/src/test/java/com/demcha/compose/document/dsl/SectionKeepTogetherTest.java index 8027761df..f815c34c3 100644 --- a/qa/src/test/java/com/demcha/compose/document/dsl/SectionKeepTogetherTest.java +++ b/qa/src/test/java/com/demcha/compose/document/dsl/SectionKeepTogetherTest.java @@ -126,10 +126,15 @@ void keepTogetherSectionTallerThanAPageStillFlows() { PlacedNode oversized = document.layoutGraph().nodes().stream() .filter(n -> "Oversized".equals(n.semanticName())) .findFirst().orElseThrow(); - // The keep-together request is ignored for a block taller than a - // full page: the section starts in the remaining space on page 0 - // (no pointless relocation) and flows across the boundary. - assertThat(oversized.startPage()).isEqualTo(0); + // The keep-together request is ignored for a block taller than a full + // page: the section cannot relocate whole, so it flows across the + // boundary. What it does NOT do is claim page 0. Its first child is a + // 100pt shape and 98pt remain there, so nothing of it was ever going on + // that page — measured either way, all five shapes sit at the same y on + // pages 1 and 2 and the document is three pages long. Starting the box + // on page 0 only made it span a page it held nothing on, which is what + // painted a section's accent beside empty space. + assertThat(oversized.startPage()).isEqualTo(1); assertThat(oversized.endPage()).isGreaterThan(oversized.startPage()); } catch (Exception e) { throw new RuntimeException(e); diff --git a/qa/src/test/java/com/demcha/compose/document/templates/core/identity/ContactUriTest.java b/qa/src/test/java/com/demcha/compose/document/templates/core/identity/ContactUriTest.java new file mode 100644 index 000000000..abc2fa454 --- /dev/null +++ b/qa/src/test/java/com/demcha/compose/document/templates/core/identity/ContactUriTest.java @@ -0,0 +1,97 @@ +package com.demcha.compose.document.templates.core.identity; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Pins the rules {@link ContactUri} exists for, starting with the one it was + * written for: a printed number and the number a device dials are not the same + * string. + * + *

Every preset in every family that draws a contact routes through it, so + * the rules are asserted here once rather than through each of their renders.

+ */ +class ContactUriTest { + + @Test + void aTrunkPrefixIsDroppedBecauseAnInternationalCallerOmitsIt() { + assertThat(ContactUri.tel("+44 (0)20 7946 0832")).isEqualTo("tel:+442079460832"); + assertThat(ContactUri.tel("+44 (0) 20 3966 1900")).isEqualTo("tel:+442039661900"); + } + + @Test + void aParenthesisedAreaCodeIsNotATrunkPrefixAndStays() { + // Only an all-zero group goes. A US area code in brackets is part of + // the number, and dropping it would dial somewhere else entirely. + assertThat(ContactUri.tel("+1 (415) 555 7842")).isEqualTo("tel:+14155557842"); + } + + @Test + void aNumberWithoutAPlusKeepsItsLocalForm() { + assertThat(ContactUri.tel("020 7946 0832")).isEqualTo("tel:02079460832"); + } + + @Test + void separatorsAndSpacingDoNotReachTheTarget() { + assertThat(ContactUri.tel("+61 402-938-209")).isEqualTo("tel:+61402938209"); + } + + @Test + void textWithNoDigitsIsNotDialable() { + assertThat(ContactUri.tel("ask reception")).isNull(); + assertThat(ContactUri.tel("")).isNull(); + assertThat(ContactUri.tel(" ")).isNull(); + assertThat(ContactUri.tel(null)).isNull(); + } + + @Test + void theLinkFormMirrorsTheTargetForm() { + assertThat(ContactUri.telLink("+44 (0)20 7946 0832")).isNotNull(); + assertThat(ContactUri.telLink("ask reception")).isNull(); + assertThat(ContactUri.telLink(null)).isNull(); + } + + @Test + void anAddressIsMailedAndASiteIsOpened() { + assertThat(ContactUri.mailLink(" billing@example.com ").uri()) + .isEqualTo("mailto:billing@example.com"); + assertThat(ContactUri.webLink("example.com/business").uri()) + .isEqualTo("https://example.com/business"); + assertThat(ContactUri.webLink("http://example.com").uri()) + .isEqualTo("http://example.com"); + assertThat(ContactUri.mailLink(" ")).isNull(); + assertThat(ContactUri.webLink(null)).isNull(); + } + + @Test + void aChannelIsReadFromTheShapeOfWhatItPrints() { + // A foot lists its channels without saying which is which, because a + // reader can see it. This reads the same thing back. + assertThat(ContactUri.channelLink("business@example.com").uri()) + .isEqualTo("mailto:business@example.com"); + assertThat(ContactUri.channelLink("+44 20 3322 8352").uri()) + .isEqualTo("tel:+442033228352"); + assertThat(ContactUri.channelLink("example.com/business").uri()) + .isEqualTo("https://example.com/business"); + assertThat(ContactUri.channelLink(" ")).isNull(); + } + + @Test + void aSiteCarryingDigitsIsStillASite() { + // The dialable test asks for nine digits AND a mostly-numeric string, + // so a domain with a year in it does not become a phone number. + assertThat(ContactUri.channelLink("example2026.com").uri()) + .isEqualTo("https://example2026.com"); + } + + @Test + void proseWhereAChannelWasExpectedLosesItsLinkRatherThanThePage() { + // A notice, a name, a line of prose can all reach a channel field. + // DocumentLinkOptions refuses a string that is not a URI, and a contact + // line is not the place to find that out. + assertThat(ContactUri.channelLink("Confidential — for the addressee only")).isNull(); + assertThat(ContactUri.webLink("ask at the bar")).isNull(); + assertThat(ContactUri.mailLink("Jane Doe ")).isNull(); + } +} diff --git a/qa/src/test/java/com/demcha/compose/document/templates/core/identity/SvgGlyphFileTest.java b/qa/src/test/java/com/demcha/compose/document/templates/core/identity/SvgGlyphFileTest.java new file mode 100644 index 000000000..24f2d8b1c --- /dev/null +++ b/qa/src/test/java/com/demcha/compose/document/templates/core/identity/SvgGlyphFileTest.java @@ -0,0 +1,85 @@ +package com.demcha.compose.document.templates.core.identity; + +import com.demcha.compose.document.style.ShapeOutline; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import java.nio.file.Files; +import java.nio.file.Path; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +/** + * {@link SvgGlyph#fromFile(java.nio.file.Path)} — the variant that loads a mark + * a caller supplies rather than one a template ships with. + */ +class SvgGlyphFileTest { + + private static final String SQUARE = """ + + + + """; + + /** Same drawing in a frame twice as wide — a different aspect ratio to detect. */ + private static final String WIDE = """ + + + + """; + + @Test + void loadsAGlyphFromDisk(@TempDir Path dir) throws Exception { + Path file = Files.writeString(dir.resolve("mark.svg"), SQUARE); + + SvgGlyph glyph = SvgGlyph.fromFile(file); + + assertThat(glyph.aspectRatio()).isCloseTo(1.0, org.assertj.core.data.Offset.offset(0.01)); + ShapeOutline outline = glyph.outline(24); + assertThat(outline).isNotNull(); + } + + @Test + void rereadsTheFileOnEveryCall(@TempDir Path dir) throws Exception { + // The classpath variant caches forever because a jar entry cannot change. + // A file can: a caller who replaces the logo must not keep getting the old + // one for the life of the JVM. + Path file = dir.resolve("mark.svg"); + Files.writeString(file, SQUARE); + assertThat(SvgGlyph.fromFile(file).aspectRatio()) + .isCloseTo(1.0, org.assertj.core.data.Offset.offset(0.01)); + + Files.writeString(file, WIDE); + assertThat(SvgGlyph.fromFile(file).aspectRatio()) + .isCloseTo(2.0, org.assertj.core.data.Offset.offset(0.01)); + } + + @Test + void reportsAMissingFileByName(@TempDir Path dir) { + Path missing = dir.resolve("nowhere.svg"); + + assertThatThrownBy(() -> SvgGlyph.fromFile(missing)) + .isInstanceOf(IllegalStateException.class) + .hasMessageContaining("nowhere.svg"); + } + + @Test + void rejectsANullPath() { + assertThatThrownBy(() -> SvgGlyph.fromFile(null)) + .isInstanceOf(NullPointerException.class) + .hasMessageContaining("file"); + } + + @Test + void refusesAnSvgWithNothingToDraw(@TempDir Path dir) throws Exception { + // Fails loudly rather than yielding a glyph that renders as nothing — the + // rejection comes from the SVG reader, before the flattening step. + Path file = Files.writeString(dir.resolve("empty.svg"), + ""); + + assertThatThrownBy(() -> SvgGlyph.fromFile(file)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("no drawable geometry"); + } +} diff --git a/qa/src/test/java/com/demcha/compose/document/templates/cv/data/CvEntryPlaceAndIconTest.java b/qa/src/test/java/com/demcha/compose/document/templates/cv/data/CvEntryPlaceAndIconTest.java new file mode 100644 index 000000000..dff48fb20 --- /dev/null +++ b/qa/src/test/java/com/demcha/compose/document/templates/cv/data/CvEntryPlaceAndIconTest.java @@ -0,0 +1,109 @@ +package com.demcha.compose.document.templates.cv.data; + +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +/** + * Pins the compatibility promise made each time {@link CvEntry} grew: the + * constructors that predate the location, the mark and the link are still + * there and still mean what they meant, so a caller written against any of + * them keeps compiling and linking. + */ +class CvEntryPlaceAndIconTest { + + @Test + void theConstructorThatPredatesThemLeavesThemAllBlank() { + CvEntry entry = new CvEntry("Engineer", "Acme", "2021", "Did the work."); + assertThat(entry.place()).isEmpty(); + assertThat(entry.icon()).isEmpty(); + assertThat(entry.link()).isEmpty(); + assertThat(entry.subtitle()).isEqualTo("Acme"); + } + + @Test + void theConstructorThatPredatesTheLinkLeavesItBlank() { + CvEntry entry = new CvEntry("Engineer", "Acme", "2021", "Body", "Berlin", "cart"); + assertThat(entry.link()).isEmpty(); + assertThat(entry.place()).isEqualTo("Berlin"); + assertThat(entry.icon()).isEqualTo("cart"); + } + + @Test + void nullsNormalizeToBlank() { + CvEntry entry = new CvEntry("Engineer", "Acme", "2021", "Body", null, null, null); + assertThat(entry.place()).isEmpty(); + assertThat(entry.icon()).isEmpty(); + assertThat(entry.link()).isEmpty(); + } + + @Test + void theOriginalFieldsStillRejectNull() { + assertThatThrownBy(() -> + new CvEntry("Engineer", null, "2021", "Body", "Berlin", "cart", "")) + .isInstanceOf(NullPointerException.class) + .hasMessageContaining("subtitle"); + } + + @Test + void aBlankTitleIsStillRejected() { + assertThatThrownBy(() -> new CvEntry(" ", "Acme", "2021", "Body", "", "", "")) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("title"); + } + + @Test + void theBuilderCarriesEveryFieldThrough() { + CvEntry entry = CvEntry.builder("Ledgerkit") + .subtitle("Java, PostgreSQL") + .date("2024") + .body("An open-source ledger.") + .place("Remote") + .icon("cart") + .link("https://example.com/ledgerkit") + .build(); + assertThat(entry).isEqualTo(new CvEntry("Ledgerkit", "Java, PostgreSQL", "2024", + "An open-source ledger.", "Remote", "cart", + "https://example.com/ledgerkit")); + } + + @Test + void theBuilderLeavesWhatItIsNotToldBlank() { + CvEntry entry = CvEntry.builder("Ledgerkit").build(); + assertThat(entry.subtitle()).isEmpty(); + assertThat(entry.date()).isEmpty(); + assertThat(entry.body()).isEmpty(); + assertThat(entry.place()).isEmpty(); + assertThat(entry.icon()).isEmpty(); + assertThat(entry.link()).isEmpty(); + } + + @Test + void theBuilderJoinsABodyGivenAsLines() { + // The presets that draw a bulleted entry read one bullet per line, so + // the list form saves every caller the same String.join. + CvEntry entry = CvEntry.builder("Engineer") + .body(List.of("Shipped a thing.", "Shipped another.")) + .build(); + assertThat(entry.body()).isEqualTo("Shipped a thing.\nShipped another."); + } + + @Test + void theBuilderTreatsNullsAsBlank() { + CvEntry entry = CvEntry.builder("Engineer") + .subtitle(null) + .date(null) + .body((String) null) + .place(null) + .icon(null) + .link(null) + .build(); + assertThat(entry.subtitle()).isEmpty(); + assertThat(entry.body()).isEmpty(); + assertThat(entry.place()).isEmpty(); + assertThat(entry.link()).isEmpty(); + } +} diff --git a/qa/src/test/java/com/demcha/compose/document/templates/cv/data/CvIdentityPortraitTest.java b/qa/src/test/java/com/demcha/compose/document/templates/cv/data/CvIdentityPortraitTest.java new file mode 100644 index 000000000..4a275da64 --- /dev/null +++ b/qa/src/test/java/com/demcha/compose/document/templates/cv/data/CvIdentityPortraitTest.java @@ -0,0 +1,75 @@ +package com.demcha.compose.document.templates.cv.data; + +import com.demcha.compose.document.image.DocumentImageData; +import com.demcha.compose.document.templates.core.identity.Contact; +import com.demcha.compose.document.templates.core.identity.Link; +import org.junit.jupiter.api.Test; + +import java.util.List; +import java.util.Optional; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Pins the compatibility promise made when {@link CvIdentity} grew a + * portrait: the constructors that predate the component are still there and + * still mean what they meant, so a caller written against the four-argument + * form keeps compiling and linking. + */ +class CvIdentityPortraitTest { + + private static final Contact CONTACT = + new Contact("+44 20 7946 0000", "ada@example.com", "London, UK"); + + @Test + void theConstructorThatPredatesThePortraitLeavesItEmpty() { + CvIdentity identity = new CvIdentity( + CvName.of("Ada", "Lovelace"), "Analyst", CONTACT, + List.of(new Link("site", "https://example.com"))); + assertThat(identity.portrait()).isEmpty(); + assertThat(identity.jobTitle()).isEqualTo("Analyst"); + } + + @Test + void theConstructorThatPredatesTheJobTitleLeavesBothEmpty() { + CvIdentity identity = new CvIdentity( + CvName.of("Ada", "Lovelace"), CONTACT, List.of()); + assertThat(identity.portrait()).isEmpty(); + assertThat(identity.jobTitle()).isEmpty(); + } + + @Test + void aNullPortraitNormalizesToAbsent() { + CvIdentity identity = new CvIdentity( + CvName.of("Ada", "Lovelace"), "Analyst", CONTACT, List.of(), null); + assertThat(identity.portrait()).isEmpty(); + } + + @Test + void theBuilderCarriesThePortraitThrough() { + DocumentImageData image = DocumentImageData.fromBytes(onePixelPng()); + CvIdentity identity = CvIdentity.builder() + .name("Ada", "Lovelace") + .contact(CONTACT) + .portrait(image) + .build(); + assertThat(identity.portrait()).isEqualTo(Optional.of(image)); + } + + @Test + void theBuilderTreatsANullPortraitAsNone() { + CvIdentity identity = CvIdentity.builder() + .name("Ada", "Lovelace") + .contact(CONTACT) + .portrait(null) + .build(); + assertThat(identity.portrait()).isEmpty(); + } + + /** The smallest valid PNG: one opaque pixel. */ + private static byte[] onePixelPng() { + return java.util.Base64.getDecoder().decode( + "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmM" + + "IQAAAABJRU5ErkJggg=="); + } +} diff --git a/qa/src/test/java/com/demcha/compose/document/templates/cv/data/CvSkillNoteTest.java b/qa/src/test/java/com/demcha/compose/document/templates/cv/data/CvSkillNoteTest.java new file mode 100644 index 000000000..a76b79a1c --- /dev/null +++ b/qa/src/test/java/com/demcha/compose/document/templates/cv/data/CvSkillNoteTest.java @@ -0,0 +1,63 @@ +package com.demcha.compose.document.templates.cv.data; + +import org.junit.jupiter.api.Test; + +import java.util.OptionalDouble; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Pins the written-out level on {@link CvSkill}: what it normalises, and that + * every way of building a skill that predates it still compiles and still + * produces a skill without one. + */ +class CvSkillNoteTest { + + @Test + void aSkillCarriesTheLevelAsTheDocumentWordsIt() { + CvSkill skill = new CvSkill("Spanish", OptionalDouble.of(0.6), + "Professional Working"); + assertThat(skill.note()).isEqualTo("Professional Working"); + assertThat(skill.level()).hasValue(0.6); + } + + @Test + void theFactoryTakesBothChannels() { + CvSkill skill = CvSkill.of("French", 0.4, "Basic"); + assertThat(skill.note()).isEqualTo("Basic"); + assertThat(skill.level()).hasValue(0.4); + } + + @Test + void anAbsentNoteIsBlankRatherThanNull() { + assertThat(new CvSkill("Java", OptionalDouble.empty(), null).note()).isEmpty(); + assertThat(new CvSkill("Java", OptionalDouble.empty(), " ").note()).isEmpty(); + } + + @Test + void aNoteIsTrimmed() { + assertThat(new CvSkill("Java", OptionalDouble.empty(), " Native ").note()) + .isEqualTo("Native"); + } + + @Test + void theTwoArgumentConstructorStillBuildsASkillWithoutANote() { + CvSkill skill = new CvSkill("Java", OptionalDouble.of(0.9)); + assertThat(skill.note()).isEmpty(); + assertThat(skill.level()).hasValue(0.9); + } + + @Test + void theNameOnlyAndLevelFactoriesStillBuildSkillsWithoutANote() { + assertThat(CvSkill.of("Java").note()).isEmpty(); + assertThat(CvSkill.of("Java").level()).isEmpty(); + assertThat(CvSkill.of("Java", 0.5).note()).isEmpty(); + assertThat(CvSkill.of("Java", 0.5).level()).hasValue(0.5); + } + + @Test + void aSkillGroupBuiltFromNamesCarriesNoNotes() { + SkillGroup group = SkillGroup.of("Languages", "Java", "Kotlin"); + assertThat(group.entries()).allSatisfy(skill -> assertThat(skill.note()).isEmpty()); + } +} diff --git a/qa/src/test/java/com/demcha/compose/document/templates/cv/presets/CharcoalGoldFixtures.java b/qa/src/test/java/com/demcha/compose/document/templates/cv/presets/CharcoalGoldFixtures.java new file mode 100644 index 000000000..84b063386 --- /dev/null +++ b/qa/src/test/java/com/demcha/compose/document/templates/cv/presets/CharcoalGoldFixtures.java @@ -0,0 +1,143 @@ +package com.demcha.compose.document.templates.cv.presets; + +import com.demcha.compose.document.templates.core.identity.Contact; +import com.demcha.compose.document.templates.core.identity.Link; +import com.demcha.compose.document.templates.cv.data.CvDocument; +import com.demcha.compose.document.templates.cv.data.CvEntry; +import com.demcha.compose.document.templates.cv.data.CvIdentity; +import com.demcha.compose.document.templates.cv.data.CvSkill; +import com.demcha.compose.document.templates.cv.data.EntriesSection; +import com.demcha.compose.document.templates.cv.data.ParagraphSection; +import com.demcha.compose.document.templates.cv.data.RowStyle; +import com.demcha.compose.document.templates.cv.data.RowsSection; +import com.demcha.compose.document.templates.cv.data.SkillGroup; +import com.demcha.compose.document.templates.cv.data.SkillsSection; + +import java.util.List; + +/** + * The canonical fixture for {@link CharcoalGold} — the document the design + * was drawn around, and the one both gates measure. + * + *

One fixture feeds the layout snapshot and the pixel baseline, so a + * change that moves the sheet moves both together instead of leaving one + * gate measuring a document the other no longer renders.

+ */ +public final class CharcoalGoldFixtures { + + private static final String NEWLINE = String.valueOf((char) 10); + + private CharcoalGoldFixtures() { + } + + /** + * The canonical CV: a photograph, five contact channels, ten rated + * skills, four languages, two degrees, three roles, three certifications, + * three achievements and a six-tool strip — one page. + * + * @return the fixture document + */ + public static CvDocument canonicalCv() { + return CvDocument.builder() + .identity(CvIdentity.builder() + .name("ANASTASIA", "SMITH") + .jobTitle("PROJECT MANAGER") + .contact(new Contact("+44 7700 900123", + "hello@anastasiasmith.com", + "London, United Kingdom")) + .link(new Link("anastasiasmith.com", + "https://anastasiasmith.com")) + .link(new Link("linkedin.com/in/anastasiasmith", + "https://www.linkedin.com/in/anastasiasmith")) + .portrait(CvFixturePortrait.silhouette()) + .build()) + .section(new ParagraphSection("Summary", String.join(NEWLINE, + "Results-driven Project Manager with 6+ years of experience leading cross-functional teams and delivering complex projects on time and within budget.", + "Skilled in agile and waterfall methodologies, process optimisation, and stakeholder management. Passionate about driving efficiency and creating value." + ))) + .section(SkillsSection.of("SKILLS", new SkillGroup("Core", List.of( + CvSkill.of("Project Management", 1.0), + CvSkill.of("Budgeting & Forecasting", 1.0), + CvSkill.of("Stakeholder Communication", 1.0), + CvSkill.of("Risk Management", 1.0), + CvSkill.of("Data Analysis", 0.8), + CvSkill.of("Process Improvement", 0.8), + CvSkill.of("Microsoft Excel", 1.0), + CvSkill.of("Power BI", 0.8), + CvSkill.of("Jira / Confluence", 1.0), + CvSkill.of("Agile Methodologies", 0.8) + )))) + .section(RowsSection.builder("LANGUAGES", RowStyle.PLAIN) + .row("English", "Native") + .row("Russian", "Native") + .row("French", "B2 – Upper Intermediate") + .row("German", "A2 – Basic") + .build()) + .section(EntriesSection.builder("EDUCATION") + .entry(CvEntry.builder("MSc Project Management") + .subtitle("University College London").date("2018 – 2019").build()) + .entry(CvEntry.builder("BSc Business Administration") + .subtitle("King's College London").date("2014 – 2017").build()) + .build()) + .section(EntriesSection.builder("EXPERIENCE") + .entry(CvEntry.builder("Senior Project Manager") + .subtitle("DIGITAL SOLUTIONS LTD") + .place("LONDON, UK") + .date("2021 – Present") + .body(List.of( + "Lead end-to-end delivery of enterprise software projects worth £1M–£3M.", + "Manage cross-functional teams of up to 15 members.", + "Implement agile practices that improved delivery speed by 25%.", + "Build strong client relationships and ensure stakeholder alignment." + )) + .build()) + .entry(CvEntry.builder("Project Manager") + .subtitle("INNOVATECH SYSTEMS") + .place("LONDON, UK") + .date("2019 – 2021") + .body(List.of( + "Delivered 12+ projects across IT and business transformation.", + "Monitored budgets, timelines, and resource allocation.", + "Identified risks and implemented mitigation strategies.", + "Achieved 98% on-time project delivery." + )) + .build()) + .entry(CvEntry.builder("Project Coordinator") + .subtitle("GLOBAL BUSINESS SERVICES") + .place("LONDON, UK") + .date("2017 – 2019") + .body(List.of( + "Supported project managers in planning and execution.", + "Prepared reports, dashboards, and documentation.", + "Coordinated meetings and tracked action items.", + "Improved internal processes and project templates." + )) + .build()) + .build()) + .section(EntriesSection.builder("CERTIFICATIONS") + .entry(CvEntry.builder("PMP® Certification") + .subtitle("Project Management Institute").date("2020").icon("certificate").build()) + .entry(CvEntry.builder("PRINCE2® Foundation") + .subtitle("AXELOS").date("2019").icon("certificate").build()) + .entry(CvEntry.builder("AgilePM® Foundation") + .subtitle("APMG International").date("2019").icon("certificate").build()) + .build()) + .section(EntriesSection.builder("ACHIEVEMENTS") + .entry(CvEntry.builder("Excellence in Delivery Award") + .subtitle("Digital Solutions Ltd").date("2023").icon("trophy").build()) + .entry(CvEntry.builder("Process Improvement Initiative") + .subtitle("Increased efficiency by 30%").date("2022").icon("growth").build()) + .entry(CvEntry.builder("Top Performer") + .subtitle("Innovatech Systems").date("2020").icon("star").build()) + .build()) + .section(new ParagraphSection("TECHNICAL TOOLS", String.join(NEWLINE, + "Microsoft 365", + "Jira", + "Confluence", + "Asana", + "Slack", + "Power BI" + ))) + .build(); + } +} diff --git a/qa/src/test/java/com/demcha/compose/document/templates/cv/presets/CharcoalGoldLayoutSnapshotTest.java b/qa/src/test/java/com/demcha/compose/document/templates/cv/presets/CharcoalGoldLayoutSnapshotTest.java new file mode 100644 index 000000000..61d7e67a5 --- /dev/null +++ b/qa/src/test/java/com/demcha/compose/document/templates/cv/presets/CharcoalGoldLayoutSnapshotTest.java @@ -0,0 +1,36 @@ +package com.demcha.compose.document.templates.cv.presets; + +import com.demcha.compose.GraphCompose; +import com.demcha.compose.document.api.DocumentPageSize; +import com.demcha.compose.document.api.DocumentSession; +import com.demcha.compose.document.templates.TemplateTestSupport; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Exact layout snapshot gate for {@link CharcoalGold} - freezes the resolved + * geometry of the canonical one-page CV. + * + *

This preset leaves the page to the caller, so the session sets A4 with + * no margin: both columns run to the paper edge and each carries its own + * padding.

+ * + *

Refresh with {@code -Dgraphcompose.updateSnapshots=true} after a + * deliberate layout change, and commit the JSON with the change.

+ */ +class CharcoalGoldLayoutSnapshotTest { + + @Test + void canonicalCvMatchesLayoutSnapshot() throws Exception { + try (DocumentSession session = GraphCompose.document() + .pageSize(DocumentPageSize.A4) + .margin(0f, 0f, 0f, 0f) + .create()) { + CharcoalGold.create().compose(session, CharcoalGoldFixtures.canonicalCv()); + assertThat(session.layoutSnapshot().totalPages()).isEqualTo(1); + TemplateTestSupport.assertCanonicalSnapshot( + session, "charcoal_gold_layout", "cv-v2"); + } + } +} diff --git a/qa/src/test/java/com/demcha/compose/document/templates/cv/presets/CharcoalGoldSmokeTest.java b/qa/src/test/java/com/demcha/compose/document/templates/cv/presets/CharcoalGoldSmokeTest.java new file mode 100644 index 000000000..975bce03f --- /dev/null +++ b/qa/src/test/java/com/demcha/compose/document/templates/cv/presets/CharcoalGoldSmokeTest.java @@ -0,0 +1,258 @@ +package com.demcha.compose.document.templates.cv.presets; + +import com.demcha.compose.GraphCompose; +import com.demcha.compose.document.api.DocumentPageSize; +import com.demcha.compose.document.api.DocumentSession; +import com.demcha.compose.document.exceptions.AtomicNodeTooLargeException; +import com.demcha.compose.document.templates.api.DocumentTemplate; +import com.demcha.compose.document.templates.core.identity.Contact; +import com.demcha.compose.document.templates.core.identity.Link; +import com.demcha.compose.document.templates.cv.data.CvDocument; +import com.demcha.compose.document.templates.cv.data.CvEntry; +import com.demcha.compose.document.templates.cv.data.CvIdentity; +import com.demcha.compose.document.templates.cv.data.EntriesSection; +import com.demcha.compose.document.templates.cv.data.RowStyle; +import com.demcha.compose.document.templates.cv.data.RowsSection; +import com.demcha.compose.document.templates.cv.data.SkillGroup; +import com.demcha.compose.document.templates.cv.data.SkillsSection; +import org.apache.pdfbox.Loader; +import org.apache.pdfbox.pdmodel.PDDocument; +import org.apache.pdfbox.pdmodel.PDPage; +import org.apache.pdfbox.pdmodel.interactive.action.PDActionURI; +import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotation; +import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationLink; +import org.apache.pdfbox.text.PDFTextStripper; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +/** + * Smoke test for {@link CharcoalGold} — proves the preset renders a + * {@link CvDocument} end-to-end with its packaged marks, reaches its + * channels and titles as PDF links, and pins the contracts the class + * documents: the credential marks are this preset's own vocabulary, an + * entry without one is drawn plain, a skill without a level draws no + * rating, and the sheet holds one page. + */ +class CharcoalGoldSmokeTest { + + private static byte[] render(CvDocument doc) throws Exception { + try (DocumentSession session = open()) { + CharcoalGold.create().compose(session, doc); + assertThat(session.roots()).isNotEmpty(); + byte[] pdfBytes = session.toPdfBytes(); + assertThat(pdfBytes).isNotEmpty(); + return pdfBytes; + } + } + + /** Both columns run to the paper edge, so the page carries no margin. */ + private static DocumentSession open() { + return GraphCompose.document() + .pageSize(DocumentPageSize.A4) + .margin(0f, 0f, 0f, 0f) + .create(); + } + + private static String textOf(byte[] pdfBytes) throws Exception { + try (PDDocument document = Loader.loadPDF(pdfBytes)) { + return new PDFTextStripper().getText(document); + } + } + + private static List linkUris(byte[] pdfBytes) throws Exception { + List uris = new ArrayList<>(); + try (PDDocument document = Loader.loadPDF(pdfBytes)) { + for (PDPage page : document.getPages()) { + for (PDAnnotation annotation : page.getAnnotations()) { + if (annotation instanceof PDAnnotationLink link + && link.getAction() instanceof PDActionURI action) { + uris.add(action.getURI()); + } + } + } + } + return uris; + } + + @Test + void exposesStableIdentity() { + DocumentTemplate template = CharcoalGold.create(); + assertThat(template.id()).isEqualTo(CharcoalGold.ID); + assertThat(template.displayName()).isEqualTo(CharcoalGold.DISPLAY_NAME); + } + + @Test + void canonicalRenderCarriesTheText() throws Exception { + String text = textOf(render(CharcoalGoldFixtures.canonicalCv())); + assertThat(text) + .contains("hello@anastasiasmith.com") + .contains("London, United Kingdom") + .contains("Results-driven Project Manager") + .contains("Project Management") + .contains("B2 – Upper Intermediate") + .contains("MSc Project Management") + .contains("Senior Project Manager") + .contains("DIGITAL SOLUTIONS LTD · LONDON, UK") + .contains("Lead end-to-end delivery of enterprise software projects") + .contains("PMP® Certification") + .contains("Excellence in Delivery Award") + .contains("Confluence"); + } + + @Test + void rendersOneCanonicalPage() throws Exception { + try (PDDocument document = + Loader.loadPDF(render(CharcoalGoldFixtures.canonicalCv()))) { + assertThat(document.getNumberOfPages()).isEqualTo(1); + } + } + + @Test + void channelsReachThePdfAsLinks() throws Exception { + // The dial and mail targets are built from the values. They are + // annotations, so they move no pixel and no layout node — which is + // why the parity gates cannot see them either. + assertThat(linkUris(render(CharcoalGoldFixtures.canonicalCv()))) + .contains("tel:+447700900123", + "mailto:hello@anastasiasmith.com", + "https://anastasiasmith.com", + "https://www.linkedin.com/in/anastasiasmith"); + } + + @Test + void aTitleWithALinkReachesThePdfAsOne() throws Exception { + byte[] pdf = render(CvDocument.builder() + .identity(identity()) + .section(EntriesSection.builder("Experience") + .entry(CvEntry.builder("Engineer") + .subtitle("Acme") + .date("2024") + .body("Shipped a thing.") + .link("https://acme.example.com") + .build()) + .build()) + .section(EntriesSection.builder("Certifications") + .entry(CvEntry.builder("Certified Something") + .subtitle("An Institute") + .date("2023") + .icon("certificate") + .link("https://example.com/cert") + .build()) + .build()) + .build()); + assertThat(linkUris(pdf)) + .contains("https://acme.example.com", "https://example.com/cert"); + } + + @Test + void rejectsAnUnknownCredentialMarkByName() { + CvDocument doc = CvDocument.builder() + .identity(identity()) + .section(EntriesSection.builder("Achievements") + .entry(CvEntry.builder("Award").icon("no-such-mark").build()) + .build()) + .build(); + try (DocumentSession session = open()) { + assertThatThrownBy(() -> CharcoalGold.create().compose(session, doc)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("no-such-mark") + .hasMessageContaining("trophy"); + } + } + + @Test + void drawsACredentialWithoutAMark() throws Exception { + String text = textOf(render(CvDocument.builder() + .identity(identity()) + .section(EntriesSection.builder("Certifications") + .entry(CvEntry.builder("Certified Something") + .subtitle("An Institute") + .date("2023") + .build()) + .build()) + .build())); + assertThat(text).contains("Certified Something").contains("An Institute"); + } + + @Test + void drawsAnUnlevelledSkillWithoutARating() throws Exception { + // A rating needs a level; without one the row is the name and its + // bullet, not five empty dots that read as a score of zero. + String text = textOf(render(CvDocument.builder() + .identity(identity()) + .section(SkillsSection.of("Skills", SkillGroup.of("Core", "Java", "Kotlin"))) + .build())); + assertThat(text).contains("Java").contains("Kotlin"); + } + + @Test + void rendersWithoutAPortrait() throws Exception { + // No photograph: the sidebar starts at the contact block rather than + // leaving a hole above it. + String text = textOf(render(CvDocument.builder() + .identity(CvIdentity.builder() + .name("Ada", "Lovelace") + .jobTitle("Analyst") + .contact(new Contact("+44 20 7946 0000", "ada@example.com", + "London, UK")) + .build()) + .build())); + assertThat(text).contains("ada@example.com"); + } + + @Test + void writesTheLanguageLevelTheDocumentCarries() throws Exception { + // Languages are rows rather than levelled skills precisely so the + // words survive; the row style is not read. + String text = textOf(render(CvDocument.builder() + .identity(identity()) + .section(RowsSection.builder("Languages", RowStyle.BULLETED_STACKED) + .row("Portuguese", "B1 – Intermediate") + .build()) + .build())); + assertThat(text).contains("Portuguese").contains("B1 – Intermediate"); + } + + @Test + void refusesACvLongerThanTheSheet() { + // The two columns are a single row, and a row is atomic. A CV past + // the sheet raises rather than flowing. + EntriesSection.Builder experience = EntriesSection.builder("Experience"); + for (int i = 0; i < 16; i++) { + experience.entry(CvEntry.builder("Role " + i) + .subtitle("Employer " + i) + .place("City") + .date("2010 - 2011") + .body(List.of( + "Did a substantial thing that took a full line of text here.", + "Did a second substantial thing, also a full line of text.", + "And a third, so each role occupies real vertical space.")) + .build()); + } + CvDocument doc = CvDocument.builder() + .identity(identity()) + .section(experience.build()) + .build(); + try (DocumentSession session = open()) { + CharcoalGold.create().compose(session, doc); + assertThatThrownBy(session::toPdfBytes) + .isInstanceOf(AtomicNodeTooLargeException.class) + .hasMessageContaining("Body"); + } + } + + private static CvIdentity identity() { + return CvIdentity.builder() + .name("Ada", "Lovelace") + .jobTitle("Analyst") + .contact(new Contact("+44 20 7946 0000", "ada@example.com", "London, UK")) + .link(new Link("linkedin.com/in/ada", "https://linkedin.com/in/ada")) + .portrait(CvFixturePortrait.silhouette()) + .build(); + } +} diff --git a/qa/src/test/java/com/demcha/compose/document/templates/cv/presets/CharcoalGoldVisualParityTest.java b/qa/src/test/java/com/demcha/compose/document/templates/cv/presets/CharcoalGoldVisualParityTest.java new file mode 100644 index 000000000..3a43c141a --- /dev/null +++ b/qa/src/test/java/com/demcha/compose/document/templates/cv/presets/CharcoalGoldVisualParityTest.java @@ -0,0 +1,45 @@ +package com.demcha.compose.document.templates.cv.presets; + +import com.demcha.compose.GraphCompose; +import com.demcha.compose.document.api.DocumentPageSize; +import com.demcha.compose.document.api.DocumentSession; +import com.demcha.compose.testing.visual.PdfVisualRegression; +import org.junit.jupiter.api.Test; + +import java.nio.file.Path; + +/** + * Pixel-diff visual parity gate for {@link CharcoalGold} - the canonical + * one-page CV against a checked-in baseline. + * + *

Re-blessing baselines - after a deliberate visual + * change, re-run with {@code -Dgraphcompose.visual.approve=true} and commit + * the updated PNG with the change. Baselines live under + * {@code src/test/resources/visual-baselines/cv-v2-layered/}.

+ */ +class CharcoalGoldVisualParityTest { + + private static final Path BASELINE_ROOT = Path.of( + "src", "test", "resources", "visual-baselines", "cv-v2-layered"); + + private static final long PIXEL_DIFF_BUDGET = 50_000L; + private static final int PER_PIXEL_TOLERANCE = 8; + + @Test + void rendersWithinPixelDiffBudget() throws Exception { + byte[] pdfBytes; + try (DocumentSession document = GraphCompose.document() + .pageSize(DocumentPageSize.A4) + .margin(0f, 0f, 0f, 0f) + .create()) { + CharcoalGold.create().compose(document, CharcoalGoldFixtures.canonicalCv()); + pdfBytes = document.toPdfBytes(); + } + + PdfVisualRegression.standard() + .baselineRoot(BASELINE_ROOT) + .perPixelTolerance(PER_PIXEL_TOLERANCE) + .mismatchedPixelBudget(PIXEL_DIFF_BUDGET) + .assertMatchesBaseline("charcoal_gold", pdfBytes); + } +} diff --git a/qa/src/test/java/com/demcha/compose/document/templates/cv/presets/CvFixturePortrait.java b/qa/src/test/java/com/demcha/compose/document/templates/cv/presets/CvFixturePortrait.java new file mode 100644 index 000000000..4b03a3504 --- /dev/null +++ b/qa/src/test/java/com/demcha/compose/document/templates/cv/presets/CvFixturePortrait.java @@ -0,0 +1,55 @@ +package com.demcha.compose.document.templates.cv.presets; + +import com.demcha.compose.document.image.DocumentImageData; + +import java.awt.Color; +import java.awt.Graphics2D; +import java.awt.RenderingHints; +import java.awt.image.BufferedImage; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.UncheckedIOException; + +import javax.imageio.ImageIO; + +/** + * The photograph the CV fixtures use. + * + *

It is drawn rather than committed: a real face in the repository would + * be a licensed asset guarding a layout, and what the gates need is only + * that the disc has something in it. It is deterministic, so the pixel + * baselines that include it are stable.

+ */ +final class CvFixturePortrait { + + private CvFixturePortrait() { + } + + /** + * A flat two-tone silhouette. + * + * @return the portrait image data + */ + static DocumentImageData silhouette() { + BufferedImage image = new BufferedImage(512, 512, BufferedImage.TYPE_INT_RGB); + Graphics2D g = image.createGraphics(); + try { + g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, + RenderingHints.VALUE_ANTIALIAS_ON); + g.setColor(new Color(222, 226, 231)); + g.fillRect(0, 0, 512, 512); + g.setColor(new Color(150, 160, 173)); + g.fillOval(171, 102, 169, 169); + g.fillOval(67, 307, 379, 379); + } finally { + g.dispose(); + } + ByteArrayOutputStream png = new ByteArrayOutputStream(); + try { + ImageIO.write(image, "png", png); + } catch (IOException e) { + throw new UncheckedIOException("Failed to draw the fixture portrait", e); + } + return DocumentImageData.fromBytes(png.toByteArray()); + } +} diff --git a/qa/src/test/java/com/demcha/compose/document/templates/cv/presets/CvPresetDialTargetTest.java b/qa/src/test/java/com/demcha/compose/document/templates/cv/presets/CvPresetDialTargetTest.java new file mode 100644 index 000000000..962f9294b --- /dev/null +++ b/qa/src/test/java/com/demcha/compose/document/templates/cv/presets/CvPresetDialTargetTest.java @@ -0,0 +1,116 @@ +package com.demcha.compose.document.templates.cv.presets; + +import com.demcha.compose.GraphCompose; +import com.demcha.compose.document.api.DocumentSession; +import com.demcha.compose.document.templates.api.DocumentTemplate; +import com.demcha.compose.document.templates.core.identity.Contact; +import com.demcha.compose.document.templates.core.identity.ContactUri; +import com.demcha.compose.document.templates.cv.data.CvDocument; +import com.demcha.compose.document.templates.cv.data.CvIdentity; +import com.demcha.compose.document.templates.cv.data.CvName; +import org.apache.pdfbox.Loader; +import org.apache.pdfbox.pdmodel.PDDocument; +import org.apache.pdfbox.pdmodel.PDPage; +import org.apache.pdfbox.pdmodel.interactive.action.PDActionURI; +import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotation; +import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationLink; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.function.Supplier; +import java.util.stream.Stream; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * A sheet prints a number the way a person reads it and links the number a + * device dials, and the two are not the same string: a UK sheet prints + * {@code +44 (0)20 7946 0832}, and a caller reaching that line from abroad + * dials {@code +442079460832} — the trunk prefix is for a domestic dialler + * and belongs to neither the printed nor the international form. + * + *

Every design here drew that conversion from its own copy, and most of the + * copies did not know about the trunk prefix, so one printed number became a + * different target — an unreachable one — depending on which design its owner + * had chosen. They all route through {@link ContactUri} now, and asking each + * of them the same question is what keeps them there: a preset that writes its + * own converter again fails here rather than at a reader's phone.

+ */ +class CvPresetDialTargetTest { + + /** The printed form: what a reader sees on the page. */ + private static final String PRINTED = "+44 (0)20 7946 0832"; + + /** The dialled form: what the link under it has to carry. */ + private static final String DIALLED = "tel:+442079460832"; + + @ParameterizedTest(name = "{0}") + @MethodSource("presets") + void theNumberOnThePageIsNotTheNumberInTheLink( + String slug, Supplier> factory) throws Exception { + + byte[] pdf = render(factory.get()); + + assertThat(dialTargets(pdf)) + .describedAs("%s prints %s for a reader, so its link has to carry %s", + slug, PRINTED, DIALLED) + .containsExactly(DIALLED); + } + + /** + * A contact block and nothing else: the phone link is what is under test, + * and an empty CV renders in every one of these designs. + */ + private static byte[] render(DocumentTemplate template) throws Exception { + CvIdentity identity = new CvIdentity(CvName.of("Ada", "Lovelace"), "Analyst", + new Contact(PRINTED, "ada@example.test", "London, UK"), + List.of(), Optional.empty()); + try (DocumentSession session = GraphCompose.document().create()) { + // One design names a display family nothing bundles, so the caller + // supplies it. Registering it for all of them costs the rest nothing. + OrangeOpsTestFont.register(session); + template.compose(session, new CvDocument(identity, List.of())); + return session.toPdfBytes(); + } + } + + /** + * The {@code tel:} targets the file carries. A run that wraps at a space + * carries one annotation per piece, all pointing at the same place, so it + * is the set of targets that is asserted rather than their number. + */ + private static Set dialTargets(byte[] pdfBytes) throws Exception { + Set targets = new LinkedHashSet<>(); + try (PDDocument document = Loader.loadPDF(pdfBytes)) { + for (PDPage page : document.getPages()) { + for (PDAnnotation annotation : page.getAnnotations()) { + if (annotation instanceof PDAnnotationLink link + && link.getAction() instanceof PDActionURI uri + && uri.getURI().startsWith("tel:")) { + targets.add(uri.getURI()); + } + } + } + } + return targets; + } + + private static Stream presets() { + return Stream.of( + Arguments.of("charcoal_gold", (Supplier>) CharcoalGold::create), + Arguments.of("midnight_navy", (Supplier>) MidnightNavy::create), + Arguments.of("navy_sidebar", (Supplier>) NavySidebar::create), + Arguments.of("orange_ops", (Supplier>) OrangeOps::create), + Arguments.of("professional_sidebar", (Supplier>) ProfessionalSidebar::create), + Arguments.of("serif_headline", (Supplier>) SerifHeadline::create), + Arguments.of("slate_orange", (Supplier>) SlateOrange::create), + Arguments.of("teal_pulse", (Supplier>) TealPulse::create), + Arguments.of("terracotta_rail", (Supplier>) TerracottaRail::create), + Arguments.of("violet_grid", (Supplier>) VioletGrid::create)); + } +} diff --git a/qa/src/test/java/com/demcha/compose/document/templates/cv/presets/MidnightNavyFixtures.java b/qa/src/test/java/com/demcha/compose/document/templates/cv/presets/MidnightNavyFixtures.java new file mode 100644 index 000000000..c575720a9 --- /dev/null +++ b/qa/src/test/java/com/demcha/compose/document/templates/cv/presets/MidnightNavyFixtures.java @@ -0,0 +1,197 @@ +package com.demcha.compose.document.templates.cv.presets; + +import com.demcha.compose.document.templates.core.identity.Contact; +import com.demcha.compose.document.templates.core.identity.Link; +import com.demcha.compose.document.templates.cv.data.CvDocument; +import com.demcha.compose.document.templates.cv.data.CvEntry; +import com.demcha.compose.document.templates.cv.data.CvIdentity; +import com.demcha.compose.document.templates.cv.data.CvName; +import com.demcha.compose.document.templates.cv.data.CvSkill; +import com.demcha.compose.document.templates.cv.data.EntriesSection; +import com.demcha.compose.document.templates.cv.data.ParagraphSection; +import com.demcha.compose.document.templates.cv.data.SkillGroup; +import com.demcha.compose.document.templates.cv.data.SkillsSection; +import com.demcha.compose.document.templates.cv.data.Slot; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +/** + * Shared fixture data for the {@link MidnightNavy} gates — the SAME document + * feeds the pixel parity test and the layout snapshot test, so a geometry + * shift the pixel budget absorbs still trips the exact snapshot, and vice + * versa. + */ +final class MidnightNavyFixtures { + + /** The break a body stacks its lines on. */ + private static final String NEWLINE = String.valueOf((char) 10); + + private MidnightNavyFixtures() { + } + + /** + * The canonical one-page CV — five contact rows including two links, two + * degrees, ten metered skills, three rated languages, the summary, three + * roles, three achievement discs and two certifications. + * + * @return the document + */ + public static CvDocument canonicalCv() { + List placements = new ArrayList<>(); + placements.add(new CvDocument.Placement(Slot.SIDEBAR, education())); + placements.add(new CvDocument.Placement(Slot.SIDEBAR, skills())); + placements.add(new CvDocument.Placement(Slot.SIDEBAR, languages())); + placements.add(new CvDocument.Placement(Slot.MAIN, summary())); + placements.add(new CvDocument.Placement(Slot.MAIN, experience())); + placements.add(new CvDocument.Placement(Slot.MAIN, achievements())); + placements.add(new CvDocument.Placement(Slot.MAIN, certifications())); + return new CvDocument(identity(), placements); + } + + static CvIdentity identity() { + return new CvIdentity( + CvName.of("Alex", "Morgan"), + "Marketing Manager", + new Contact("+1 (555) 123-4567", + "alex.morgan@email.com", + "New York, NY, USA"), + List.of(new Link("LinkedIn", "https://www.linkedin.com/in/alexmorgan"), + new Link("Portfolio", "https://alexmorgan.com")), + Optional.empty()); + } + + /** The degrees, newest first. */ + static EntriesSection education() { + return new EntriesSection("EDUCATION", List.of( + CvEntry.builder("MASTER OF BUSINESS ADMINISTRATION") + .subtitle("University of Chicago Booth School of Business") + .date("2016 \u2013 2018") + .build(), + CvEntry.builder("BACHELOR OF SCIENCE IN MARKETING") + .subtitle("Boston University") + .date("2012 \u2013 2016") + .build())); + } + + /** The metered skills, as one flat group. */ + static SkillsSection skills() { + List entries = new ArrayList<>(); + entries.add(CvSkill.of("Strategic Planning", 0.82)); + entries.add(CvSkill.of("Digital Marketing", 0.84)); + entries.add(CvSkill.of("Market Research", 0.76)); + entries.add(CvSkill.of("Brand Management", 0.84)); + entries.add(CvSkill.of("Data Analysis", 0.81)); + entries.add(CvSkill.of("Project Management", 0.68)); + entries.add(CvSkill.of("Google Analytics", 0.83)); + entries.add(CvSkill.of("SEO / SEM", 0.76)); + entries.add(CvSkill.of("Microsoft Office Suite", 0.75)); + entries.add(CvSkill.of("Communication", 0.96)); + return new SkillsSection("SKILLS", + List.of(new SkillGroup("SKILLS", entries))); + } + + /** The languages. The design shows a rating in fifths. */ + static SkillsSection languages() { + List entries = new ArrayList<>(); + entries.add(CvSkill.of("English", 1.0)); + entries.add(CvSkill.of("Spanish", 0.8)); + entries.add(CvSkill.of("French", 0.6)); + return new SkillsSection("LANGUAGES", + List.of(new SkillGroup("LANGUAGES", entries))); + } + + /** The opening prose. */ + static ParagraphSection summary() { + return new ParagraphSection("PROFESSIONAL SUMMARY", + "Results-driven Marketing Manager with 6+ years of " + + "experience in developing and executing data-driven " + + "marketing strategies that drive brand growth and " + + "customer engagement. Proven track record in leading " + + "cross-functional teams, managing successful campaigns, " + + "and delivering measurable results. Passionate about " + + "building strong brands and creating impactful marketing " + + "initiatives."); + } + + /** The roles held, each a headline over its bullets. */ + static EntriesSection experience() { + return new EntriesSection("EXPERIENCE", List.of( + CvEntry.builder("MARKETING MANAGER") + .subtitle("Starwave Solutions") + .place("New York, NY") + .date("2021 \u2013 Present") + .body(String.join(NEWLINE, + "Develop and implement comprehensive marketing strategies " + + "that increased brand awareness by 40% and lead " + + "generation by 35%.", + "Manage a team of 6 marketing professionals and " + + "collaborate with sales, product, and design teams to " + + "drive campaign success.", + "Oversee digital campaigns across SEO, SEM, social media, " + + "and email marketing resulting in a 25% increase in ROI.", + "Analyze market trends and competitor activities to " + + "identify new opportunities and optimize marketing " + + "efforts.")) + .build(), + CvEntry.builder("SENIOR MARKETING SPECIALIST") + .subtitle("BrightLine Technologies") + .place("New York, NY") + .date("2018 \u2013 2021") + .body(String.join(NEWLINE, + "Executed multi-channel marketing campaigns that improved " + + "customer engagement by 30%.", + "Conducted market research and data analysis to support " + + "strategic decision-making.", + "Managed social media channels and content strategy, " + + "growing followers by 50%.", + "Coordinated with creative teams to develop compelling " + + "content and visuals.")) + .build(), + CvEntry.builder("MARKETING COORDINATOR") + .subtitle("Peak Performance Group") + .place("Boston, MA") + .date("2016 \u2013 2018") + .body(String.join(NEWLINE, + "Supported the development and execution of marketing " + + "plans and campaigns.", + "Assisted in organizing events, webinars, and promotional " + + "activities.", + "Monitored campaign performance and prepared reports for " + + "management.", + "Maintained and updated marketing databases and CRM " + + "systems.")) + .build())); + } + + /** + * The discs. A card has one line and no heading over it, so the line is + * the entry's title. + */ + static EntriesSection achievements() { + return new EntriesSection("ACHIEVEMENTS", List.of( + CvEntry.builder("Increased brand awareness by 40% within 2 years.") + .icon("trophy") + .build(), + CvEntry.builder("Boosted lead generation by 35% annually.") + .icon("growth") + .build(), + CvEntry.builder("Recognized as Top Performer in 2022.") + .icon("award") + .build())); + } + + /** The certifications, one to a column. */ + static EntriesSection certifications() { + return new EntriesSection("CERTIFICATIONS", List.of( + CvEntry.builder("Google Analytics Certified") + .subtitle("Google") + .date("2020") + .build(), + CvEntry.builder("HubSpot Content Marketing Certified") + .subtitle("HubSpot Academy") + .date("2021") + .build())); + } +} diff --git a/qa/src/test/java/com/demcha/compose/document/templates/cv/presets/MidnightNavyLayoutSnapshotTest.java b/qa/src/test/java/com/demcha/compose/document/templates/cv/presets/MidnightNavyLayoutSnapshotTest.java new file mode 100644 index 000000000..3168694ee --- /dev/null +++ b/qa/src/test/java/com/demcha/compose/document/templates/cv/presets/MidnightNavyLayoutSnapshotTest.java @@ -0,0 +1,32 @@ +package com.demcha.compose.document.templates.cv.presets; + +import com.demcha.compose.GraphCompose; +import com.demcha.compose.document.api.DocumentSession; +import com.demcha.compose.document.templates.TemplateTestSupport; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Exact layout snapshot gate for {@link MidnightNavy} — freezes the resolved + * geometry of the canonical one-page CV, which the pixel budget cannot see: a + * small column or spacing shift stays under the visual-diff budget but changes + * the snapshot. + * + *

The preset owns its page geometry, so the session starts unconfigured.

+ * + *

Refresh with {@code -Dgraphcompose.updateSnapshots=true} after a + * deliberate layout change, and commit the JSON with the change.

+ */ +class MidnightNavyLayoutSnapshotTest { + + @Test + void canonicalCvMatchesLayoutSnapshot() throws Exception { + try (DocumentSession session = GraphCompose.document().create()) { + MidnightNavy.create().compose(session, MidnightNavyFixtures.canonicalCv()); + assertThat(session.layoutSnapshot().totalPages()).isEqualTo(1); + TemplateTestSupport.assertCanonicalSnapshot( + session, "midnight_navy_layout", "cv-v2"); + } + } +} diff --git a/qa/src/test/java/com/demcha/compose/document/templates/cv/presets/MidnightNavySmokeTest.java b/qa/src/test/java/com/demcha/compose/document/templates/cv/presets/MidnightNavySmokeTest.java new file mode 100644 index 000000000..565c0418f --- /dev/null +++ b/qa/src/test/java/com/demcha/compose/document/templates/cv/presets/MidnightNavySmokeTest.java @@ -0,0 +1,292 @@ +package com.demcha.compose.document.templates.cv.presets; + +import com.demcha.compose.GraphCompose; +import com.demcha.compose.document.api.DocumentSession; +import com.demcha.compose.document.exceptions.AtomicNodeTooLargeException; +import com.demcha.compose.document.templates.api.DocumentTemplate; +import com.demcha.compose.document.templates.core.identity.Contact; +import com.demcha.compose.document.templates.cv.data.CvDocument; +import com.demcha.compose.document.templates.cv.data.CvEntry; +import com.demcha.compose.document.templates.cv.data.CvIdentity; +import com.demcha.compose.document.templates.cv.data.CvName; +import com.demcha.compose.document.templates.cv.data.CvSection; +import com.demcha.compose.document.templates.cv.data.CvSkill; +import com.demcha.compose.document.templates.cv.data.EntriesSection; +import com.demcha.compose.document.templates.cv.data.SkillGroup; +import com.demcha.compose.document.templates.cv.data.SkillsSection; +import com.demcha.compose.document.templates.cv.data.Slot; +import org.apache.pdfbox.Loader; +import org.apache.pdfbox.pdmodel.PDDocument; +import org.apache.pdfbox.pdmodel.PDPage; +import org.apache.pdfbox.pdmodel.interactive.action.PDActionURI; +import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotation; +import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationLink; +import org.apache.pdfbox.text.PDFTextStripper; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.Set; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +/** + * Smoke test for {@link MidnightNavy} — proves the preset renders a + * {@link CvDocument} end-to-end with its packaged marks, builds the monogram + * and the tracked role line from the identity it is given, rounds a language's + * rating to the nearest of five dots, reports an unknown mark as a data error, + * drops a berth nobody filled, and carries the channels and the entry titles as + * link annotations, which move no pixel and no layout node so neither gate + * would notice them going missing. + */ +class MidnightNavySmokeTest { + + private static final String NEWLINE = String.valueOf((char) 10); + + private static byte[] render(CvDocument doc) throws Exception { + // The preset owns its page geometry, so the session starts unconfigured. + try (DocumentSession session = GraphCompose.document().create()) { + MidnightNavy.create().compose(session, doc); + assertThat(session.roots()).isNotEmpty(); + byte[] pdfBytes = session.toPdfBytes(); + assertThat(pdfBytes).isNotEmpty(); + return pdfBytes; + } + } + + private static String textOf(byte[] pdfBytes) throws Exception { + try (PDDocument document = Loader.loadPDF(pdfBytes)) { + return new PDFTextStripper().getText(document); + } + } + + /** The text with its spaces removed — the role line is tracked with real spaces. */ + private static String compact(String text) { + return text.replaceAll("(?U)\\s", ""); + } + + private static List linkTargets(byte[] pdfBytes) throws Exception { + List targets = new ArrayList<>(); + try (PDDocument document = Loader.loadPDF(pdfBytes)) { + for (PDPage page : document.getPages()) { + for (PDAnnotation annotation : page.getAnnotations()) { + if (annotation instanceof PDAnnotationLink link + && link.getAction() instanceof PDActionURI uri) { + targets.add(uri.getURI()); + } + } + } + } + return targets; + } + + /** The canonical document with one section replaced. */ + private static CvDocument withSection(Slot slot, CvSection replacement, + String replacedTitle) { + List placements = new ArrayList<>(); + for (CvDocument.Placement placement : MidnightNavyFixtures.canonicalCv().placements()) { + if (placement.section().title().equals(replacedTitle)) { + placements.add(new CvDocument.Placement(slot, replacement)); + } else { + placements.add(placement); + } + } + return new CvDocument(MidnightNavyFixtures.identity(), placements); + } + + /** The canonical document with one section left out entirely. */ + private static CvDocument withoutSection(String droppedTitle) { + List placements = new ArrayList<>(); + for (CvDocument.Placement placement : MidnightNavyFixtures.canonicalCv().placements()) { + if (!placement.section().title().equals(droppedTitle)) { + placements.add(placement); + } + } + return new CvDocument(MidnightNavyFixtures.identity(), placements); + } + + @Test + void exposesStableIdentity() { + DocumentTemplate template = MidnightNavy.create(); + assertThat(template.id()).isEqualTo(MidnightNavy.ID); + assertThat(template.displayName()).isEqualTo(MidnightNavy.DISPLAY_NAME); + } + + @Test + void rendersCanonicalCvWithPackagedMarks() throws Exception { + render(MidnightNavyFixtures.canonicalCv()); + } + + @Test + void canonicalRenderCarriesEveryBlocksText() throws Exception { + String text = textOf(render(MidnightNavyFixtures.canonicalCv())); + assertThat(text) + .contains("alex.morgan@email.com") + .contains("New York, NY, USA") + .contains("University of Chicago Booth School of") + .contains("Strategic Planning") + .contains("English") + .contains("Results-driven Marketing Manager") + .contains("Starwave Solutions") + .contains("Increased brand") + .contains("Google Analytics Certified"); + assertThat(compact(text)).contains("CONTACT"); + } + + @Test + void theMonogramIsTheTwoInitialsOfTheName() throws Exception { + // The circle carries one initial per line, taken from the name rather + // than from a field a document would have to fill in twice. + String text = textOf(render(MidnightNavyFixtures.canonicalCv())); + List lines = text.lines().map(String::strip) + .filter(line -> !line.isEmpty()).toList(); + assertThat(lines).contains("A", "M"); + } + + @Test + void theNameAndRoleAreSetInCapitals() throws Exception { + // The design sets both in capitals, and the role is tracked with real + // spaces — so it is the spaceless text that carries the wording. + String compact = compact(textOf(render(MidnightNavyFixtures.canonicalCv()))); + assertThat(compact).contains("ALEXMORGAN").contains("MARKETINGMANAGER"); + } + + @Test + void contactChannelsAndLinksAreClickable() throws Exception { + List targets = linkTargets(render(MidnightNavyFixtures.canonicalCv())); + assertThat(targets) + .contains("mailto:alex.morgan@email.com") + .contains("tel:+15551234567") + .contains("https://www.linkedin.com/in/alexmorgan") + .contains("https://alexmorgan.com"); + } + + @Test + void aLinkShowsItsLabelAndHidesItsAddress() throws Exception { + // The plate draws the label, so its width is the same whatever the + // profile behind it is called; the address is reachable, not written. + byte[] pdfBytes = render(MidnightNavyFixtures.canonicalCv()); + assertThat(textOf(pdfBytes)) + .contains("LinkedIn") + .contains("Portfolio") + .doesNotContain("linkedin.com/in/alexmorgan"); + assertThat(linkTargets(pdfBytes)) + .contains("https://www.linkedin.com/in/alexmorgan"); + } + + @Test + void aTrunkPrefixIsNotDialled() throws Exception { + CvIdentity printed = new CvIdentity(CvName.of("Ada", "Lovelace"), "Analyst", + new Contact("+44 (0)20 7946 0832", "ada@example.test", "London, UK"), + List.of(), Optional.empty()); + + byte[] pdfBytes = render(new CvDocument(printed, List.of())); + assertThat(textOf(pdfBytes)).contains("+44 (0)20 7946 0832"); + assertThat(Set.copyOf(linkTargets(pdfBytes))) + .containsExactlyInAnyOrder("mailto:ada@example.test", "tel:+442079460832"); + } + + @Test + void aDegreeAJobAndACertificationBecomeLinksWhenTheirEntryCarriesOne() throws Exception { + List placements = new ArrayList<>(); + for (CvDocument.Placement placement : MidnightNavyFixtures.canonicalCv().placements()) { + placements.add(switch (placement.section().title()) { + case "EDUCATION" -> new CvDocument.Placement(Slot.SIDEBAR, + new EntriesSection("EDUCATION", List.of( + CvEntry.builder("MBA").subtitle("Booth").date("2018") + .link("https://example.test/booth").build()))); + case "EXPERIENCE" -> new CvDocument.Placement(Slot.MAIN, + new EntriesSection("EXPERIENCE", List.of( + CvEntry.builder("MARKETING MANAGER").subtitle("Starwave") + .place("New York, NY").date("2021") + .body("Ran the funnel.") + .link("https://example.test/starwave").build()))); + case "CERTIFICATIONS" -> new CvDocument.Placement(Slot.MAIN, + new EntriesSection("CERTIFICATIONS", List.of( + CvEntry.builder("Analytics Certified").subtitle("Issuer") + .date("2020") + .link("https://example.test/cert").build()))); + default -> placement; + }); + } + + assertThat(linkTargets(render(new CvDocument(MidnightNavyFixtures.identity(), placements)))) + .contains("https://example.test/booth") + .contains("https://example.test/starwave") + .contains("https://example.test/cert"); + } + + @Test + void anUnknownMarkIsReportedAsADataError() { + EntriesSection wrong = new EntriesSection("ACHIEVEMENTS", List.of( + CvEntry.builder("Read minds").icon("telescope").build())); + + assertThatThrownBy(() -> render(withSection(Slot.MAIN, wrong, "ACHIEVEMENTS"))) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("telescope") + .hasMessageContaining("trophy"); + } + + @Test + void aLanguageRatingRoundsToTheNearestDot() throws Exception { + // The model carries a fraction and the design shows fifths, so a rating + // between two dots has to land on one of them rather than throw. + SkillsSection odd = new SkillsSection("LANGUAGES", List.of( + new SkillGroup("LANGUAGES", List.of( + CvSkill.of("Dutch", 0.7), CvSkill.of("Greek", 0.0), + CvSkill.of("Latin", 1.0))))); + + String text = textOf(render(withSection(Slot.SIDEBAR, odd, "LANGUAGES"))); + assertThat(text).contains("Dutch").contains("Greek").contains("Latin"); + } + + @Test + void aSkillWithoutALevelDrawsAnEmptyMeter() throws Exception { + SkillsSection unrated = new SkillsSection("SKILLS", List.of( + new SkillGroup("SKILLS", List.of(CvSkill.of("Copywriting"))))); + + assertThat(textOf(render(withSection(Slot.SIDEBAR, unrated, "SKILLS")))) + .contains("Copywriting"); + } + + @Test + void anEmptyBerthTakesItsHeadingWithIt() throws Exception { + byte[] shorter = render(withoutSection("CERTIFICATIONS")); + assertThat(textOf(shorter)).doesNotContain("Google Analytics Certified"); + assertThat(compact(textOf(shorter))).doesNotContain("CERTIFICATIONS"); + } + + @Test + void aDocumentWithNothingButAnIdentityStillRenders() throws Exception { + // Every berth empty: the plate, the monogram, the name and the contact + // block, and nothing else on the sheet. + render(new CvDocument(MidnightNavyFixtures.identity(), List.of())); + } + + @Test + void aCvLongerThanTheSheetIsRefusedRatherThanCut() throws Exception { + // The body is one row and a row is atomic, so this design does not + // paginate: content past the sheet is reported with the node that could + // not fit rather than silently clipped. TimelineMinimal is the preset in + // this package built to flow. + List many = new ArrayList<>(); + for (int index = 0; index < 8; index++) { + many.add(CvEntry.builder("MARKETING MANAGER " + index) + .subtitle("Starwave Solutions") + .place("New York, NY") + .date("2021 - Present") + .body(String.join(NEWLINE, + "Develop and implement marketing strategies.", + "Lead a team across campaigns and channels.")) + .build()); + } + CvDocument longer = withSection(Slot.MAIN, + new EntriesSection("EXPERIENCE", many), "EXPERIENCE"); + + assertThatThrownBy(() -> render(longer)) + .isInstanceOf(AtomicNodeTooLargeException.class) + .hasMessageContaining("PageGrid"); + } +} diff --git a/qa/src/test/java/com/demcha/compose/document/templates/cv/presets/MidnightNavyVisualParityTest.java b/qa/src/test/java/com/demcha/compose/document/templates/cv/presets/MidnightNavyVisualParityTest.java new file mode 100644 index 000000000..69309a7cb --- /dev/null +++ b/qa/src/test/java/com/demcha/compose/document/templates/cv/presets/MidnightNavyVisualParityTest.java @@ -0,0 +1,41 @@ +package com.demcha.compose.document.templates.cv.presets; + +import com.demcha.compose.GraphCompose; +import com.demcha.compose.document.api.DocumentSession; +import com.demcha.compose.testing.visual.PdfVisualRegression; +import org.junit.jupiter.api.Test; + +import java.nio.file.Path; + +/** + * Pixel-diff visual parity gate for {@link MidnightNavy} — the canonical + * one-page CV against a checked-in baseline. + * + *

Re-blessing baselines — after a deliberate visual change, + * re-run with {@code -Dgraphcompose.visual.approve=true} and commit the updated + * PNG with the change. Baselines live under + * {@code src/test/resources/visual-baselines/cv-v2-layered/}.

+ */ +class MidnightNavyVisualParityTest { + + private static final Path BASELINE_ROOT = Path.of( + "src", "test", "resources", "visual-baselines", "cv-v2-layered"); + + private static final long PIXEL_DIFF_BUDGET = 50_000L; + private static final int PER_PIXEL_TOLERANCE = 8; + + @Test + void rendersWithinPixelDiffBudget() throws Exception { + byte[] pdfBytes; + try (DocumentSession document = GraphCompose.document().create()) { + MidnightNavy.create().compose(document, MidnightNavyFixtures.canonicalCv()); + pdfBytes = document.toPdfBytes(); + } + + PdfVisualRegression.standard() + .baselineRoot(BASELINE_ROOT) + .perPixelTolerance(PER_PIXEL_TOLERANCE) + .mismatchedPixelBudget(PIXEL_DIFF_BUDGET) + .assertMatchesBaseline("midnight_navy", pdfBytes); + } +} diff --git a/qa/src/test/java/com/demcha/compose/document/templates/cv/presets/NavySidebarFixtures.java b/qa/src/test/java/com/demcha/compose/document/templates/cv/presets/NavySidebarFixtures.java new file mode 100644 index 000000000..aeff9351d --- /dev/null +++ b/qa/src/test/java/com/demcha/compose/document/templates/cv/presets/NavySidebarFixtures.java @@ -0,0 +1,145 @@ +package com.demcha.compose.document.templates.cv.presets; + +import com.demcha.compose.document.image.DocumentImageData; +import com.demcha.compose.document.templates.core.identity.Contact; +import com.demcha.compose.document.templates.core.identity.Link; +import com.demcha.compose.document.templates.cv.data.CvDocument; +import com.demcha.compose.document.templates.cv.data.CvEntry; +import com.demcha.compose.document.templates.cv.data.CvIdentity; +import com.demcha.compose.document.templates.cv.data.EntriesSection; +import com.demcha.compose.document.templates.cv.data.ParagraphSection; +import com.demcha.compose.document.templates.cv.data.RowStyle; +import com.demcha.compose.document.templates.cv.data.RowsSection; +import com.demcha.compose.document.templates.cv.data.SkillGroup; +import com.demcha.compose.document.templates.cv.data.SkillsSection; +import com.demcha.compose.document.templates.cv.data.Slot; + + +/** + * The canonical fixture for {@link NavySidebar} — the document the design was + * drawn around, and the one both gates measure. + * + *

One fixture feeds the layout snapshot and the pixel baseline, so a + * change that moves the sheet moves both together instead of leaving one + * gate measuring a document the other no longer renders.

+ * + *

The portrait is drawn here rather than committed: a photograph in the + * repository would be a licensed asset guarding a layout, and what the gates + * need is only that the disc has something in it. It is deterministic, so + * the pixel baseline is stable.

+ */ +public final class NavySidebarFixtures { + + private NavySidebarFixtures() { + } + + /** + * The canonical CV: a portrait, four contact channels, two degrees, eight + * skills, three languages, three roles, three achievements and three + * certifications — one page. + * + * @return the fixture document + */ + public static CvDocument canonicalCv() { + return CvDocument.builder() + .identity(CvIdentity.builder() + .name("Olivia", "Martinez") + .jobTitle("Marketing Manager") + .contact(new Contact("+1 (555) 123-4567", + "your.email@gmail.com", + "New York, NY, USA")) + .link(new Link("linkedin.com/in/yourname", + "https://linkedin.com/in/yourname")) + .portrait(portrait()) + .build()) + .section(Slot.MAIN, new ParagraphSection("SUMMARY", + "Results-driven marketing professional with 5+ years of experience" + + " developing and executing data-driven marketing strategies" + + " that drive brand growth, increase engagement, and deliver" + + " measurable results. Skilled in digital marketing, market" + + " research, and cross-functional collaboration.")) + .section(Slot.SIDEBAR, EntriesSection.builder("Education") + .entry(CvEntry.builder("Master of Science in Marketing") + .subtitle("New York University") + .date("2020 - 2022") + .place("New York, NY") + .build()) + .entry(CvEntry.builder("Bachelor of Business Administration") + .subtitle("University of California") + .date("2016 - 2020") + .place("Los Angeles, CA") + .build()) + .build()) + .section(Slot.SIDEBAR, SkillsSection.of("Skills", SkillGroup.of("Core", + "Digital Marketing", + "Data Analysis", + "Project Management", + "SEO / SEM", + "Microsoft Office Suite", + "Google Analytics", + "Social Media Strategy", + "Content Marketing"))) + .section(Slot.SIDEBAR, RowsSection.builder("Languages", RowStyle.PLAIN) + .row("English", "Native") + .row("Spanish", "Advanced") + .row("French", "Intermediate") + .build()) + .section(Slot.MAIN, EntriesSection.builder("Experience") + .entry("Marketing Manager", + "BrightWave Solutions, New York, NY", + "Jan 2022 - Present", + String.join("\n", + "Develop and implement integrated marketing campaigns" + + " that increased brand awareness by 35% and" + + " lead generation by 50%.", + "Manage a team of 4 marketing specialists and" + + " collaborate with sales, product, and design" + + " teams to align strategies.", + "Analyze campaign performance using Google Analytics" + + " and other tools to optimize ROI and" + + " reporting.")) + .entry("Marketing Specialist", + "BrightWave Solutions, New York, NY", + "Jun 2020 - Dec 2021", + String.join("\n", + "Executed digital marketing campaigns across SEO, PPC," + + " email, and social media channels.", + "Conducted market research and competitor analysis to" + + " identify opportunities and inform strategy.", + "Created engaging content for blogs, newsletters, and" + + " social media that increased engagement by" + + " 40%.")) + .entry("Marketing Intern", + "BrightWave Solutions, New York, NY", + "Jan 2020 - May 2020", + String.join("\n", + "Assisted in planning and executing marketing" + + " initiatives and events.", + "Supported content creation and social media" + + " management.", + "Analyzed data and prepared reports to support the" + + " marketing team.")) + .build()) + .section(Slot.MAIN, new ParagraphSection("Achievements", String.join("\n", + "Increased website traffic by 60% within one year through SEO and" + + " content strategy.", + "Launched a successful email marketing campaign with a 25% conversion" + + " rate.", + "Received \u201cEmployee of the Year\u201d award in 2023 for outstanding" + + " performance."))) + .section(Slot.MAIN, new ParagraphSection("Certifications", String.join("\n", + "Google Analytics Individual Qualification (GAIQ)", + "HubSpot Content Marketing Certification", + "Facebook Blueprint Certification"))) + .build(); + } + + /** + * The silhouette the CV fixtures share. + * + * @return the portrait image data + */ + static DocumentImageData portrait() { + return CvFixturePortrait.silhouette(); + } +} diff --git a/qa/src/test/java/com/demcha/compose/document/templates/cv/presets/NavySidebarLayoutSnapshotTest.java b/qa/src/test/java/com/demcha/compose/document/templates/cv/presets/NavySidebarLayoutSnapshotTest.java new file mode 100644 index 000000000..5af914f79 --- /dev/null +++ b/qa/src/test/java/com/demcha/compose/document/templates/cv/presets/NavySidebarLayoutSnapshotTest.java @@ -0,0 +1,29 @@ +package com.demcha.compose.document.templates.cv.presets; + +import com.demcha.compose.GraphCompose; +import com.demcha.compose.document.api.DocumentSession; +import com.demcha.compose.document.templates.TemplateTestSupport; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Exact layout snapshot gate for {@link NavySidebar} - freezes the resolved + * geometry of the canonical one-page CV, which is what catches a shift the + * pixel budget is too coarse to see. + * + *

Refresh with {@code -Dgraphcompose.updateSnapshots=true} after a + * deliberate layout change, and commit the JSON with the change.

+ */ +class NavySidebarLayoutSnapshotTest { + + @Test + void canonicalCvMatchesLayoutSnapshot() throws Exception { + try (DocumentSession session = GraphCompose.document().create()) { + NavySidebar.create().compose(session, NavySidebarFixtures.canonicalCv()); + assertThat(session.layoutSnapshot().totalPages()).isEqualTo(1); + TemplateTestSupport.assertCanonicalSnapshot( + session, "navy_sidebar_layout", "cv-v2"); + } + } +} diff --git a/qa/src/test/java/com/demcha/compose/document/templates/cv/presets/NavySidebarSmokeTest.java b/qa/src/test/java/com/demcha/compose/document/templates/cv/presets/NavySidebarSmokeTest.java new file mode 100644 index 000000000..d0d245e81 --- /dev/null +++ b/qa/src/test/java/com/demcha/compose/document/templates/cv/presets/NavySidebarSmokeTest.java @@ -0,0 +1,222 @@ +package com.demcha.compose.document.templates.cv.presets; + +import com.demcha.compose.GraphCompose; +import com.demcha.compose.document.api.DocumentSession; +import com.demcha.compose.document.exceptions.AtomicNodeTooLargeException; +import com.demcha.compose.document.templates.api.DocumentTemplate; +import com.demcha.compose.document.templates.core.identity.Contact; +import com.demcha.compose.document.templates.core.identity.Link; +import com.demcha.compose.document.templates.cv.data.CvDocument; +import com.demcha.compose.document.templates.cv.data.CvEntry; +import com.demcha.compose.document.templates.cv.data.CvIdentity; +import com.demcha.compose.document.templates.cv.data.CvSkill; +import com.demcha.compose.document.templates.cv.data.EntriesSection; +import com.demcha.compose.document.templates.cv.data.ParagraphSection; +import com.demcha.compose.document.templates.cv.data.RowStyle; +import com.demcha.compose.document.templates.cv.data.RowsSection; +import com.demcha.compose.document.templates.cv.data.SkillGroup; +import com.demcha.compose.document.templates.cv.data.SkillsSection; +import org.apache.pdfbox.Loader; +import org.apache.pdfbox.pdmodel.PDDocument; +import org.apache.pdfbox.pdmodel.PDPage; +import org.apache.pdfbox.pdmodel.interactive.action.PDActionURI; +import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotation; +import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationLink; +import org.apache.pdfbox.text.PDFTextStripper; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +/** + * Smoke test for {@link NavySidebar} — proves the preset renders a + * {@link CvDocument} end-to-end with its packaged marks and the portrait + * from the identity, composes without one, and pins the limits the class + * documents: the sheet holds one page, and the fields this design has no + * place for are not drawn. + */ +class NavySidebarSmokeTest { + + private static byte[] render(CvDocument doc) throws Exception { + // The preset owns its page geometry, so the session starts unconfigured. + try (DocumentSession session = GraphCompose.document().create()) { + NavySidebar.create().compose(session, doc); + assertThat(session.roots()).isNotEmpty(); + byte[] pdfBytes = session.toPdfBytes(); + assertThat(pdfBytes).isNotEmpty(); + return pdfBytes; + } + } + + private static String textOf(byte[] pdfBytes) throws Exception { + try (PDDocument document = Loader.loadPDF(pdfBytes)) { + return new PDFTextStripper().getText(document); + } + } + + private static List linkUris(byte[] pdfBytes) throws Exception { + List uris = new ArrayList<>(); + try (PDDocument document = Loader.loadPDF(pdfBytes)) { + for (PDPage page : document.getPages()) { + for (PDAnnotation annotation : page.getAnnotations()) { + if (annotation instanceof PDAnnotationLink link + && link.getAction() instanceof PDActionURI action) { + uris.add(action.getURI()); + } + } + } + } + return uris; + } + + @Test + void exposesStableIdentity() { + DocumentTemplate template = NavySidebar.create(); + assertThat(template.id()).isEqualTo(NavySidebar.ID); + assertThat(template.displayName()).isEqualTo(NavySidebar.DISPLAY_NAME); + } + + @Test + void canonicalRenderCarriesTheUntrackedText() throws Exception { + // The headings, the name and the role are set letter by letter to + // reach their tracking, so they arrive on the text layer spaced out. + // What is asserted here is the text drawn as written. + String text = textOf(render(NavySidebarFixtures.canonicalCv())); + assertThat(text) + .contains("your.email@gmail.com") + .contains("New York, NY, USA") + .contains("linkedin.com/in/yourname") + .contains("Results-driven marketing professional") + .contains("New York University") + .contains("Digital Marketing") + .contains("Native") + .contains("BrightWave Solutions, New York, NY") + .contains("Increased website traffic by 60%") + .contains("HubSpot Content Marketing Certification"); + } + + @Test + void channelsReachThePdfAsLinks() throws Exception { + // The dial and mail targets are built from the values, so nothing in + // the document carries them and nothing but the PDF can show them. + // They are annotations, so they move no pixel and no layout node — + // which is why the parity gates cannot see them either. + assertThat(linkUris(render(NavySidebarFixtures.canonicalCv()))) + .contains("tel:+15551234567", + "mailto:your.email@gmail.com", + "https://linkedin.com/in/yourname"); + } + + @Test + void rendersOneCanonicalPage() throws Exception { + try (PDDocument document = + Loader.loadPDF(render(NavySidebarFixtures.canonicalCv()))) { + assertThat(document.getNumberOfPages()).isEqualTo(1); + } + } + + @Test + void rendersWithoutAPortraitOrAJobTitle() throws Exception { + // No photograph: the ring is drawn around an empty navy disc rather + // than left as a hole in the column. And no role: that line is + // written letter by letter to reach its tracking, so a blank one + // reaches the engine as a paragraph holding no runs at all. + String text = textOf(render(CvDocument.builder() + .identity(CvIdentity.builder() + .name("Ada", "Lovelace") + .contact(new Contact("+44 20 7946 0000", "empty@example.com", + "London, UK")) + .build()) + .build())); + assertThat(text).contains("empty@example.com"); + } + + @Test + void degreesAndTitlesAreDrawnInCapitals() throws Exception { + // This design upper-cases for the author — the name, the role, each + // degree and every heading — so a document written in title case + // still reads as the sheet it is. + String text = textOf(render(CvDocument.builder() + .identity(identity()) + .section(EntriesSection.builder("Education") + .entry(CvEntry.builder("Master of Science") + .subtitle("Some University") + .date("2019") + .place("Berlin") + .build()) + .build()) + .build())); + assertThat(text).contains("MASTER OF SCIENCE").doesNotContain("Master of Science"); + } + + @Test + void drawsNothingTheDesignHasNoPlaceFor() throws Exception { + // A berth is filled once, and this sheet has nowhere to put a skill + // level, a group category or the summary's own title. + String text = textOf(render(CvDocument.builder() + .identity(identity()) + .section(new ParagraphSection("Summary title here", "The opening paragraph.")) + .section(SkillsSection.of("Skills", + new SkillGroup("Category name here", List.of(CvSkill.of("Java", 0.8))))) + .section(new ParagraphSection("Achievements", "Shipped a thing.")) + .section(new ParagraphSection("More achievements", "Second block here.")) + .build())); + assertThat(text) + .contains("The opening paragraph.") + .contains("Java") + .contains("Shipped a thing.") + .doesNotContain("Summary title here") + .doesNotContain("Category name here") + .doesNotContain("Second block here."); + } + + @Test + void writesTheProficiencyTheDocumentCarries() throws Exception { + // Languages are rows rather than levelled skills precisely so the + // words survive; the row style is not read. + String text = textOf(render(CvDocument.builder() + .identity(identity()) + .section(RowsSection.builder("Languages", RowStyle.BULLETED_STACKED) + .row("Portuguese", "Conversational") + .build()) + .build())); + assertThat(text).contains("Portuguese").contains("Conversational"); + } + + @Test + void refusesACvLongerThanTheSheet() { + // The sheet holds one page: the two columns are a single row, and a + // row is atomic. A CV past that raises rather than flowing. + EntriesSection.Builder experience = EntriesSection.builder("Experience"); + for (int i = 0; i < 14; i++) { + experience.entry("Role " + i, "Employer " + i + ", City", "2010 - 2011", + String.join("\n", + "Did a substantial thing that took a full line of text here.", + "Did a second substantial thing, also a full line of text.", + "And a third, so each role occupies real vertical space.")); + } + CvDocument doc = CvDocument.builder() + .identity(identity()) + .section(experience.build()) + .build(); + try (DocumentSession session = GraphCompose.document().create()) { + NavySidebar.create().compose(session, doc); + assertThatThrownBy(session::toPdfBytes) + .isInstanceOf(AtomicNodeTooLargeException.class) + .hasMessageContaining("PageGrid"); + } + } + + private static CvIdentity identity() { + return CvIdentity.builder() + .name("Ada", "Lovelace") + .jobTitle("Analyst") + .contact(new Contact("+44 20 7946 0000", "empty@example.com", "London, UK")) + .link(new Link("linkedin.com/in/ada", "https://linkedin.com/in/ada")) + .portrait(NavySidebarFixtures.portrait()) + .build(); + } +} diff --git a/qa/src/test/java/com/demcha/compose/document/templates/cv/presets/NavySidebarVisualParityTest.java b/qa/src/test/java/com/demcha/compose/document/templates/cv/presets/NavySidebarVisualParityTest.java new file mode 100644 index 000000000..e5dc3d6e1 --- /dev/null +++ b/qa/src/test/java/com/demcha/compose/document/templates/cv/presets/NavySidebarVisualParityTest.java @@ -0,0 +1,41 @@ +package com.demcha.compose.document.templates.cv.presets; + +import com.demcha.compose.GraphCompose; +import com.demcha.compose.document.api.DocumentSession; +import com.demcha.compose.testing.visual.PdfVisualRegression; +import org.junit.jupiter.api.Test; + +import java.nio.file.Path; + +/** + * Pixel-diff visual parity gate for {@link NavySidebar} - the canonical + * one-page CV against a checked-in baseline. + * + *

Re-blessing baselines - after a deliberate visual + * change, re-run with {@code -Dgraphcompose.visual.approve=true} and commit + * the updated PNG with the change. Baselines live under + * {@code src/test/resources/visual-baselines/cv-v2-layered/}.

+ */ +class NavySidebarVisualParityTest { + + private static final Path BASELINE_ROOT = Path.of( + "src", "test", "resources", "visual-baselines", "cv-v2-layered"); + + private static final long PIXEL_DIFF_BUDGET = 50_000L; + private static final int PER_PIXEL_TOLERANCE = 8; + + @Test + void rendersWithinPixelDiffBudget() throws Exception { + byte[] pdfBytes; + try (DocumentSession document = GraphCompose.document().create()) { + NavySidebar.create().compose(document, NavySidebarFixtures.canonicalCv()); + pdfBytes = document.toPdfBytes(); + } + + PdfVisualRegression.standard() + .baselineRoot(BASELINE_ROOT) + .perPixelTolerance(PER_PIXEL_TOLERANCE) + .mismatchedPixelBudget(PIXEL_DIFF_BUDGET) + .assertMatchesBaseline("navy_sidebar", pdfBytes); + } +} diff --git a/qa/src/test/java/com/demcha/compose/document/templates/cv/presets/OrangeOpsFixtures.java b/qa/src/test/java/com/demcha/compose/document/templates/cv/presets/OrangeOpsFixtures.java new file mode 100644 index 000000000..0808db6eb --- /dev/null +++ b/qa/src/test/java/com/demcha/compose/document/templates/cv/presets/OrangeOpsFixtures.java @@ -0,0 +1,246 @@ +package com.demcha.compose.document.templates.cv.presets; + +import com.demcha.compose.document.templates.core.identity.Contact; +import com.demcha.compose.document.templates.core.identity.Link; +import com.demcha.compose.document.templates.cv.data.CvDocument; +import com.demcha.compose.document.templates.cv.data.CvEntry; +import com.demcha.compose.document.templates.cv.data.CvIdentity; +import com.demcha.compose.document.templates.cv.data.CvName; +import com.demcha.compose.document.templates.cv.data.EntriesSection; +import com.demcha.compose.document.templates.cv.data.ParagraphSection; +import com.demcha.compose.document.templates.cv.data.SkillGroup; +import com.demcha.compose.document.templates.cv.data.SkillsSection; +import com.demcha.compose.document.templates.cv.data.Slot; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +/** + * Shared fixture data for the {@link OrangeOps} gates — the SAME document + * feeds the pixel parity test and the layout snapshot test, so a geometry + * shift the pixel budget absorbs still trips the exact snapshot, and vice + * versa. + */ +final class OrangeOpsFixtures { + + /** The break a body stacks its lines on. */ + private static final String NEWLINE = String.valueOf((char) 10); + + private OrangeOpsFixtures() { + } + + /** + * The canonical one-page CV — four contact items including one link, + * eleven skills, four achievement cards, a degree, four certifications, + * the opening prose, three roles, four metrics and four closing lines. + */ + static CvDocument canonicalCv() { + List placements = new ArrayList<>(); + placements.add(new CvDocument.Placement(Slot.SIDEBAR, skills())); + placements.add(new CvDocument.Placement(Slot.SIDEBAR, achievements())); + placements.add(new CvDocument.Placement(Slot.SIDEBAR, education())); + placements.add(new CvDocument.Placement(Slot.SIDEBAR, certifications())); + placements.add(new CvDocument.Placement(Slot.MAIN, profile())); + placements.add(new CvDocument.Placement(Slot.MAIN, experience())); + placements.add(new CvDocument.Placement(Slot.MAIN, metrics())); + placements.add(new CvDocument.Placement(Slot.MAIN, additional())); + return new CvDocument(identity(), placements); + } + + static CvIdentity identity() { + return new CvIdentity( + CvName.of("Marcus", "Bennett"), + "Warehouse Operations Supervisor", + new Contact("+44 7700 900123", + "marcus.bennett@email.com", + "Birmingham, West Midlands, UK"), + List.of(new Link("LinkedIn", "https://www.linkedin.com/in/marcusbennett")), + Optional.empty()); + } + + /** The skills, as one flat group — the design draws no group names. */ + static SkillsSection skills() { + return new SkillsSection("KEY SKILLS", List.of(SkillGroup.ofNames("KEY SKILLS", List.of( + "Warehouse Operations Management", + "Team Leadership & Development", + "Inventory Control & Accuracy", + "Logistics & Freight Coordination", + "Order Fulfilment & Dispatch", + "KPI Monitoring & Reporting", + "Lean Process Improvement", + "Health & Safety Compliance", + "WMS & ERP Systems", + "Problem Solving & Decision Making", + "Stakeholder Communication")))); + } + + /** The achievement cards: a mark, a title and a body each. */ + static EntriesSection achievements() { + return new EntriesSection("ACHIEVEMENTS", List.of( + CvEntry.builder("15% PRODUCTIVITY INCREASE") + .icon("achievement-productivity") + .body("Improved warehouse productivity by 15% through workflow " + + "redesign and performance management.") + .build(), + CvEntry.builder("99.2% INVENTORY ACCURACY") + .icon("achievement-accuracy") + .body("Maintained inventory accuracy above 99% for 12 " + + "consecutive months through cycle count discipline and " + + "process control.") + .build(), + CvEntry.builder("ZERO LOST TIME INCIDENTS") + .icon("achievement-safety") + .body("Led a safety-first culture resulting in zero lost time " + + "incidents over 24 months across operations.") + .build(), + CvEntry.builder("\u00a3280K COST SAVINGS") + .icon("achievement-savings") + .body("Delivered \u00a3280K in annual savings by reducing waste, " + + "lowering overtime and renegotiating carrier contracts.") + .build())); + } + + /** The degree, its institution, place and years one line each. */ + static EntriesSection education() { + return new EntriesSection("EDUCATION", List.of( + CvEntry.builder("BSc (Hons) Logistics & Supply Chain Management") + .icon("graduation") + .body(String.join(NEWLINE, + "De Montfort University", + "Leicester, UK", + "2011 \u2013 2014")) + .build())); + } + + /** The certifications: a title over its issuer. */ + static EntriesSection certifications() { + return new EntriesSection("CERTIFICATIONS", List.of( + CvEntry.builder("IOSH Managing Safely") + .subtitle("Institution of Occupational Safety and Health \u2013 2021") + .build(), + CvEntry.builder("Lean Six Sigma Yellow Belt") + .subtitle("The Knowledge Academy \u2013 2020") + .build(), + CvEntry.builder("Counterbalance Forklift Truck Instructor") + .subtitle("RTITB \u2013 2019") + .build(), + CvEntry.builder("First Aid at Work") + .subtitle("St John Ambulance \u2013 2019") + .build())); + } + + /** The opening prose. */ + static ParagraphSection profile() { + return new ParagraphSection("PROFESSIONAL PROFILE", + "Results-driven Warehouse Operations Supervisor with 8+ " + + "years of experience leading high-performing teams and " + + "optimising warehouse performance in fast-paced " + + "distribution environments. Proven track record of " + + "improving operational efficiency, ensuring inventory " + + "accuracy, and delivering excellent service levels while " + + "maintaining a strong focus on health and safety. Adept " + + "at using data to drive decisions, streamline processes, " + + "and achieve measurable results."); + } + + /** The roles held, each a headline over its bullets. */ + static EntriesSection experience() { + return new EntriesSection("WORK EXPERIENCE", List.of( + CvEntry.builder("Warehouse Operations Supervisor") + .subtitle("ExpressLink Distribution Ltd | Birmingham, UK") + .date("Mar 2021 \u2013 Present") + .body(String.join(NEWLINE, + "Oversee daily warehouse operations across a 150,000 sq " + + "ft facility, managing a team of 45 staff across inbound, " + + "storage, picking, packing and dispatch.", + "Ensure timely and accurate order fulfilment, achieving " + + "98%+ on-time delivery.", + "Monitor and maintain inventory accuracy through cycle " + + "counts, audits and investigation of variances.", + "Analyse KPIs and operational data to identify trends and " + + "implement improvements.", + "Coordinate with transport and customer service teams to " + + "resolve delivery issues and improve service levels.", + "Drive a culture of safety and accountability; deliver " + + "regular training and ensure full compliance with H&S " + + "regulations.", + "Lead continuous improvement initiatives using Lean " + + "principles to reduce waste and enhance productivity.")) + .build(), + CvEntry.builder("Warehouse Team Leader") + .subtitle("Midland Gate Logistics | Coventry, UK") + .date("May 2017 \u2013 Feb 2021") + .body(String.join(NEWLINE, + "Led a team of 25 warehouse operatives across day-to-day " + + "operations.", + "Managed inbound receipts, putaway, picking and shipping " + + "activities.", + "Maintained accurate stock records and completed regular " + + "cycle counts.", + "Supported the implementation of a new WMS, improving " + + "stock visibility and reporting.", + "Reduced picking errors by 23% through training and " + + "process optimisation.")) + .build(), + CvEntry.builder("Warehouse Coordinator") + .subtitle("TotalSupply Solutions | Northampton, UK") + .date("Jun 2015 \u2013 Apr 2017") + .body(String.join(NEWLINE, + "Coordinated daily operations and allocated tasks to " + + "warehouse staff.", + "Monitored stock levels and initiated replenishment to " + + "avoid stockouts.", + "Assisted with KPI reporting and performance reviews.", + "Ensured compliance with company policies and health & " + + "safety standards.")) + .build())); + } + + /** + * The metric strip. The parenthetical is part of the berth title: the + * preset splits it off and sets it smaller. + */ + static EntriesSection metrics() { + return new EntriesSection("KEY KPI SNAPSHOT (Recent 12 Months)", List.of( + CvEntry.builder("15%") + .icon("kpi-productivity") + .body("Productivity" + NEWLINE + "Increase") + .build(), + CvEntry.builder("99.2%") + .icon("kpi-accuracy") + .body("Inventory" + NEWLINE + "Accuracy") + .build(), + CvEntry.builder("98.4%") + .icon("kpi-delivery") + .body("On-Time" + NEWLINE + "Delivery") + .build(), + CvEntry.builder("0") + .icon("kpi-safety") + .body("Lost Time" + NEWLINE + "Incidents") + .build())); + } + + /** The closing lines: a mark, a label and its value. */ + static EntriesSection additional() { + return new EntriesSection("ADDITIONAL INFORMATION", List.of( + CvEntry.builder("Systems:") + .icon("systems") + .body("SAP EWM, Manhattan WMS, Microsoft Dynamics, Excel " + + "(Advanced)") + .build(), + CvEntry.builder("Languages:") + .icon("languages") + .body("English (Native)") + .build(), + CvEntry.builder("Driving Licence:") + .icon("driving") + .body("Full UK Driving Licence") + .build(), + CvEntry.builder("Interests:") + .icon("interests") + .body("Strength training, Motorsport, Volunteering with local " + + "food banks") + .build())); + } +} diff --git a/qa/src/test/java/com/demcha/compose/document/templates/cv/presets/OrangeOpsLayoutSnapshotTest.java b/qa/src/test/java/com/demcha/compose/document/templates/cv/presets/OrangeOpsLayoutSnapshotTest.java new file mode 100644 index 000000000..2356ab174 --- /dev/null +++ b/qa/src/test/java/com/demcha/compose/document/templates/cv/presets/OrangeOpsLayoutSnapshotTest.java @@ -0,0 +1,34 @@ +package com.demcha.compose.document.templates.cv.presets; + +import com.demcha.compose.GraphCompose; +import com.demcha.compose.document.api.DocumentSession; +import com.demcha.compose.document.templates.TemplateTestSupport; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Exact layout snapshot gate for {@link OrangeOps} — freezes the resolved + * geometry of the canonical one-page CV, which the pixel budget cannot see: a + * small column or spacing shift stays under the visual-diff budget but changes + * the snapshot. + * + *

The preset owns its page geometry, so the session starts unconfigured + * apart from the display family it does not carry.

+ * + *

Refresh with {@code -Dgraphcompose.updateSnapshots=true} after a + * deliberate layout change, and commit the JSON with the change.

+ */ +class OrangeOpsLayoutSnapshotTest { + + @Test + void canonicalCvMatchesLayoutSnapshot() throws Exception { + try (DocumentSession session = GraphCompose.document().create()) { + OrangeOpsTestFont.register(session); + OrangeOps.create().compose(session, OrangeOpsFixtures.canonicalCv()); + assertThat(session.layoutSnapshot().totalPages()).isEqualTo(1); + TemplateTestSupport.assertCanonicalSnapshot( + session, "orange_ops_layout", "cv-v2"); + } + } +} diff --git a/qa/src/test/java/com/demcha/compose/document/templates/cv/presets/OrangeOpsSmokeTest.java b/qa/src/test/java/com/demcha/compose/document/templates/cv/presets/OrangeOpsSmokeTest.java new file mode 100644 index 000000000..7a21e2c0e --- /dev/null +++ b/qa/src/test/java/com/demcha/compose/document/templates/cv/presets/OrangeOpsSmokeTest.java @@ -0,0 +1,368 @@ +package com.demcha.compose.document.templates.cv.presets; + +import com.demcha.compose.GraphCompose; +import com.demcha.compose.document.api.DocumentSession; +import com.demcha.compose.document.templates.api.DocumentTemplate; +import com.demcha.compose.document.templates.core.identity.Contact; +import com.demcha.compose.document.templates.core.identity.Link; +import com.demcha.compose.document.templates.cv.data.CvDocument; +import com.demcha.compose.document.templates.cv.data.CvEntry; +import com.demcha.compose.document.templates.cv.data.CvIdentity; +import com.demcha.compose.document.templates.cv.data.CvName; +import com.demcha.compose.document.templates.cv.data.CvSection; +import com.demcha.compose.document.templates.cv.data.EntriesSection; +import com.demcha.compose.document.templates.cv.data.Slot; +import org.apache.pdfbox.Loader; +import org.apache.pdfbox.pdmodel.PDDocument; +import org.apache.pdfbox.pdmodel.PDPage; +import org.apache.pdfbox.pdmodel.interactive.action.PDActionURI; +import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotation; +import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationLink; +import org.apache.pdfbox.text.PDFTextStripper; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.Set; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +/** + * Smoke test for {@link OrangeOps} — proves the preset renders a + * {@link CvDocument} end-to-end with its packaged marks, uppercases the name it + * is given, sets a heading's parenthetical apart from the heading, reports an + * unknown mark as a data error, drops a berth nobody filled together with its + * join rule, and carries the channels and the entry titles as link annotations, + * which move no pixel and no layout node so neither gate would notice them + * going missing. + */ +class OrangeOpsSmokeTest { + + /** The break a body stacks its lines on. */ + private static final String NEWLINE = String.valueOf((char) 10); + + private static byte[] render(CvDocument doc) throws Exception { + // The preset owns its page geometry, so the session starts unconfigured + // apart from the display family the preset names but does not carry. + try (DocumentSession session = GraphCompose.document().create()) { + OrangeOpsTestFont.register(session); + OrangeOps.create().compose(session, doc); + assertThat(session.roots()).isNotEmpty(); + byte[] pdfBytes = session.toPdfBytes(); + assertThat(pdfBytes).isNotEmpty(); + return pdfBytes; + } + } + + private static String textOf(byte[] pdfBytes) throws Exception { + try (PDDocument document = Loader.loadPDF(pdfBytes)) { + return new PDFTextStripper().getText(document); + } + } + + /** The text as one line, so an assertion is about wording, not wrapping. */ + private static String unwrapped(String text) { + return text.replaceAll("(?U)\\s+", " "); + } + + private static List linkTargets(byte[] pdfBytes) throws Exception { + List targets = new ArrayList<>(); + try (PDDocument document = Loader.loadPDF(pdfBytes)) { + for (PDPage page : document.getPages()) { + for (PDAnnotation annotation : page.getAnnotations()) { + if (annotation instanceof PDAnnotationLink link + && link.getAction() instanceof PDActionURI uri) { + targets.add(uri.getURI()); + } + } + } + } + return targets; + } + + /** The canonical document with one section replaced. */ + private static CvDocument withSection(Slot slot, EntriesSection replacement, + String replacedTitle) { + List placements = new ArrayList<>(); + for (CvDocument.Placement placement : OrangeOpsFixtures.canonicalCv().placements()) { + if (placement.section().title().equals(replacedTitle)) { + placements.add(new CvDocument.Placement(slot, replacement)); + } else { + placements.add(placement); + } + } + return new CvDocument(OrangeOpsFixtures.identity(), placements); + } + + /** The canonical document with one section left out entirely. */ + private static CvDocument withoutSection(String droppedTitle) { + List placements = new ArrayList<>(); + for (CvDocument.Placement placement : OrangeOpsFixtures.canonicalCv().placements()) { + if (!placement.section().title().equals(droppedTitle)) { + placements.add(placement); + } + } + return new CvDocument(OrangeOpsFixtures.identity(), placements); + } + + @Test + void exposesStableIdentity() { + DocumentTemplate template = OrangeOps.create(); + assertThat(template.id()).isEqualTo(OrangeOps.ID); + assertThat(template.displayName()).isEqualTo(OrangeOps.DISPLAY_NAME); + } + + @Test + void namesTheDisplayFamilyItDoesNotCarry() { + // A caller has to register something, and this is the name to register + // it under; the constant is the contract, so it is pinned. + assertThat(OrangeOps.DISPLAY_FONT.name()).isEqualTo("Oswald"); + } + + @Test + void rendersCanonicalCvWithPackagedMarks() throws Exception { + render(OrangeOpsFixtures.canonicalCv()); + } + + @Test + void canonicalRenderCarriesEveryBlocksText() throws Exception { + String text = textOf(render(OrangeOpsFixtures.canonicalCv())); + assertThat(text) + .contains("marcus.bennett@email.com") + .contains("Birmingham, West Midlands, UK") + .contains("Warehouse Operations Management") + .contains("ZERO LOST TIME INCIDENTS") + .contains("De Montfort University") + .contains("IOSH Managing Safely") + .contains("Results-driven Warehouse Operations Supervisor") + .contains("ExpressLink Distribution Ltd") + .contains("Productivity") + .contains("SAP EWM, Manhattan WMS"); + } + + @Test + void theNameIsSetInTwoTonesAndUppercased() throws Exception { + // The fixture writes the name as a person would; the design sets it in + // capitals, so the preset is what uppercases it. Both names are runs of + // one paragraph, so the gap between them is a real word space. + String text = textOf(render(OrangeOpsFixtures.canonicalCv())); + assertThat(text.lines().map(String::strip).toList()) + .anyMatch(line -> line.contains("MARCUS") && line.contains("BENNETT")); + assertThat(text).doesNotContain("Marcus Bennett"); + } + + @Test + void theRoleBarIsSetInCapitals() throws Exception { + String text = textOf(render(OrangeOpsFixtures.canonicalCv())); + assertThat(text).contains("WAREHOUSE OPERATIONS SUPERVISOR"); + } + + @Test + void aHeadingsParentheticalIsDrawnBesideIt() throws Exception { + // The berth's title carries both; the preset splits at the bracket and + // sets what follows smaller, so both halves reach the page. + String text = unwrapped(textOf(render(OrangeOpsFixtures.canonicalCv()))); + assertThat(text).contains("KEY KPI SNAPSHOT (Recent 12 Months)"); + } + + @Test + void contactChannelsAndLinksAreClickable() throws Exception { + List targets = linkTargets(render(OrangeOpsFixtures.canonicalCv())); + assertThat(targets) + .contains("mailto:marcus.bennett@email.com") + .contains("tel:+447700900123") + .contains("https://www.linkedin.com/in/marcusbennett"); + } + + @Test + void aLinkShowsItsLabelAndHidesItsAddress() throws Exception { + // The strip draws the label, so its width is the same whatever the + // profile behind it is called; the address is reachable, not written. + byte[] pdfBytes = render(OrangeOpsFixtures.canonicalCv()); + assertThat(textOf(pdfBytes)) + .contains("LinkedIn") + .doesNotContain("linkedin.com/in/marcusbennett"); + assertThat(linkTargets(pdfBytes)) + .contains("https://www.linkedin.com/in/marcusbennett"); + } + + @Test + void aLinkToSomethingOtherThanANetworkTakesTheGlobe() throws Exception { + // The globe is a packaged mark like any other, so a missing file would + // fail the render rather than quietly dropping the item. + CvIdentity site = new CvIdentity(CvName.of("Ada", "Lovelace"), "Analyst", + new Contact("+44 20 7946 0000", "ada@example.test", "London, UK"), + List.of(new Link("Portfolio", "https://ada.example.test")), Optional.empty()); + + byte[] pdfBytes = render(new CvDocument(site, List.of())); + assertThat(textOf(pdfBytes)).contains("Portfolio").doesNotContain("ada.example.test"); + assertThat(linkTargets(pdfBytes)).contains("https://ada.example.test"); + } + + @Test + void metricsWithUnevenCaptionsStillShareOneGrid() throws Exception { + // Every metric gets a row per caption line the longest one has, so the + // numbers stay on one baseline and a shorter caption leaves its lower + // rows empty rather than lifting its neighbours. + EntriesSection uneven = new EntriesSection("KEY KPI SNAPSHOT", List.of( + CvEntry.builder("15%").icon("kpi-productivity") + .body("Productivity" + NEWLINE + "Increase").build(), + CvEntry.builder("0").icon("kpi-safety").body("Incidents").build())); + + String text = textOf(render(withSection(Slot.MAIN, uneven, "KEY KPI SNAPSHOT " + + "(Recent 12 Months)"))); + assertThat(text).contains("Productivity").contains("Incidents"); + } + + @Test + void aTrunkPrefixIsNotDialled() throws Exception { + // "(0)" is for a domestic dialler; left in the digits it would make a + // number that reaches nobody. + CvIdentity printed = new CvIdentity(CvName.of("Ada", "Lovelace"), "Analyst", + new Contact("+44 (0)20 7946 0832", "ada@example.test", "London, UK"), + List.of(), Optional.empty()); + + byte[] pdfBytes = render(new CvDocument(printed, List.of())); + assertThat(textOf(pdfBytes)).contains("+44 (0)20 7946 0832"); + assertThat(Set.copyOf(linkTargets(pdfBytes))) + .containsExactlyInAnyOrder("mailto:ada@example.test", "tel:+442079460832"); + } + + @Test + void anAchievementAJobAndTheDegreeBecomeLinksWhenTheirEntryCarriesOne() throws Exception { + List placements = new ArrayList<>(); + for (CvDocument.Placement placement : OrangeOpsFixtures.canonicalCv().placements()) { + CvSection section = placement.section(); + placements.add(switch (section.title()) { + case "ACHIEVEMENTS" -> new CvDocument.Placement(Slot.SIDEBAR, + new EntriesSection("ACHIEVEMENTS", List.of( + CvEntry.builder("15% PRODUCTIVITY INCREASE") + .icon("achievement-productivity") + .body("Workflow redesign.") + .link("https://example.test/award") + .build()))); + case "EDUCATION" -> new CvDocument.Placement(Slot.SIDEBAR, + new EntriesSection("EDUCATION", List.of( + CvEntry.builder("BSc Logistics") + .icon("graduation") + .body("De Montfort University") + .link("https://example.test/dmu") + .build()))); + case "CERTIFICATIONS" -> new CvDocument.Placement(Slot.SIDEBAR, + new EntriesSection("CERTIFICATIONS", List.of( + CvEntry.builder("IOSH Managing Safely") + .subtitle("IOSH") + .link("https://example.test/iosh") + .build()))); + case "WORK EXPERIENCE" -> new CvDocument.Placement(Slot.MAIN, + new EntriesSection("WORK EXPERIENCE", List.of( + CvEntry.builder("Warehouse Operations Supervisor") + .subtitle("ExpressLink") + .date("2021") + .body("Ran the floor.") + .link("https://example.test/expresslink") + .build()))); + default -> placement; + }); + } + + List targets = linkTargets( + render(new CvDocument(OrangeOpsFixtures.identity(), placements))); + assertThat(targets) + .contains("https://example.test/award") + .contains("https://example.test/dmu") + .contains("https://example.test/iosh") + .contains("https://example.test/expresslink"); + } + + @Test + void aRolesPlaceIsSetBesideItsEmployer() throws Exception { + EntriesSection located = new EntriesSection("WORK EXPERIENCE", List.of( + CvEntry.builder("Shift Lead") + .subtitle("Midland Gate") + .place("Coventry, UK") + .date("2018") + .body("Ran the floor.") + .build())); + + String text = textOf(render(withSection(Slot.MAIN, located, "WORK EXPERIENCE"))); + assertThat(text).contains("Midland Gate | Coventry, UK"); + } + + @Test + void anUnknownMarkIsReportedAsADataError() { + EntriesSection wrong = new EntriesSection("ACHIEVEMENTS", List.of( + CvEntry.builder("Telepathy").icon("telescope").body("Reading minds.").build())); + + assertThatThrownBy(() -> render(withSection(Slot.SIDEBAR, wrong, "ACHIEVEMENTS"))) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("telescope") + .hasMessageContaining("graduation"); + } + + @Test + void anEntryWithoutAMarkIsDrawnWithoutOne() throws Exception { + EntriesSection unmarked = new EntriesSection("ADDITIONAL INFORMATION", List.of( + CvEntry.builder("Availability:").body("Immediate").build())); + + String text = textOf(render(withSection(Slot.MAIN, unmarked, "ADDITIONAL INFORMATION"))); + assertThat(unwrapped(text)).contains("Availability: Immediate"); + } + + @Test + void anEmptyBerthTakesItsHeadingAndItsJoinRuleWithIt() throws Exception { + // The rule between two blocks belongs to the pair; drop one block and + // the sheet must not keep a hairline over the gap where it was. + byte[] full = render(OrangeOpsFixtures.canonicalCv()); + byte[] shorter = render(withoutSection("CERTIFICATIONS")); + + assertThat(textOf(full)).contains("CERTIFICATIONS"); + assertThat(textOf(shorter)).doesNotContain("CERTIFICATIONS"); + + try (DocumentSession session = GraphCompose.document().create()) { + OrangeOpsTestFont.register(session); + OrangeOps.create().compose(session, withoutSection("CERTIFICATIONS")); + assertThat(session.layoutSnapshot().nodes()) + .noneMatch(node -> "AfterEducationRule".equals(node.entityName())); + } + try (DocumentSession session = GraphCompose.document().create()) { + OrangeOpsTestFont.register(session); + OrangeOps.create().compose(session, OrangeOpsFixtures.canonicalCv()); + assertThat(session.layoutSnapshot().nodes()) + .anyMatch(node -> "AfterEducationRule".equals(node.entityName())); + } + } + + @Test + void aDocumentWithNothingButAnIdentityStillRenders() throws Exception { + // Every berth empty: the name, the role bar and the contact strip, and + // nothing else on the sheet. + render(new CvDocument(OrangeOpsFixtures.identity(), List.of())); + } + + @Test + void aLongerCvRunsOntoASecondPageRatherThanLosingARole() throws Exception { + // Each column is a stack of blocks, so more content than the design + // holds runs over; each entry is held together, so a role is never cut + // in two. + List many = new ArrayList<>(); + for (int index = 0; index < 8; index++) { + many.add(CvEntry.builder("Warehouse Operations Supervisor " + index) + .subtitle("ExpressLink Distribution Ltd") + .date("Mar 2021") + .body("Oversee daily warehouse operations across a large facility.") + .build()); + } + CvDocument longer = withSection(Slot.MAIN, + new EntriesSection("WORK EXPERIENCE", many), "WORK EXPERIENCE"); + + try (DocumentSession session = GraphCompose.document().create()) { + OrangeOpsTestFont.register(session); + OrangeOps.create().compose(session, longer); + assertThat(session.layoutSnapshot().totalPages()).isGreaterThan(1); + } + assertThat(textOf(render(longer))).contains("Warehouse Operations Supervisor 7"); + } +} diff --git a/qa/src/test/java/com/demcha/compose/document/templates/cv/presets/OrangeOpsTestFont.java b/qa/src/test/java/com/demcha/compose/document/templates/cv/presets/OrangeOpsTestFont.java new file mode 100644 index 000000000..7c5d221a8 --- /dev/null +++ b/qa/src/test/java/com/demcha/compose/document/templates/cv/presets/OrangeOpsTestFont.java @@ -0,0 +1,29 @@ +package com.demcha.compose.document.templates.cv.presets; + +import com.demcha.compose.document.api.DocumentSession; +import com.demcha.compose.font.FontFamilyDefinition; + +/** + * Registers the display family the {@link OrangeOps} gates compose against. + * + *

The preset names Oswald and carries none of it — neither the templates + * artifact nor {@code graph-compose-fonts} ships the family — so every caller + * registers it, and in this module the caller is a test. The two faces are qa's + * own test resources under {@code /fonts/oswald/}, beside the licence they are + * distributed under. Without this call the engine substitutes and both gates + * would be measuring a face the design was never set in.

+ */ +final class OrangeOpsTestFont { + + private OrangeOpsTestFont() { + } + + static void register(DocumentSession document) { + document.registerFontFamily( + FontFamilyDefinition.classpath(OrangeOps.DISPLAY_FONT, + "/fonts/oswald/Oswald-Regular.ttf") + .wordFamily("Oswald") + .boldResource("/fonts/oswald/Oswald-SemiBold.ttf") + .build()); + } +} diff --git a/qa/src/test/java/com/demcha/compose/document/templates/cv/presets/OrangeOpsVisualParityTest.java b/qa/src/test/java/com/demcha/compose/document/templates/cv/presets/OrangeOpsVisualParityTest.java new file mode 100644 index 000000000..5294f0ea4 --- /dev/null +++ b/qa/src/test/java/com/demcha/compose/document/templates/cv/presets/OrangeOpsVisualParityTest.java @@ -0,0 +1,42 @@ +package com.demcha.compose.document.templates.cv.presets; + +import com.demcha.compose.GraphCompose; +import com.demcha.compose.document.api.DocumentSession; +import com.demcha.compose.testing.visual.PdfVisualRegression; +import org.junit.jupiter.api.Test; + +import java.nio.file.Path; + +/** + * Pixel-diff visual parity gate for {@link OrangeOps} — the canonical one-page + * CV against a checked-in baseline. + * + *

Re-blessing baselines — after a deliberate visual change, + * re-run with {@code -Dgraphcompose.visual.approve=true} and commit the updated + * PNG with the change. Baselines live under + * {@code src/test/resources/visual-baselines/cv-v2-layered/}.

+ */ +class OrangeOpsVisualParityTest { + + private static final Path BASELINE_ROOT = Path.of( + "src", "test", "resources", "visual-baselines", "cv-v2-layered"); + + private static final long PIXEL_DIFF_BUDGET = 50_000L; + private static final int PER_PIXEL_TOLERANCE = 8; + + @Test + void rendersWithinPixelDiffBudget() throws Exception { + byte[] pdfBytes; + try (DocumentSession document = GraphCompose.document().create()) { + OrangeOpsTestFont.register(document); + OrangeOps.create().compose(document, OrangeOpsFixtures.canonicalCv()); + pdfBytes = document.toPdfBytes(); + } + + PdfVisualRegression.standard() + .baselineRoot(BASELINE_ROOT) + .perPixelTolerance(PER_PIXEL_TOLERANCE) + .mismatchedPixelBudget(PIXEL_DIFF_BUDGET) + .assertMatchesBaseline("orange_ops", pdfBytes); + } +} diff --git a/qa/src/test/java/com/demcha/compose/document/templates/cv/presets/ProfessionalSidebarFixtures.java b/qa/src/test/java/com/demcha/compose/document/templates/cv/presets/ProfessionalSidebarFixtures.java new file mode 100644 index 000000000..7d6fef3cb --- /dev/null +++ b/qa/src/test/java/com/demcha/compose/document/templates/cv/presets/ProfessionalSidebarFixtures.java @@ -0,0 +1,128 @@ +package com.demcha.compose.document.templates.cv.presets; + +import com.demcha.compose.document.templates.core.identity.Contact; +import com.demcha.compose.document.templates.core.identity.Link; +import com.demcha.compose.document.templates.cv.data.CvDocument; +import com.demcha.compose.document.templates.cv.data.CvIdentity; +import com.demcha.compose.document.templates.cv.data.CvSkill; +import com.demcha.compose.document.templates.cv.data.EntriesSection; +import com.demcha.compose.document.templates.cv.data.ParagraphSection; +import com.demcha.compose.document.templates.cv.data.SkillGroup; +import com.demcha.compose.document.templates.cv.data.SkillsSection; +import com.demcha.compose.document.templates.cv.data.Slot; + +import java.util.List; + +/** + * The canonical fixture for {@link ProfessionalSidebar} — the document the + * design was drawn around, and the one both gates measure. + * + *

One fixture feeds the layout snapshot and the pixel baseline, so a + * change that moves the sheet moves both together instead of leaving one + * gate measuring a document the other no longer renders.

+ */ +public final class ProfessionalSidebarFixtures { + + /** The en dash the design sets between dates. */ + private static final String DASH = "–"; + + private ProfessionalSidebarFixtures() { + } + + /** + * The canonical CV: five contact channels, ten levelled skills, two + * degrees, three languages, three roles, two projects and a references + * note — one page. + * + * @return the fixture document + */ + public static CvDocument canonicalCv() { + return CvDocument.builder() + .identity(CvIdentity.builder() + .name("YOUR", "NAME") + .jobTitle("YOUR PROFESSIONAL TITLE") + .contact(new Contact("+1 234 567 8900", + "your.email@example.com", + "City, State, Country")) + .link(new Link("linkedin.com/in/username", + "https://linkedin.com/in/username")) + .link(new Link("www.yourwebsite.com", + "https://www.yourwebsite.com")) + .build()) + .section(Slot.MAIN, new ParagraphSection("PROFILE", + "Java backend engineer experienced in building secure Spring Boot" + + " services, REST APIs and data-driven products. Delivers clean" + + " architecture, reliable integrations and well-tested" + + " persistence layers with PostgreSQL, JPA and Flyway." + + " Comfortable owning features from API design through" + + " Docker-based delivery and production support.")) + .section(Slot.SIDEBAR, SkillsSection.of("SKILLS", new SkillGroup("Core", List.of( + CvSkill.of("Java", 0.72), + CvSkill.of("Spring Boot", 0.7), + CvSkill.of("SQL / Databases", 0.7), + CvSkill.of("RESTful APIs", 0.6), + CvSkill.of("Microservices", 0.72), + CvSkill.of("Git / GitHub", 0.72), + CvSkill.of("Docker", 0.56), + CvSkill.of("Testing (JUnit)", 0.76), + CvSkill.of("Problem Solving", 0.72), + CvSkill.of("Communication", 0.7))))) + .section(Slot.SIDEBAR, EntriesSection.builder("EDUCATION") + .entry("DEGREE NAME", "University Name", "2016 " + DASH + " 2020", "") + .entry("DEGREE NAME", "University Name", "2012 " + DASH + " 2016", "") + .build()) + .section(Slot.SIDEBAR, SkillsSection.of("LANGUAGES", + new SkillGroup("Spoken", List.of( + CvSkill.of("English", 1.0), + CvSkill.of("Ukrainian", 0.8), + CvSkill.of("Russian", 0.6))))) + .section(Slot.MAIN, EntriesSection.builder("EXPERIENCE") + .entry("JAVA BACKEND DEVELOPER", + "Product Studio | London, UK", + "2023 " + DASH + " Present", + String.join("\n", + "Built secure Spring Boot REST APIs for customer" + + " workflows.", + "Added JWT authentication, validation and role-based" + + " access.", + "Tuned JPA queries and PostgreSQL indexes for faster" + + " responses.", + "Added JUnit tests and Docker environments for reliable" + + " releases.")) + .entry("SOFTWARE DEVELOPER", + "Digital Solutions Ltd | Manchester, UK", + "2021 " + DASH + " 2023", + String.join("\n", + "Integrated internal services with third-party REST" + + " APIs.", + "Delivered Flyway migrations and improved data" + + " consistency.", + "Introduced structured logging and production" + + " diagnostics.", + "Worked with frontend engineers on clear API contracts.")) + .entry("JUNIOR JAVA DEVELOPER", + "TechWorks | Remote", + "2019 " + DASH + " 2021", + String.join("\n", + "Developed Java services and reusable backend" + + " components.", + "Fixed defects across REST, persistence and security" + + " layers.", + "Wrote JUnit tests and supported continuous integration.", + "Documented endpoints and operational runbooks.")) + .build()) + .section(Slot.MAIN, EntriesSection.builder("PROJECTS") + .entry("CVREWRITER", "", "2026", + "AI-powered resume tailoring platform with Spring Boot, JWT" + + " authentication, PDF generation and application" + + " tracking.") + .entry("GRAPHCOMPOSE", "", "2025", + "Declarative Java and Kotlin document layout engine with" + + " semantic components, pagination and multi-format" + + " rendering.") + .build()) + .section(Slot.MAIN, new ParagraphSection("REFERENCES", + "Available upon request.")) + .build(); + } +} diff --git a/qa/src/test/java/com/demcha/compose/document/templates/cv/presets/ProfessionalSidebarLayoutSnapshotTest.java b/qa/src/test/java/com/demcha/compose/document/templates/cv/presets/ProfessionalSidebarLayoutSnapshotTest.java new file mode 100644 index 000000000..01737a10b --- /dev/null +++ b/qa/src/test/java/com/demcha/compose/document/templates/cv/presets/ProfessionalSidebarLayoutSnapshotTest.java @@ -0,0 +1,30 @@ +package com.demcha.compose.document.templates.cv.presets; + +import com.demcha.compose.GraphCompose; +import com.demcha.compose.document.api.DocumentSession; +import com.demcha.compose.document.templates.TemplateTestSupport; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Exact layout snapshot gate for {@link ProfessionalSidebar} — freezes the + * resolved geometry of the canonical one-page CV, which is what catches a + * shift the pixel budget is too coarse to see. + * + *

Refresh with {@code -Dgraphcompose.updateSnapshots=true} after a + * deliberate layout change, and commit the JSON with the change.

+ */ +class ProfessionalSidebarLayoutSnapshotTest { + + @Test + void canonicalCvMatchesLayoutSnapshot() throws Exception { + try (DocumentSession session = GraphCompose.document().create()) { + ProfessionalSidebar.create().compose( + session, ProfessionalSidebarFixtures.canonicalCv()); + assertThat(session.layoutSnapshot().totalPages()).isEqualTo(1); + TemplateTestSupport.assertCanonicalSnapshot( + session, "professional_sidebar_layout", "cv-v2"); + } + } +} diff --git a/qa/src/test/java/com/demcha/compose/document/templates/cv/presets/ProfessionalSidebarLongDegreeTest.java b/qa/src/test/java/com/demcha/compose/document/templates/cv/presets/ProfessionalSidebarLongDegreeTest.java new file mode 100644 index 000000000..f24c2e3ea --- /dev/null +++ b/qa/src/test/java/com/demcha/compose/document/templates/cv/presets/ProfessionalSidebarLongDegreeTest.java @@ -0,0 +1,99 @@ +package com.demcha.compose.document.templates.cv.presets; + +import com.demcha.compose.GraphCompose; +import com.demcha.compose.document.api.DocumentSession; +import com.demcha.compose.document.layout.PlacedNode; +import com.demcha.compose.document.templates.core.identity.Contact; +import com.demcha.compose.document.templates.cv.data.CvDocument; +import com.demcha.compose.document.templates.cv.data.CvIdentity; +import com.demcha.compose.document.templates.cv.data.EntriesSection; +import com.demcha.compose.document.templates.cv.data.Slot; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * A degree title too long for one line of the sidebar, on + * {@link ProfessionalSidebar}. + * + *

The canonical fixture's degrees read {@code "DEGREE NAME"}, which fits, + * and a title that fits is the only one the block was ever measured with. A + * real degree — {@code "MSc Advanced Computer Science and Software + * Engineering"} — wraps in a sidebar this narrow, and the row it wraps into has + * to belong to it rather than to the line below.

+ * + *

This is the block's own contract, not the timeline's: the rail, the dots + * and the entry spacing are all the timeline's and are unaffected by how tall + * one degree is. What is asserted here is that a degree which needs two lines + * gets two lines of room.

+ */ +class ProfessionalSidebarLongDegreeTest { + + private static final String LONG_DEGREE = + "MSc Advanced Computer Science and Software Engineering"; + + @Test + void aDegreeThatWrapsDoesNotLandOnItsOwnInstitutionLine() throws Exception { + try (DocumentSession session = GraphCompose.document().create()) { + ProfessionalSidebar.create().compose(session, longDegreeCv()); + session.toPdfBytes(); + + PlacedNode degree = node(session, "EducationDegree_0"); + PlacedNode institution = node(session, "EducationInstitution_0"); + + assertThat(degree.placementHeight()) + .as("the degree needs more than one line at this width") + .isGreaterThan(12.0); + + // y grows upwards, so the degree's foot must sit at or above the + // institution's head. Overlap is the defect: the head band declared + // one line of height for a title that needed two, and drew the + // second over the line below rather than pushing it down. + double degreeFoot = degree.placementY(); + double institutionHead = institution.placementY() + institution.placementHeight(); + assertThat(degreeFoot) + .as("degree foot %.3f must not fall below institution head %.3f", + degreeFoot, institutionHead) + .isGreaterThanOrEqualTo(institutionHead - 0.001); + } + } + + @Test + void theSecondDegreeStillClearsTheFirstEntry() throws Exception { + try (DocumentSession session = GraphCompose.document().create()) { + ProfessionalSidebar.create().compose(session, longDegreeCv()); + session.toPdfBytes(); + + PlacedNode firstDates = node(session, "EducationDates_0"); + PlacedNode secondDegree = node(session, "EducationDegree_1"); + + assertThat(secondDegree.placementY() + secondDegree.placementHeight()) + .as("a taller first entry pushes the second one down") + .isLessThanOrEqualTo(firstDates.placementY() + 0.001); + } + } + + private static PlacedNode node(DocumentSession session, String name) { + return session.layoutGraph().nodes().stream() + .filter(n -> name.equals(n.semanticName())) + .findFirst() + .orElseThrow(() -> new AssertionError("no node named " + name)); + } + + /** The canonical fixture with one degree title that cannot fit one line. */ + private static CvDocument longDegreeCv() { + return CvDocument.builder() + .identity(CvIdentity.builder() + .name("YOUR", "NAME") + .jobTitle("YOUR PROFESSIONAL TITLE") + .contact(new Contact("+1 234 567 8900", + "your.email@example.com", + "City, Country")) + .build()) + .section(Slot.SIDEBAR, EntriesSection.builder("EDUCATION") + .entry(LONG_DEGREE, "University Name", "2016 – 2020", "") + .entry("DEGREE NAME", "University Name", "2012 – 2016", "") + .build()) + .build(); + } +} diff --git a/qa/src/test/java/com/demcha/compose/document/templates/cv/presets/ProfessionalSidebarSmokeTest.java b/qa/src/test/java/com/demcha/compose/document/templates/cv/presets/ProfessionalSidebarSmokeTest.java new file mode 100644 index 000000000..f9e7d6d12 --- /dev/null +++ b/qa/src/test/java/com/demcha/compose/document/templates/cv/presets/ProfessionalSidebarSmokeTest.java @@ -0,0 +1,243 @@ +package com.demcha.compose.document.templates.cv.presets; + +import com.demcha.compose.GraphCompose; +import com.demcha.compose.document.api.DocumentSession; +import com.demcha.compose.document.exceptions.AtomicNodeTooLargeException; +import com.demcha.compose.document.templates.api.DocumentTemplate; +import com.demcha.compose.document.templates.core.identity.Contact; +import com.demcha.compose.document.templates.core.identity.Link; +import com.demcha.compose.document.templates.cv.data.CvDocument; +import com.demcha.compose.document.templates.cv.data.CvIdentity; +import com.demcha.compose.document.templates.cv.data.CvSkill; +import com.demcha.compose.document.templates.cv.data.EntriesSection; +import com.demcha.compose.document.templates.cv.data.SkillGroup; +import com.demcha.compose.document.templates.cv.data.SkillsSection; +import com.demcha.compose.document.templates.cv.data.Slot; +import org.apache.pdfbox.Loader; +import org.apache.pdfbox.pdmodel.PDDocument; +import org.apache.pdfbox.pdmodel.PDPage; +import org.apache.pdfbox.pdmodel.interactive.action.PDActionURI; +import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotation; +import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationLink; +import org.apache.pdfbox.text.PDFTextStripper; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +/** + * Smoke test for {@link ProfessionalSidebar} — proves the preset renders a + * {@link CvDocument} end-to-end with its packaged marks, reaches its + * channels as PDF links, degrades through its guards on a document that + * fills none of the berths, and draws a name without a level rather than an + * empty meter. + * + *

It also pins the two limits the class documents, because both are things + * a caller finds out the hard way otherwise: the sheet holds one page and + * raises past it, and three fields this design has no place for are not + * drawn.

+ */ +class ProfessionalSidebarSmokeTest { + + private static byte[] render(CvDocument doc) throws Exception { + // The preset owns its page geometry, so the session starts unconfigured. + try (DocumentSession session = GraphCompose.document().create()) { + ProfessionalSidebar.create().compose(session, doc); + assertThat(session.roots()).isNotEmpty(); + byte[] pdfBytes = session.toPdfBytes(); + assertThat(pdfBytes).isNotEmpty(); + return pdfBytes; + } + } + + private static String textOf(byte[] pdfBytes) throws Exception { + try (PDDocument document = Loader.loadPDF(pdfBytes)) { + return new PDFTextStripper().getText(document); + } + } + + private static List linkUris(byte[] pdfBytes) throws Exception { + List uris = new ArrayList<>(); + try (PDDocument document = Loader.loadPDF(pdfBytes)) { + for (PDPage page : document.getPages()) { + for (PDAnnotation annotation : page.getAnnotations()) { + if (annotation instanceof PDAnnotationLink link + && link.getAction() instanceof PDActionURI action) { + uris.add(action.getURI()); + } + } + } + } + return uris; + } + + @Test + void exposesStableIdentity() { + DocumentTemplate template = ProfessionalSidebar.create(); + assertThat(template.id()).isEqualTo(ProfessionalSidebar.ID); + assertThat(template.displayName()).isEqualTo(ProfessionalSidebar.DISPLAY_NAME); + } + + @Test + void canonicalRenderCarriesTheUntrackedText() throws Exception { + // The headings and the name are set letter by letter to reach their + // tracking, so they arrive on the text layer spaced out. What is + // asserted here is the text drawn as written. + String text = textOf(render(ProfessionalSidebarFixtures.canonicalCv())); + assertThat(text) + .contains("your.email@example.com") + .contains("City, State, Country") + .contains("Java backend engineer") + .contains("DEGREE NAME") + .contains("University Name") + .contains("Docker") + .contains("English") + .contains("Product Studio") + .contains("Built secure Spring Boot REST APIs for customer workflows.") + .contains("Available upon request."); + } + + @Test + void channelsReachThePdfAsLinks() throws Exception { + // The dial and mail targets are built from the values, so nothing in + // the document carries them and nothing but the PDF can show them. + assertThat(linkUris(render(ProfessionalSidebarFixtures.canonicalCv()))) + .contains("tel:+12345678900", + "mailto:your.email@example.com", + "https://linkedin.com/in/username", + "https://www.yourwebsite.com"); + } + + @Test + void rendersOneCanonicalPage() throws Exception { + try (PDDocument document = + Loader.loadPDF(render(ProfessionalSidebarFixtures.canonicalCv()))) { + assertThat(document.getNumberOfPages()).isEqualTo(1); + } + } + + @Test + void rendersIdentityOnly() throws Exception { + // Every berth empty: the plate, the initials and the three channels + // the identity always carries, and no hairline left hanging under a + // section that was never drawn. + String text = textOf(render(CvDocument.builder() + .identity(identity()) + .build())); + assertThat(text).contains("empty@example.com"); + } + + @Test + void rendersWithoutAJobTitle() throws Exception { + // The role line is written letter by letter to reach its tracking, so + // an identity with no job title reaches the engine as a paragraph + // holding no runs at all. + String text = textOf(render(CvDocument.builder() + .identity(CvIdentity.builder() + .name("Ada", "Lovelace") + .contact(new Contact("+44 20 7946 0000", "empty@example.com", + "London, UK")) + .build()) + .build())); + assertThat(text).contains("empty@example.com"); + } + + @Test + void drawsAnUnlevelledSkillAsItsNameAlone() throws Exception { + // A meter needs a level; without one the row is the name, not an + // empty track that reads as a zero score. + String text = textOf(render(CvDocument.builder() + .identity(identity()) + .section(Slot.SIDEBAR, SkillsSection.of("SKILLS", + SkillGroup.of("Core", "Java", "Kotlin"))) + .section(Slot.SIDEBAR, SkillsSection.of("LANGUAGES", + new SkillGroup("Spoken", List.of(CvSkill.of("English"))))) + .build())); + assertThat(text).contains("Java").contains("Kotlin").contains("English"); + } + + @Test + void placesSectionsByTitleWhateverSlotTheDocumentNames() throws Exception { + // The columns are fixed, so a document that leaves everything in the + // main slot still gets its education in the sidebar. + String text = textOf(render(CvDocument.builder() + .identity(identity()) + .section(Slot.MAIN, EntriesSection.builder("EDUCATION") + .entry("BSc Computing", "University Name", "2016", "") + .build()) + .build())); + assertThat(text).contains("BSc Computing").contains("University Name"); + } + + @Test + void refusesACvLongerThanTheSheet() { + // The sheet holds one page: the two columns are a single row, and a + // row is atomic. A CV past that raises rather than flowing — pinned + // here because the alternative the sibling presets take is to cap + // each block and drop the rest without saying so. + EntriesSection.Builder experience = EntriesSection.builder("EXPERIENCE"); + for (int i = 0; i < 12; i++) { + experience.entry("ROLE " + i, "Employer " + i + " | City", + "2010 - 2011", + String.join("\n", + "Did a substantial thing that took a full line of text here.", + "Did a second substantial thing, also a full line of text.", + "And a third, so each role occupies real vertical space.")); + } + CvDocument doc = CvDocument.builder() + .identity(identity()) + .section(experience.build()) + .build(); + try (DocumentSession session = GraphCompose.document().create()) { + ProfessionalSidebar.create().compose(session, doc); + assertThatThrownBy(session::toPdfBytes) + .isInstanceOf(AtomicNodeTooLargeException.class) + .hasMessageContaining("PageGrid"); + } + } + + @Test + void drawsNothingTheDesignHasNoPlaceFor() throws Exception { + // A berth is filled once, and three fields have nowhere to go on this + // sheet. A caller who puts content there gets none of it drawn, which + // is why the class says so. + String text = textOf(render(CvDocument.builder() + .identity(identity()) + .section(EntriesSection.builder("PROJECTS") + .entry("LEDGERKIT", "Client name here", "2024", "A ledger.") + .build()) + .section(EntriesSection.builder("EDUCATION") + .entry("BSc", "University Name", "2016", "Thesis title here") + .build()) + .section(SkillsSection.of("SKILLS", + SkillGroup.of("Category name here", "Java"))) + .section(EntriesSection.builder("EXPERIENCE") + .entry("FIRST ROLE", "Employer", "2020", "Did a thing.") + .build()) + .section(EntriesSection.builder("MORE EXPERIENCE") + .entry("SECOND ROLE", "Employer", "2021", "Did another thing.") + .build()) + .build())); + assertThat(text) + .contains("LEDGERKIT") + .contains("University Name") + .contains("Java") + .contains("FIRST ROLE") + .doesNotContain("Client name here") + .doesNotContain("Thesis title here") + .doesNotContain("Category name here") + .doesNotContain("SECOND ROLE"); + } + + private static CvIdentity identity() { + return CvIdentity.builder() + .name("Ada", "Lovelace") + .jobTitle("ANALYST") + .contact(new Contact("+44 20 7946 0000", "empty@example.com", "London, UK")) + .link(new Link("linkedin.com/in/ada", "https://linkedin.com/in/ada")) + .build(); + } +} diff --git a/qa/src/test/java/com/demcha/compose/document/templates/cv/presets/ProfessionalSidebarVisualParityTest.java b/qa/src/test/java/com/demcha/compose/document/templates/cv/presets/ProfessionalSidebarVisualParityTest.java new file mode 100644 index 000000000..5596d0d78 --- /dev/null +++ b/qa/src/test/java/com/demcha/compose/document/templates/cv/presets/ProfessionalSidebarVisualParityTest.java @@ -0,0 +1,42 @@ +package com.demcha.compose.document.templates.cv.presets; + +import com.demcha.compose.GraphCompose; +import com.demcha.compose.document.api.DocumentSession; +import com.demcha.compose.testing.visual.PdfVisualRegression; +import org.junit.jupiter.api.Test; + +import java.nio.file.Path; + +/** + * Pixel-diff visual parity gate for {@link ProfessionalSidebar} — the + * canonical one-page CV against a checked-in baseline. + * + *

Re-blessing baselines — after a deliberate visual + * change, re-run with {@code -Dgraphcompose.visual.approve=true} and commit + * the updated PNG with the change. Baselines live under + * {@code src/test/resources/visual-baselines/cv-v2-layered/}.

+ */ +class ProfessionalSidebarVisualParityTest { + + private static final Path BASELINE_ROOT = Path.of( + "src", "test", "resources", "visual-baselines", "cv-v2-layered"); + + private static final long PIXEL_DIFF_BUDGET = 50_000L; + private static final int PER_PIXEL_TOLERANCE = 8; + + @Test + void rendersWithinPixelDiffBudget() throws Exception { + byte[] pdfBytes; + try (DocumentSession document = GraphCompose.document().create()) { + ProfessionalSidebar.create().compose( + document, ProfessionalSidebarFixtures.canonicalCv()); + pdfBytes = document.toPdfBytes(); + } + + PdfVisualRegression.standard() + .baselineRoot(BASELINE_ROOT) + .perPixelTolerance(PER_PIXEL_TOLERANCE) + .mismatchedPixelBudget(PIXEL_DIFF_BUDGET) + .assertMatchesBaseline("professional_sidebar", pdfBytes); + } +} diff --git a/qa/src/test/java/com/demcha/compose/document/templates/cv/presets/SerifHeadlineFixtures.java b/qa/src/test/java/com/demcha/compose/document/templates/cv/presets/SerifHeadlineFixtures.java new file mode 100644 index 000000000..1561d4948 --- /dev/null +++ b/qa/src/test/java/com/demcha/compose/document/templates/cv/presets/SerifHeadlineFixtures.java @@ -0,0 +1,173 @@ +package com.demcha.compose.document.templates.cv.presets; + +import com.demcha.compose.document.templates.core.identity.Contact; +import com.demcha.compose.document.templates.core.identity.Link; +import com.demcha.compose.document.templates.cv.data.CvDocument; +import com.demcha.compose.document.templates.cv.data.CvEntry; +import com.demcha.compose.document.templates.cv.data.CvIdentity; +import com.demcha.compose.document.templates.cv.data.CvSkill; +import com.demcha.compose.document.templates.cv.data.EntriesSection; +import com.demcha.compose.document.templates.cv.data.ParagraphSection; +import com.demcha.compose.document.templates.cv.data.RowStyle; +import com.demcha.compose.document.templates.cv.data.RowsSection; +import com.demcha.compose.document.templates.cv.data.SkillsSection; + +import java.util.List; + +/** + * The canonical fixture for {@link SerifHeadline} — the document the design + * was drawn around, and the one both gates measure. + * + *

One fixture feeds the layout snapshot and the pixel baseline, so a + * change that moves the sheet moves both together instead of leaving one + * gate measuring a document the other no longer renders.

+ */ +public final class SerifHeadlineFixtures { + + /** The line break the presets split a body on. */ + private static final String NEWLINE = String.valueOf((char) 10); + + private SerifHeadlineFixtures() { + } + + /** + * The canonical CV: five contact channels, three roles, two projects, + * two degrees, four skill groups, three soft-skill lines, five + * certifications and three achievements — one page. + * + * @return the fixture document + */ + public static CvDocument canonicalCv() { + return CvDocument.builder() + .identity(CvIdentity.builder() + .name("Alexander", "Morgan") + .jobTitle("Software Engineer") + .contact(new Contact("+1 (555) 123-4567", + "alex.morgan@email.com", + "New York, NY, USA")) + .link(new Link("linkedin.com/in/alexmorgan", + "https://www.linkedin.com/in/alexmorgan")) + .link(new Link("github.com/alexmorgan", + "https://github.com/alexmorgan")) + .build()) + .section(new ParagraphSection("Summary", + "Software Engineer with 5+ years of experience designing and building scalable web applications and backend services. Proficient in Java, Spring Boot, and cloud technologies. Passionate about clean code, system design, and delivering high-quality software that creates real value for users.")) + .section(EntriesSection.builder("Experience") + .entry(CvEntry.builder("Senior Software Engineer") + .subtitle("TechSolutions Inc.") + .date("Jan 2022 – Present") + .place("New York, NY") + .body(List.of( + "Designed and developed microservices using Java, Spring Boot, and Docker serving over 1M active users.", + "Implemented RESTful APIs and integrated with third-party services.", + "Optimized database queries and application performance, improving response time by 35%.", + "Led a team of 4 engineers and mentored junior developers." + )) + .build()) + .entry(CvEntry.builder("Software Engineer") + .subtitle("InnovaTech Ltd.") + .date("Jun 2019 – Dec 2021") + .place("New York, NY") + .body(List.of( + "Built scalable web applications using Java, Hibernate, and JavaScript.", + "Collaborated with cross-functional teams to deliver new features.", + "Implemented CI/CD pipelines using Jenkins and Docker.", + "Improved code quality and reduced production bugs by 25%." + )) + .build()) + .entry(CvEntry.builder("Junior Software Engineer") + .subtitle("DevSphere") + .date("Jun 2018 – May 2019") + .place("New York, NY") + .body(List.of( + "Assisted in developing and maintaining internal tools and applications.", + "Wrote unit and integration tests to ensure code reliability.", + "Participated in agile ceremonies and contributed to sprint goals." + )) + .build()) + .build()) + .section(EntriesSection.builder("Projects") + .entry(CvEntry.builder("E-Commerce Platform") + .subtitle("Java, Spring Boot, React, PostgreSQL, AWS") + .body("Developed a full-stack e-commerce platform with user authentication, product catalog, cart, and payment integration.") + .icon("cart") + .build()) + .entry(CvEntry.builder("Task Management API") + .subtitle("Java, Spring Boot, MongoDB, Docker") + .body("Built a RESTful API for task management with role-based access, real-time updates, and Dockerized deployment.") + .icon("api") + .build()) + .build()) + .section(EntriesSection.builder("Education") + .entry(CvEntry.builder("M.S. in Computer Science") + .subtitle("New York University") + .date("2018 – 2020") + .place("New York, NY") + .build()) + .entry(CvEntry.builder("B.S. in Computer Science") + .subtitle("University of California, Los Angeles") + .date("2014 – 2018") + .place("Los Angeles, CA") + .build()) + .build()) + .section(SkillsSection.builder("Skills") + .leveledGroup("Languages", List.of( + CvSkill.of("Java", 0.75), + CvSkill.of("Python", 0.58), + CvSkill.of("JavaScript / TypeScript", 0.48), + CvSkill.of("SQL", 0.75) + )) + .leveledGroup("Frameworks & Libraries", List.of( + CvSkill.of("Spring Boot", 0.75), + CvSkill.of("Hibernate", 0.54), + CvSkill.of("React", 0.75), + CvSkill.of("Node.js", 0.39), + CvSkill.of("Express", 0.75) + )) + .leveledGroup("Databases", List.of( + CvSkill.of("MySQL", 0.75), + CvSkill.of("PostgreSQL", 0.75), + CvSkill.of("MongoDB", 0.75) + )) + .leveledGroup("Tools & Technologies", List.of( + CvSkill.of("Docker", 0.75), + CvSkill.of("Kubernetes", 0.75), + CvSkill.of("Jenkins", 0.75), + CvSkill.of("Git", 0.39), + CvSkill.of("AWS", 0.75) + )) + .build()) + .section(new ParagraphSection("Soft Skills", String.join(NEWLINE, + "Problem Solving | Communication", + "Teamwork | Adaptability", + "Leadership | Time Management" + ))) + .section(RowsSection.builder("Certifications", RowStyle.PLAIN) + .row("AWS Certified Developer – Associate", + "(AWS)") + .row("Oracle Certified Professional,", + "Java SE 17 (Oracle)") + .row("Docker Certified Associate", + "(DCA)") + .row("Spring Professional Certification", + "(VMware)") + .row("Kubernetes Fundamentals", + "(Linux Foundation)") + .build()) + .section(EntriesSection.builder("Achievements") + .entry(CvEntry.builder("Engineer of the Year – 2023") + .body("Recognized for outstanding performance and leadership.") + .icon("trophy") + .build()) + .entry(CvEntry.builder("Performance Optimization") + .body("Improved system efficiency by 40% through database and code optimizations.") + .icon("chart") + .build()) + .entry(CvEntry.builder("Successful Migration") + .body("Led the migration of a monolith application to microservices architecture.") + .icon("rocket") + .build()) + .build()) + .build(); + } +} diff --git a/qa/src/test/java/com/demcha/compose/document/templates/cv/presets/SerifHeadlineLayoutSnapshotTest.java b/qa/src/test/java/com/demcha/compose/document/templates/cv/presets/SerifHeadlineLayoutSnapshotTest.java new file mode 100644 index 000000000..6304fef86 --- /dev/null +++ b/qa/src/test/java/com/demcha/compose/document/templates/cv/presets/SerifHeadlineLayoutSnapshotTest.java @@ -0,0 +1,31 @@ +package com.demcha.compose.document.templates.cv.presets; + +import com.demcha.compose.GraphCompose; +import com.demcha.compose.document.api.DocumentSession; +import com.demcha.compose.document.templates.TemplateTestSupport; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Exact layout snapshot gate for {@link SerifHeadline} - freezes the resolved + * geometry of the canonical one-page CV. It matters more here than on the + * simpler sheets: almost every distance in this design is computed from a + * reference grid and a set of type metrics, so a change to one constant moves + * a great many nodes at once. + * + *

Refresh with {@code -Dgraphcompose.updateSnapshots=true} after a + * deliberate layout change, and commit the JSON with the change.

+ */ +class SerifHeadlineLayoutSnapshotTest { + + @Test + void canonicalCvMatchesLayoutSnapshot() throws Exception { + try (DocumentSession session = GraphCompose.document().create()) { + SerifHeadline.create().compose(session, SerifHeadlineFixtures.canonicalCv()); + assertThat(session.layoutSnapshot().totalPages()).isEqualTo(1); + TemplateTestSupport.assertCanonicalSnapshot( + session, "serif_headline_layout", "cv-v2"); + } + } +} diff --git a/qa/src/test/java/com/demcha/compose/document/templates/cv/presets/SerifHeadlineSmokeTest.java b/qa/src/test/java/com/demcha/compose/document/templates/cv/presets/SerifHeadlineSmokeTest.java new file mode 100644 index 000000000..6517e5c08 --- /dev/null +++ b/qa/src/test/java/com/demcha/compose/document/templates/cv/presets/SerifHeadlineSmokeTest.java @@ -0,0 +1,412 @@ +package com.demcha.compose.document.templates.cv.presets; + +import com.demcha.compose.GraphCompose; +import com.demcha.compose.document.api.DocumentSession; +import com.demcha.compose.document.exceptions.AtomicNodeTooLargeException; +import com.demcha.compose.document.snapshot.LayoutNodeSnapshot; +import com.demcha.compose.document.templates.api.DocumentTemplate; +import com.demcha.compose.document.templates.core.identity.Contact; +import com.demcha.compose.document.templates.core.identity.Link; +import com.demcha.compose.document.templates.cv.data.CvDocument; +import com.demcha.compose.document.templates.cv.data.CvEntry; +import com.demcha.compose.document.templates.cv.data.CvIdentity; +import com.demcha.compose.document.templates.cv.data.CvSkill; +import com.demcha.compose.document.templates.cv.data.EntriesSection; +import com.demcha.compose.document.templates.cv.data.ParagraphSection; +import com.demcha.compose.document.templates.cv.data.SkillsSection; +import org.apache.pdfbox.Loader; +import org.apache.pdfbox.pdmodel.PDDocument; +import org.apache.pdfbox.pdmodel.PDPage; +import org.apache.pdfbox.pdmodel.interactive.action.PDActionURI; +import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotation; +import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationLink; +import org.apache.pdfbox.text.PDFTextStripper; +import org.apache.pdfbox.text.TextPosition; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.assertj.core.api.Assertions.within; + +/** + * Smoke test for {@link SerifHeadline} — proves the preset renders a + * {@link CvDocument} end-to-end with its packaged marks, reaches its + * channels as PDF links, and pins the contracts the class documents: the + * entry marks are this preset's own vocabulary, an entry without one is + * drawn plain, the sheet holds one page, and the two blocks whose titles + * overlap reach the right berths. + */ +class SerifHeadlineSmokeTest { + + /** Everything right of this belongs to the aside, not the main rail. */ + private static final double MAIN_RAIL_RIGHT_EDGE = 380.0; + + private static byte[] render(CvDocument doc) throws Exception { + // The preset owns its page geometry, so the session starts unconfigured. + try (DocumentSession session = GraphCompose.document().create()) { + SerifHeadline.create().compose(session, doc); + assertThat(session.roots()).isNotEmpty(); + byte[] pdfBytes = session.toPdfBytes(); + assertThat(pdfBytes).isNotEmpty(); + return pdfBytes; + } + } + + private static String textOf(byte[] pdfBytes) throws Exception { + try (PDDocument document = Loader.loadPDF(pdfBytes)) { + return new PDFTextStripper().getText(document); + } + } + + private static List linkUris(byte[] pdfBytes) throws Exception { + List uris = new ArrayList<>(); + try (PDDocument document = Loader.loadPDF(pdfBytes)) { + for (PDPage page : document.getPages()) { + for (PDAnnotation annotation : page.getAnnotations()) { + if (annotation instanceof PDAnnotationLink link + && link.getAction() instanceof PDActionURI action) { + uris.add(action.getURI()); + } + } + } + } + return uris; + } + + @Test + void exposesStableIdentity() { + DocumentTemplate template = SerifHeadline.create(); + assertThat(template.id()).isEqualTo(SerifHeadline.ID); + assertThat(template.displayName()).isEqualTo(SerifHeadline.DISPLAY_NAME); + } + + @Test + void canonicalRenderCarriesTheUntrackedText() throws Exception { + // The headings, the name and the role are set letter by letter to + // reach their tracking, so they arrive on the text layer spaced out. + // What is asserted here is the text drawn as written. + String text = textOf(render(SerifHeadlineFixtures.canonicalCv())); + assertThat(text) + .contains("alex.morgan@email.com") + .contains("github.com/alexmorgan") + .contains("Software Engineer with 5+ years") + .contains("TechSolutions Inc.") + .contains("Led a team of 4 engineers and mentored junior developers.") + .contains("E-Commerce Platform") + .contains("Java, Spring Boot, React, PostgreSQL, AWS") + .contains("New York University") + .contains("Kubernetes") + .contains("Problem Solving | Communication") + // The certification titles wrap inside their narrow column, + // which is the design: assert either side of the break. + .contains("Docker Certified") + .contains("(DCA)") + .contains("Improved system efficiency by 40%"); + } + + @Test + void rendersOneCanonicalPage() throws Exception { + try (PDDocument document = + Loader.loadPDF(render(SerifHeadlineFixtures.canonicalCv()))) { + assertThat(document.getNumberOfPages()).isEqualTo(1); + } + } + + @Test + void everyHighlightLineStartsOnTheSameTextColumn() throws Exception { + // Neither parity gate can see this. A list's items are not layout nodes, + // so the snapshot records the list's box and nothing inside it, and two + // continuation lines moving 1.5pt sits far inside the pixel budget. The + // contract is that the dot has a column and the text has a column, and + // that a highlight which wraps resumes on the text one — so it is read + // off the glyph positions, which is where it actually lives. + List lines = highlightLines( + render(SerifHeadlineFixtures.canonicalCv())); + + assertThat(lines).filteredOn(line -> !line.wrapped()) + .as("every authored highlight is here") + .hasSize(11); + assertThat(lines).filteredOn(HighlightLine::wrapped) + .as("and at least one of them wraps, with nothing of its own in the marker" + + " column — a continuation padded back to that column with spaces would" + + " land here too, and is what this is watching for") + .isNotEmpty(); + + double textColumn = lines.get(0).textX(); + double markerColumn = lines.stream().filter(line -> !line.wrapped()) + .mapToDouble(HighlightLine::markerX).findFirst().orElse(Double.NaN); + assertThat(lines).allSatisfy(line -> assertThat(line.textX()) + .as("the %s line %s starts its text on the column", + line.wrapped() ? "wrapped" : "first", line.text()) + .isCloseTo(textColumn, within(0.01))); + assertThat(lines).filteredOn(HighlightLine::wrapped) + .as("a wrapped line resumes on the text, never in the marker column") + .allSatisfy(line -> assertThat(line.markerX()).isNaN()); + + // 6.750pt between the two columns: 4.683 of dot advance and the 2.067 + // the design authors as the gap. Pinned because it is the number the + // migration had to solve for, and nothing else on the sheet would move + // if it drifted. + assertThat(textColumn - markerColumn) + .as("the dot's advance plus the authored gap") + .isCloseTo(6.750, within(0.01)); + } + + /** One rendered highlight line: where its dot is, if any, and where its text starts. */ + private record HighlightLine(double markerX, double textX, String text) { + + boolean wrapped() { + return Double.isNaN(markerX); + } + } + + /** + * The highlight lines of the canonical sheet, read off the page. + * + *

Scoped by the y-bands of the {@code Highlights_*} list boxes and to the + * main rail's half of the sheet, so the aside's own text cannot wander in.

+ * + * @param pdfBytes the rendered sheet + * @return one entry per rendered line + */ + private static List highlightLines(byte[] pdfBytes) throws Exception { + List bands = new ArrayList<>(); + try (DocumentSession session = GraphCompose.document().create()) { + SerifHeadline.create().compose(session, SerifHeadlineFixtures.canonicalCv()); + for (LayoutNodeSnapshot node : session.layoutSnapshot().nodes()) { + if (node.entityName() != null && node.entityName().startsWith("Highlights_")) { + bands.add(new double[] { + node.placementY(), node.placementY() + node.placementHeight()}); + } + } + } + assertThat(bands).as("three highlight lists on the canonical sheet").hasSize(3); + + List glyphs = new ArrayList<>(); + try (PDDocument document = Loader.loadPDF(pdfBytes)) { + double pageHeight = document.getPage(0).getMediaBox().getHeight(); + PDFTextStripper stripper = new PDFTextStripper() { + @Override + protected void writeString(String text, List positions) { + for (TextPosition position : positions) { + // Every glyph that belongs to a highlight, with the + // baseline it sits on. The call boundaries are no use + // here: a marker is its own text run, so a first line + // arrives as two calls, and a call can reach across both + // columns of the sheet. Grouping by baseline afterwards + // is what puts a dot back with its own text. + double y = pageHeight - position.getYDirAdj(); + boolean inBand = bands.stream() + .anyMatch(band -> y > band[0] - 1.5 && y < band[1] + 1.5); + if (!inBand || position.getXDirAdj() > MAIN_RAIL_RIGHT_EDGE) { + continue; + } + glyphs.add(new Glyph(position.getXDirAdj(), y, position.getUnicode())); + } + } + }; + stripper.setSortByPosition(true); + stripper.getText(document); + } + + // The dot does not survive the text layer as "•" — its font carries no + // ToUnicode for it, so it extracts as a replacement character. The + // marker column is therefore the leftmost x any highlight glyph sits at, + // read off the page rather than assumed, and a baseline with a glyph + // there is one that shows a dot. That is what makes the interesting + // number — where the TEXT starts — comparable between a first line and a + // wrapped one. + double markerColumn = glyphs.stream().mapToDouble(Glyph::x).min().orElse(Double.NaN); + Map> byBaseline = new TreeMap<>(Comparator.reverseOrder()); + for (Glyph glyph : glyphs) { + byBaseline.computeIfAbsent(Math.round(glyph.y() * 4.0), key -> new ArrayList<>()) + .add(glyph); + } + + List lines = new ArrayList<>(byBaseline.size()); + for (List baseline : byBaseline.values()) { + boolean hasMarker = baseline.stream() + .anyMatch(glyph -> Math.abs(glyph.x() - markerColumn) < 0.01); + StringBuilder body = new StringBuilder(); + double textX = Double.NaN; + for (Glyph glyph : baseline.stream().sorted(Comparator.comparingDouble(Glyph::x)) + .toList()) { + if (hasMarker && Math.abs(glyph.x() - markerColumn) < 0.01) { + continue; + } + if (Double.isNaN(textX) && !glyph.unicode().isBlank()) { + textX = glyph.x(); + } + if (!Double.isNaN(textX)) { + body.append(glyph.unicode()); + } + } + if (!Double.isNaN(textX)) { + lines.add(new HighlightLine(hasMarker ? markerColumn : Double.NaN, textX, + body.length() > 18 ? body.substring(0, 18) : body.toString())); + } + } + return lines; + } + + /** One extracted glyph: where it sits and what it is. */ + private record Glyph(double x, double y, String unicode) { + } + + @Test + void channelsReachThePdfAsLinks() throws Exception { + // The dial and mail targets are built from the values. They are + // annotations, so they move no pixel and no layout node — which is + // why the parity gates cannot see them either. + assertThat(linkUris(render(SerifHeadlineFixtures.canonicalCv()))) + .contains("tel:+15551234567", + "mailto:alex.morgan@email.com", + "https://www.linkedin.com/in/alexmorgan", + "https://github.com/alexmorgan"); + } + + @Test + void rejectsAnUnknownEntryMarkByName() { + // An icon token is data, so a wrong one is a data error — and the + // message names the set a document may choose from. + CvDocument doc = CvDocument.builder() + .identity(identity()) + .section(EntriesSection.builder("Projects") + .entry(CvEntry.builder("Ledgerkit").icon("no-such-mark").build()) + .build()) + .build(); + try (DocumentSession session = GraphCompose.document().create()) { + assertThatThrownBy(() -> SerifHeadline.create().compose(session, doc)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("no-such-mark") + .hasMessageContaining("rocket"); + } + } + + @Test + void anEntryWithALinkReachesThePdfAsOne() throws Exception { + // Every title this preset draws is a link when its entry carries + // one. It is an annotation, so it moves no pixel and no layout node + // — which is why neither parity gate can see it. + byte[] pdf = render(CvDocument.builder() + .identity(identity()) + .section(EntriesSection.builder("Projects") + .entry(CvEntry.builder("Ledgerkit") + .subtitle("Java") + .body("A ledger.") + .icon("api") + .link("https://example.com/ledgerkit") + .build()) + .build()) + .section(EntriesSection.builder("Experience") + .entry(CvEntry.builder("Engineer") + .subtitle("Acme") + .date("2024") + .body("Shipped a thing.") + .link("https://acme.example.com") + .build()) + .build()) + .section(EntriesSection.builder("Achievements") + .entry(CvEntry.builder("Engineer of the Year") + .body("For the platform work.") + .icon("trophy") + .link("https://example.com/award") + .build()) + .build()) + .build()); + assertThat(linkUris(pdf)) + .contains("https://example.com/ledgerkit", + "https://acme.example.com", + "https://example.com/award"); + } + + @Test + void drawsAnEntryWithoutAMark() throws Exception { + // No token, no plate — the card is drawn, just unmarked. + String text = textOf(render(CvDocument.builder() + .identity(identity()) + .section(EntriesSection.builder("Projects") + .entry(CvEntry.builder("Ledgerkit") + .subtitle("Java") + .body("A ledger.") + .build()) + .build()) + .build())); + assertThat(text).contains("Ledgerkit").contains("A ledger."); + } + + @Test + void softSkillsTakeTheirOwnBerthRatherThanTheSkillsOne() throws Exception { + // "Soft Skills" names the skills berth too. The block with a shape of + // its own wins it, so both are drawn rather than one swallowing the + // other. + String text = textOf(render(CvDocument.builder() + .identity(identity()) + .section(SkillsSection.builder("Skills") + .leveledGroup("Languages", List.of(CvSkill.of("Java", 0.8))) + .build()) + .section(new ParagraphSection("Soft Skills", "Teamwork | Curiosity")) + .build())); + assertThat(text).contains("Java").contains("Teamwork | Curiosity"); + } + + @Test + void drawsTheEmployerWithoutAPlace() throws Exception { + // The pipe belongs to the location; an entry with no place gets the + // employer alone rather than a line ending in a separator. + String text = textOf(render(CvDocument.builder() + .identity(identity()) + .section(EntriesSection.builder("Experience") + .entry(CvEntry.builder("Engineer") + .subtitle("Acme") + .date("2024") + .body("Shipped a thing.") + .build()) + .build()) + .build())); + assertThat(text).contains("Acme").doesNotContain("Acme |"); + } + + @Test + void refusesACvLongerThanTheSheet() { + // The sheet holds one page: the body is a single row, and a row is + // atomic. A CV past that raises rather than flowing. + EntriesSection.Builder experience = EntriesSection.builder("Experience"); + for (int i = 0; i < 14; i++) { + experience.entry(CvEntry.builder("Role " + i) + .subtitle("Employer " + i) + .place("City") + .date("2010 - 2011") + .body(List.of( + "Did a substantial thing that took a full line of text here.", + "Did a second substantial thing, also a full line of text.", + "And a third, so each role occupies real vertical space.")) + .build()); + } + CvDocument doc = CvDocument.builder() + .identity(identity()) + .section(experience.build()) + .build(); + try (DocumentSession session = GraphCompose.document().create()) { + SerifHeadline.create().compose(session, doc); + assertThatThrownBy(session::toPdfBytes) + .isInstanceOf(AtomicNodeTooLargeException.class) + .hasMessageContaining("BodyColumns"); + } + } + + private static CvIdentity identity() { + return CvIdentity.builder() + .name("Ada", "Lovelace") + .jobTitle("Engineer") + .contact(new Contact("+44 20 7946 0000", "ada@example.com", "London, UK")) + .link(new Link("github.com/ada", "https://github.com/ada")) + .build(); + } +} diff --git a/qa/src/test/java/com/demcha/compose/document/templates/cv/presets/SerifHeadlineVisualParityTest.java b/qa/src/test/java/com/demcha/compose/document/templates/cv/presets/SerifHeadlineVisualParityTest.java new file mode 100644 index 000000000..fbacb17ce --- /dev/null +++ b/qa/src/test/java/com/demcha/compose/document/templates/cv/presets/SerifHeadlineVisualParityTest.java @@ -0,0 +1,41 @@ +package com.demcha.compose.document.templates.cv.presets; + +import com.demcha.compose.GraphCompose; +import com.demcha.compose.document.api.DocumentSession; +import com.demcha.compose.testing.visual.PdfVisualRegression; +import org.junit.jupiter.api.Test; + +import java.nio.file.Path; + +/** + * Pixel-diff visual parity gate for {@link SerifHeadline} - the canonical + * one-page CV against a checked-in baseline. + * + *

Re-blessing baselines - after a deliberate visual + * change, re-run with {@code -Dgraphcompose.visual.approve=true} and commit + * the updated PNG with the change. Baselines live under + * {@code src/test/resources/visual-baselines/cv-v2-layered/}.

+ */ +class SerifHeadlineVisualParityTest { + + private static final Path BASELINE_ROOT = Path.of( + "src", "test", "resources", "visual-baselines", "cv-v2-layered"); + + private static final long PIXEL_DIFF_BUDGET = 50_000L; + private static final int PER_PIXEL_TOLERANCE = 8; + + @Test + void rendersWithinPixelDiffBudget() throws Exception { + byte[] pdfBytes; + try (DocumentSession document = GraphCompose.document().create()) { + SerifHeadline.create().compose(document, SerifHeadlineFixtures.canonicalCv()); + pdfBytes = document.toPdfBytes(); + } + + PdfVisualRegression.standard() + .baselineRoot(BASELINE_ROOT) + .perPixelTolerance(PER_PIXEL_TOLERANCE) + .mismatchedPixelBudget(PIXEL_DIFF_BUDGET) + .assertMatchesBaseline("serif_headline", pdfBytes); + } +} diff --git a/qa/src/test/java/com/demcha/compose/document/templates/cv/presets/SlateOrangeFixtures.java b/qa/src/test/java/com/demcha/compose/document/templates/cv/presets/SlateOrangeFixtures.java new file mode 100644 index 000000000..7557c0bf3 --- /dev/null +++ b/qa/src/test/java/com/demcha/compose/document/templates/cv/presets/SlateOrangeFixtures.java @@ -0,0 +1,206 @@ +package com.demcha.compose.document.templates.cv.presets; + +import com.demcha.compose.document.templates.core.identity.Contact; +import com.demcha.compose.document.templates.core.identity.Link; +import com.demcha.compose.document.templates.cv.data.CvDocument; +import com.demcha.compose.document.templates.cv.data.CvEntry; +import com.demcha.compose.document.templates.cv.data.CvIdentity; +import com.demcha.compose.document.templates.cv.data.CvName; +import com.demcha.compose.document.templates.cv.data.CvSkill; +import com.demcha.compose.document.templates.cv.data.EntriesSection; +import com.demcha.compose.document.templates.cv.data.ParagraphSection; +import com.demcha.compose.document.templates.cv.data.SkillGroup; +import com.demcha.compose.document.templates.cv.data.SkillsSection; +import com.demcha.compose.document.templates.cv.data.Slot; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +/** + * Shared fixture data for the {@link SlateOrange} gates — the SAME document + * feeds the pixel parity test and the layout snapshot test, so a geometry + * shift the pixel budget absorbs still trips the exact snapshot, and vice + * versa. + */ +final class SlateOrangeFixtures { + + /** The break a body stacks its lines on. */ + private static final String NEWLINE = String.valueOf((char) 10); + + private SlateOrangeFixtures() { + } + + /** + * The canonical one-page CV — the monogram tile, four contact lines + * including a link, ten marked competencies, three achievements, three + * rated languages, three closing facts, a profile, four roles, a degree + * and four certifications. + */ + static CvDocument canonicalCv() { + List placements = new ArrayList<>(); + placements.add(new CvDocument.Placement(Slot.MAIN, specialisms())); + placements.add(new CvDocument.Placement(Slot.SIDEBAR, competencies())); + placements.add(new CvDocument.Placement(Slot.SIDEBAR, achievements())); + placements.add(new CvDocument.Placement(Slot.SIDEBAR, languages())); + placements.add(new CvDocument.Placement(Slot.SIDEBAR, facts())); + placements.add(new CvDocument.Placement(Slot.MAIN, profile())); + placements.add(new CvDocument.Placement(Slot.MAIN, experience())); + placements.add(new CvDocument.Placement(Slot.FOOTER, education())); + placements.add(new CvDocument.Placement(Slot.FOOTER, certifications())); + return new CvDocument(identity(), placements); + } + + static CvIdentity identity() { + return new CvIdentity( + CvName.of("DANIEL", "HARPER"), + "VERSATILE PROFESSIONAL", + new Contact("(555) 123-4567", + "daniel.harper@email.com", + "Austin, Texas \u2022 Open to Relocate"), + List.of(new Link("LinkedIn", "https://www.linkedin.com/in/danielharper")), + Optional.empty()); + } + + /** The strip under the role. Only the body is drawn; the title names the berth. */ + static ParagraphSection specialisms() { + return new ParagraphSection("SPECIALISMS", String.join(NEWLINE, + "OPERATIONS", "CUSTOMER SUCCESS", "COORDINATION")); + } + + /** The competencies, each behind the mark its entry names. */ + static EntriesSection competencies() { + return new EntriesSection("CORE COMPETENCIES", List.of( + CvEntry.builder("Customer Service Excellence").icon("customer-service").build(), + CvEntry.builder("Operations & Process Support").icon("operations").build(), + CvEntry.builder("Project & Calendar Management").icon("calendar").build(), + CvEntry.builder("Data Management & Reporting").icon("reporting").build(), + CvEntry.builder("Communication & Stakeholders").icon("communication").build(), + CvEntry.builder("Problem Solving & Adaptability").icon("problem-solving").build(), + CvEntry.builder("Sales & Relationship Building").icon("sales").build(), + CvEntry.builder("Microsoft Office & Google Suite").icon("office-suite").build(), + CvEntry.builder("CRM Systems (Salesforce, HubSpot)").icon("crm").build(), + CvEntry.builder("Time Management & Prioritization").icon("time-management").build())); + } + + /** The achievements: a bold title over its body, beside one shared trophy. */ + static EntriesSection achievements() { + return new EntriesSection("SELECTED ACHIEVEMENTS", List.of( + CvEntry.builder("Process Improvement") + .icon("achievement") + .body("Designed a new tracking system that reduced reporting time " + + "by 30% and improved accuracy.") + .build(), + CvEntry.builder("Customer Satisfaction") + .icon("achievement") + .body("Maintained a 96% customer satisfaction rating through " + + "responsive support and issue resolution.") + .build(), + CvEntry.builder("Revenue Contribution") + .icon("achievement") + .body("Consistently exceeded monthly sales goals by 15\u201320% " + + "through relationship building and consultative service.") + .build())); + } + + /** The languages, each carrying both the rating and the word for it. */ + static SkillsSection languages() { + return new SkillsSection("LANGUAGES", List.of(new SkillGroup("LANGUAGES", List.of( + CvSkill.of("English", 1.0, "Native"), + CvSkill.of("Spanish", 0.8, "Professional Working"), + CvSkill.of("French", 0.4, "Basic"))))); + } + + /** The closing facts, on the same mark column at a wider pitch. */ + static EntriesSection facts() { + return new EntriesSection("ADDITIONAL INFORMATION", List.of( + CvEntry.builder("Availability: Full-time").icon("availability").build(), + CvEntry.builder("Willing to Relocate: Yes").icon("relocation").build(), + CvEntry.builder("Remote Work: Open to Hybrid/Remote").icon("remote-work").build())); + } + + static ParagraphSection profile() { + return new ParagraphSection("PROFESSIONAL PROFILE", + "Adaptable and resourceful professional with 6+ years of " + + "experience across operations, customer service, sales, and " + + "project support. Known for strong communication, " + + "organizational skills, and the ability to thrive in " + + "fast-paced environments. Adept at managing priorities, " + + "building relationships, and delivering results that drive " + + "efficiency, customer satisfaction, and business growth."); + } + + /** The roles held, each a headline over its bullets. */ + static EntriesSection experience() { + return new EntriesSection("PROFESSIONAL EXPERIENCE", List.of( + CvEntry.builder("Operations Coordinator") + .subtitle("BrightStart Logistics, Austin, TX") + .date("Jan 2022 \u2013 Present") + .body(String.join(NEWLINE, + "Coordinate daily operations and scheduling for a team of " + + "12 across multiple sites.", + "Develop and maintain dashboards and reports to track KPIs " + + "and operational metrics.", + "Streamline communication between departments, reducing " + + "response time by 25%.", + "Manage vendor relationships and assist with contract and " + + "invoice reconciliation.")) + .build(), + CvEntry.builder("Customer Support Specialist") + .subtitle("TechWave Solutions, Remote") + .date("Jun 2020 \u2013 Dec 2021") + .body(String.join(NEWLINE, + "Provided technical support to customers via phone, email, " + + "and live chat.", + "Resolved an average of 40+ inquiries daily with a " + + "first-contact resolution rate of 92%.", + "Created knowledge base articles and improved internal " + + "documentation.", + "Collaborated with product and engineering teams to " + + "escalate and resolve issues.")) + .build(), + CvEntry.builder("Sales Associate") + .subtitle("Urban Outfitters, Austin, TX") + .date("Mar 2019 \u2013 May 2020") + .body(String.join(NEWLINE, + "Consistently exceeded sales targets by 15\u201320% through " + + "product knowledge and personalized customer service.", + "Built and maintained strong customer relationships, " + + "driving repeat business.", + "Merchandised floor displays and contributed to visual " + + "standards and promotions.", + "Recognized as \u201cTop Performer\u201d for Q4 2019.")) + .build(), + CvEntry.builder("Administrative Assistant / Project Assistant") + .subtitle("Greenfield Nonprofit Initiative, Austin, TX") + .date("May 2018 \u2013 Feb 2019") + .body(String.join(NEWLINE, + "Supported project managers with scheduling, documentation, " + + "and meeting logistics.", + "Managed calendars, travel arrangements, and expense " + + "reports.", + "Maintained donor records and prepared reports for " + + "leadership and stakeholders.", + "Assisted with event planning and community outreach " + + "initiatives.")) + .build())); + } + + /** The degree; the design's third line is where and when, as one line. */ + static EntriesSection education() { + return new EntriesSection("EDUCATION", List.of( + CvEntry.builder("Bachelor of Arts in Communications") + .subtitle("University of Texas at Austin") + .place("Austin, TX \u2022 Graduated May 2018") + .build())); + } + + /** The certifications, one bulleted line each. */ + static EntriesSection certifications() { + return new EntriesSection("CERTIFICATIONS & TRAINING", List.of( + CvEntry.builder("Google Project Management Certificate (Coursera) \u2013 2023").build(), + CvEntry.builder("HubSpot Customer Service Certification \u2013 2022").build(), + CvEntry.builder("Microsoft Excel: Advanced Formulas & Functions \u2013 2021").build(), + CvEntry.builder("ServSafe Food Handler Certification \u2013 2019").build())); + } +} diff --git a/qa/src/test/java/com/demcha/compose/document/templates/cv/presets/SlateOrangeLayoutSnapshotTest.java b/qa/src/test/java/com/demcha/compose/document/templates/cv/presets/SlateOrangeLayoutSnapshotTest.java new file mode 100644 index 000000000..38afc1408 --- /dev/null +++ b/qa/src/test/java/com/demcha/compose/document/templates/cv/presets/SlateOrangeLayoutSnapshotTest.java @@ -0,0 +1,32 @@ +package com.demcha.compose.document.templates.cv.presets; + +import com.demcha.compose.GraphCompose; +import com.demcha.compose.document.api.DocumentSession; +import com.demcha.compose.document.templates.TemplateTestSupport; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Exact layout snapshot gate for {@link SlateOrange} — freezes the resolved + * geometry of the canonical one-page CV, which the pixel budget cannot see: a + * small column or spacing shift stays under the visual-diff budget but changes + * the snapshot. + * + *

The preset owns its page geometry, so the session starts unconfigured.

+ * + *

Refresh with {@code -Dgraphcompose.updateSnapshots=true} after a + * deliberate layout change, and commit the JSON with the change.

+ */ +class SlateOrangeLayoutSnapshotTest { + + @Test + void canonicalCvMatchesLayoutSnapshot() throws Exception { + try (DocumentSession session = GraphCompose.document().create()) { + SlateOrange.create().compose(session, SlateOrangeFixtures.canonicalCv()); + assertThat(session.layoutSnapshot().totalPages()).isEqualTo(1); + TemplateTestSupport.assertCanonicalSnapshot( + session, "slate_orange_layout", "cv-v2"); + } + } +} diff --git a/qa/src/test/java/com/demcha/compose/document/templates/cv/presets/SlateOrangeSmokeTest.java b/qa/src/test/java/com/demcha/compose/document/templates/cv/presets/SlateOrangeSmokeTest.java new file mode 100644 index 000000000..17e635b89 --- /dev/null +++ b/qa/src/test/java/com/demcha/compose/document/templates/cv/presets/SlateOrangeSmokeTest.java @@ -0,0 +1,277 @@ +package com.demcha.compose.document.templates.cv.presets; + +import com.demcha.compose.GraphCompose; +import com.demcha.compose.document.api.DocumentSession; +import com.demcha.compose.document.templates.api.DocumentTemplate; +import com.demcha.compose.document.templates.core.identity.Contact; +import com.demcha.compose.document.templates.cv.data.CvDocument; +import com.demcha.compose.document.templates.cv.data.CvEntry; +import com.demcha.compose.document.templates.cv.data.CvIdentity; +import com.demcha.compose.document.templates.cv.data.CvName; +import com.demcha.compose.document.templates.cv.data.CvSkill; +import com.demcha.compose.document.templates.cv.data.EntriesSection; +import com.demcha.compose.document.templates.cv.data.SkillGroup; +import com.demcha.compose.document.templates.cv.data.SkillsSection; +import com.demcha.compose.document.templates.cv.data.Slot; +import org.apache.pdfbox.Loader; +import org.apache.pdfbox.pdmodel.PDDocument; +import org.apache.pdfbox.pdmodel.PDPage; +import org.apache.pdfbox.pdmodel.interactive.action.PDActionURI; +import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotation; +import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationLink; +import org.apache.pdfbox.text.PDFTextStripper; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.OptionalDouble; +import java.util.Set; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +/** + * Smoke test for {@link SlateOrange} — proves the preset renders a + * {@link CvDocument} end-to-end with its packaged marks, draws the monogram + * from the name and the specialism strip from its berth's body, reports an + * unknown mark as a data error, draws both channels of a rated language, + * refuses a CV taller than its sheet rather than losing a role, and carries + * the channels and the titles as link annotations — which move no pixel and + * no layout node, so neither gate would notice them going missing. + */ +class SlateOrangeSmokeTest { + + private static byte[] render(CvDocument doc) throws Exception { + // The preset owns its page geometry, so the session starts unconfigured. + try (DocumentSession session = GraphCompose.document().create()) { + SlateOrange.create().compose(session, doc); + assertThat(session.roots()).isNotEmpty(); + byte[] pdfBytes = session.toPdfBytes(); + assertThat(pdfBytes).isNotEmpty(); + return pdfBytes; + } + } + + private static String textOf(byte[] pdfBytes) throws Exception { + try (PDDocument document = Loader.loadPDF(pdfBytes)) { + return new PDFTextStripper().getText(document); + } + } + + private static List linkTargets(byte[] pdfBytes) throws Exception { + List targets = new ArrayList<>(); + try (PDDocument document = Loader.loadPDF(pdfBytes)) { + for (PDPage page : document.getPages()) { + for (PDAnnotation annotation : page.getAnnotations()) { + if (annotation instanceof PDAnnotationLink link + && link.getAction() instanceof PDActionURI uri) { + targets.add(uri.getURI()); + } + } + } + } + return targets; + } + + /** The canonical document with one section replaced. */ + private static CvDocument withSection(Slot slot, EntriesSection replacement, + String replacedTitle) { + List placements = new ArrayList<>(); + for (CvDocument.Placement placement : SlateOrangeFixtures.canonicalCv().placements()) { + if (placement.section().title().equals(replacedTitle)) { + placements.add(new CvDocument.Placement(slot, replacement)); + } else { + placements.add(placement); + } + } + return new CvDocument(SlateOrangeFixtures.identity(), placements); + } + + @Test + void exposesStableIdentity() { + DocumentTemplate template = SlateOrange.create(); + assertThat(template.id()).isEqualTo(SlateOrange.ID); + assertThat(template.displayName()).isEqualTo(SlateOrange.DISPLAY_NAME); + } + + @Test + void rendersCanonicalCvWithPackagedMarks() throws Exception { + render(SlateOrangeFixtures.canonicalCv()); + } + + @Test + void canonicalRenderCarriesEveryBlocksText() throws Exception { + String text = textOf(render(SlateOrangeFixtures.canonicalCv())); + assertThat(text) + .contains("DANIEL HARPER") + .contains("VERSATILE PROFESSIONAL") + .contains("Customer Service Excellence") + .contains("Process Improvement") + .contains("Professional Working") + .contains("Adaptable and resourceful professional") + .contains("BrightStart Logistics, Austin, TX") + .contains("Bachelor of Arts in Communications") + .contains("ServSafe Food Handler Certification"); + } + + @Test + void theMonogramAndTheSpecialismStripAreDrawn() throws Exception { + // The monogram comes from the name's own initials, and the strip is + // the specialism berth's body joined by the design's own separator. + String text = textOf(render(SlateOrangeFixtures.canonicalCv())); + assertThat(text.lines().map(String::strip).toList()) + .anyMatch(line -> line.startsWith("DH")); + assertThat(text.replaceAll("(?U)\\s", "")) + .contains("OPERATIONS") + .contains("CUSTOMERSUCCESS") + .contains("COORDINATION") + .doesNotContain("SPECIALISMS"); + } + + @Test + void aLanguageDrawsBothItsRatingAndItsWording() throws Exception { + // The discs come from the level and the words beside them from the + // note; this is the block that wants both channels of a rated skill. + SkillsSection languages = new SkillsSection("LANGUAGES", + List.of(new SkillGroup("LANGUAGES", List.of( + CvSkill.of("Dutch", 0.6, "Conversational"))))); + List placements = new ArrayList<>(); + for (CvDocument.Placement placement : SlateOrangeFixtures.canonicalCv().placements()) { + placements.add(placement.section().title().equals("LANGUAGES") + ? new CvDocument.Placement(Slot.SIDEBAR, languages) + : placement); + } + + String text = textOf(render(new CvDocument(SlateOrangeFixtures.identity(), placements))); + assertThat(text).contains("Dutch").contains("Conversational"); + } + + @Test + void aSkillWithoutANoteLeavesThatColumnEmpty() throws Exception { + SkillsSection languages = new SkillsSection("LANGUAGES", + List.of(new SkillGroup("LANGUAGES", List.of( + new CvSkill("Dutch", OptionalDouble.of(0.6)))))); + List placements = new ArrayList<>(); + for (CvDocument.Placement placement : SlateOrangeFixtures.canonicalCv().placements()) { + placements.add(placement.section().title().equals("LANGUAGES") + ? new CvDocument.Placement(Slot.SIDEBAR, languages) + : placement); + } + + String text = textOf(render(new CvDocument(SlateOrangeFixtures.identity(), placements))); + assertThat(text).contains("Dutch").doesNotContain("Conversational"); + } + + @Test + void contactChannelsAndLinksAreClickable() throws Exception { + // The dial target is derived from the number as the document writes + // it: this one carries no country code, so neither does the target. + List targets = linkTargets(render(SlateOrangeFixtures.canonicalCv())); + assertThat(targets) + .contains("tel:5551234567") + .contains("mailto:daniel.harper@email.com") + .contains("https://www.linkedin.com/in/danielharper"); + } + + @Test + void aLinkShowsItsLabelAndHidesItsAddress() throws Exception { + byte[] pdfBytes = render(SlateOrangeFixtures.canonicalCv()); + assertThat(textOf(pdfBytes)) + .contains("LinkedIn") + .doesNotContain("linkedin.com/in/danielharper"); + assertThat(linkTargets(pdfBytes)).contains("https://www.linkedin.com/in/danielharper"); + } + + @Test + void aRoleAndADegreeBecomeLinksWhenTheirEntryCarriesOne() throws Exception { + List placements = new ArrayList<>(); + for (CvDocument.Placement placement : SlateOrangeFixtures.canonicalCv().placements()) { + placements.add(switch (placement.section().title()) { + case "PROFESSIONAL EXPERIENCE" -> new CvDocument.Placement(Slot.MAIN, + new EntriesSection("PROFESSIONAL EXPERIENCE", List.of( + CvEntry.builder("Operations Coordinator") + .subtitle("BrightStart Logistics, Austin, TX") + .date("Jan 2022") + .link("https://example.test/brightstart") + .body("Coordinate daily operations.") + .build()))); + case "EDUCATION" -> new CvDocument.Placement(Slot.FOOTER, + new EntriesSection("EDUCATION", List.of( + CvEntry.builder("Bachelor of Arts in Communications") + .subtitle("University of Texas at Austin") + .link("https://example.test/utexas") + .build()))); + default -> placement; + }); + } + + List targets = linkTargets( + render(new CvDocument(SlateOrangeFixtures.identity(), placements))); + assertThat(targets) + .contains("https://example.test/brightstart") + .contains("https://example.test/utexas"); + } + + @Test + void anUnknownMarkIsReportedAsADataError() { + EntriesSection wrong = new EntriesSection("CORE COMPETENCIES", List.of( + CvEntry.builder("Telepathy").icon("telescope").build())); + + assertThatThrownBy(() -> render(withSection(Slot.SIDEBAR, wrong, "CORE COMPETENCIES"))) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("telescope") + .hasMessageContaining("time-management"); + } + + @Test + void anAchievementWithoutAMarkTakesTheTrophy() throws Exception { + // Every achievement carries the same mark in this design, so an entry + // that names none is drawn with it rather than without one. + EntriesSection unmarked = new EntriesSection("SELECTED ACHIEVEMENTS", List.of( + CvEntry.builder("Process Improvement").body("Reduced reporting time.").build())); + + String text = textOf(render(withSection(Slot.SIDEBAR, unmarked, + "SELECTED ACHIEVEMENTS"))); + assertThat(text).contains("Process Improvement").contains("Reduced reporting time."); + } + + @Test + void aDocumentWithNothingButAnIdentityStillRenders() throws Exception { + // Every berth empty: the band, the monogram and the name, and nothing + // in either column. + render(new CvDocument(SlateOrangeFixtures.identity(), List.of())); + } + + @Test + void anIdentityWithoutLinksDrawsTheThreeChannels() throws Exception { + CvIdentity noLinks = new CvIdentity(CvName.of("Ada", "Lovelace"), "Analyst", + new Contact("+44 20 7946 0000", "ada@example.test", "London, UK"), + List.of(), Optional.empty()); + + // A run that breaks at a space carries one annotation per piece, so it + // is the set of targets that is asserted, not the count. + byte[] pdfBytes = render(new CvDocument(noLinks, List.of())); + assertThat(Set.copyOf(linkTargets(pdfBytes))) + .containsExactlyInAnyOrder("mailto:ada@example.test", "tel:+442079460000"); + } + + @Test + void refusesACvTallerThanTheSheet() { + // The body's two columns are one row, and a row is atomic: a CV that + // outgrows the sheet raises rather than losing a role to a silent cap. + List many = new ArrayList<>(); + for (int index = 0; index < 14; index++) { + many.add(CvEntry.builder("Operations Coordinator " + index) + .subtitle("BrightStart Logistics, Austin, TX") + .date("Jan 2022") + .body("Coordinate daily operations and scheduling for a team of 12.") + .build()); + } + + assertThatThrownBy(() -> render(withSection(Slot.MAIN, + new EntriesSection("PROFESSIONAL EXPERIENCE", many), + "PROFESSIONAL EXPERIENCE"))) + .hasMessageContaining("Body"); + } +} diff --git a/qa/src/test/java/com/demcha/compose/document/templates/cv/presets/SlateOrangeVisualParityTest.java b/qa/src/test/java/com/demcha/compose/document/templates/cv/presets/SlateOrangeVisualParityTest.java new file mode 100644 index 000000000..041eb0c5e --- /dev/null +++ b/qa/src/test/java/com/demcha/compose/document/templates/cv/presets/SlateOrangeVisualParityTest.java @@ -0,0 +1,41 @@ +package com.demcha.compose.document.templates.cv.presets; + +import com.demcha.compose.GraphCompose; +import com.demcha.compose.document.api.DocumentSession; +import com.demcha.compose.testing.visual.PdfVisualRegression; +import org.junit.jupiter.api.Test; + +import java.nio.file.Path; + +/** + * Pixel-diff visual parity gate for {@link SlateOrange} — the canonical + * one-page CV against a checked-in baseline. + * + *

Re-blessing baselines — after a deliberate visual + * change, re-run with {@code -Dgraphcompose.visual.approve=true} and commit + * the updated PNG with the change. Baselines live under + * {@code src/test/resources/visual-baselines/cv-v2-layered/}.

+ */ +class SlateOrangeVisualParityTest { + + private static final Path BASELINE_ROOT = Path.of( + "src", "test", "resources", "visual-baselines", "cv-v2-layered"); + + private static final long PIXEL_DIFF_BUDGET = 50_000L; + private static final int PER_PIXEL_TOLERANCE = 8; + + @Test + void rendersWithinPixelDiffBudget() throws Exception { + byte[] pdfBytes; + try (DocumentSession document = GraphCompose.document().create()) { + SlateOrange.create().compose(document, SlateOrangeFixtures.canonicalCv()); + pdfBytes = document.toPdfBytes(); + } + + PdfVisualRegression.standard() + .baselineRoot(BASELINE_ROOT) + .perPixelTolerance(PER_PIXEL_TOLERANCE) + .mismatchedPixelBudget(PIXEL_DIFF_BUDGET) + .assertMatchesBaseline("slate_orange", pdfBytes); + } +} diff --git a/qa/src/test/java/com/demcha/compose/document/templates/cv/presets/TealPulseFixtures.java b/qa/src/test/java/com/demcha/compose/document/templates/cv/presets/TealPulseFixtures.java new file mode 100644 index 000000000..d9138f809 --- /dev/null +++ b/qa/src/test/java/com/demcha/compose/document/templates/cv/presets/TealPulseFixtures.java @@ -0,0 +1,166 @@ +package com.demcha.compose.document.templates.cv.presets; + +import com.demcha.compose.document.templates.core.identity.Contact; +import com.demcha.compose.document.templates.core.identity.Link; +import com.demcha.compose.document.templates.cv.data.CvDocument; +import com.demcha.compose.document.templates.cv.data.CvEntry; +import com.demcha.compose.document.templates.cv.data.CvIdentity; +import com.demcha.compose.document.templates.cv.data.CvName; +import com.demcha.compose.document.templates.cv.data.CvSkill; +import com.demcha.compose.document.templates.cv.data.EntriesSection; +import com.demcha.compose.document.templates.cv.data.ParagraphSection; +import com.demcha.compose.document.templates.cv.data.SkillGroup; +import com.demcha.compose.document.templates.cv.data.SkillsSection; +import com.demcha.compose.document.templates.cv.data.Slot; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.OptionalDouble; + +/** + * Shared fixture data for the {@link TealPulse} gates — the SAME document + * feeds the pixel parity test and the layout snapshot test, so a geometry + * shift the pixel budget absorbs still trips the exact snapshot, and vice + * versa. + */ +final class TealPulseFixtures { + + /** The break an entry stacks its highlights on. */ + private static final String NEWLINE = String.valueOf((char) 10); + + /** The dash this sheet writes a date range with. */ + private static final String EN_DASH = String.valueOf((char) 0x2013); + + private TealPulseFixtures() { + } + + /** + * The canonical one-page CV — the brand mark, four contact channels + * including a link, twelve competencies, a summary, three roles with their + * highlights, a degree, four certifications, three facts and the closing + * tagline. + */ + static CvDocument canonicalCv() { + List placements = new ArrayList<>(); + placements.add(new CvDocument.Placement(Slot.SIDEBAR, competencies())); + placements.add(new CvDocument.Placement(Slot.MAIN, summary())); + placements.add(new CvDocument.Placement(Slot.MAIN, experience())); + placements.add(new CvDocument.Placement(Slot.FOOTER, education())); + placements.add(new CvDocument.Placement(Slot.FOOTER, certifications())); + placements.add(new CvDocument.Placement(Slot.FOOTER, facts())); + placements.add(new CvDocument.Placement(Slot.FOOTER, tagline())); + return new CvDocument(identity(), placements); + } + + static CvIdentity identity() { + return new CvIdentity( + CvName.of("ISABELLA", "MOORE"), + "REGISTERED NURSE", + new Contact("+44 7700 900123", "isabella.moore@email.com", + "Manchester, United Kingdom"), + List.of(new Link("LinkedIn", "https://www.linkedin.com/in/isabellamoore-rn")), + Optional.empty()); + } + + static SkillsSection competencies() { + List entries = new ArrayList<>(); + for (String name : new String[] { + "Patient Assessment", "Care Planning", "Medication Administration", + "Clinical Documentation", "Infection Prevention", "IV Therapy", + "Wound Care", "Patient Education", "Safeguarding", "Team Collaboration", + "Discharge Coordination", "Electronic Health Records"}) { + entries.add(new CvSkill(name, OptionalDouble.empty())); + } + return new SkillsSection("CORE COMPETENCIES", + List.of(new SkillGroup("CORE COMPETENCIES", entries))); + } + + static ParagraphSection summary() { + return new ParagraphSection("PROFESSIONAL SUMMARY", + "Compassionate Registered Nurse with 7+ years of experience delivering " + + "high-quality patient care across acute medical, surgical, and " + + "community settings. Skilled in patient assessment, medication " + + "administration, multidisciplinary collaboration, discharge " + + "planning, and maintaining accurate clinical documentation. Known " + + "for calm decision-making, strong communication, and patient-centred " + + "care."); + } + + static EntriesSection experience() { + return new EntriesSection("PROFESSIONAL EXPERIENCE", List.of( + CvEntry.builder("Senior Staff Nurse") + .subtitle("Manchester Royal Infirmary, Manchester, UK") + .date("2021" + EN_DASH + "Present") + .body(String.join(NEWLINE, + "Deliver direct nursing care for adult patients on a busy " + + "acute medical ward.", + "Coordinate care plans with doctors, therapists, and support " + + "staff to improve outcomes.", + "Supervise junior nurses and support student placements during " + + "clinical rotations.", + "Improved discharge coordination and patient communication " + + "across the ward.", + "Maintain precise documentation and ensure compliance with " + + "clinical standards.")) + .build(), + CvEntry.builder("Staff Nurse") + .subtitle("Salford General Hospital, Salford, UK") + .date("2018" + EN_DASH + "2021") + .body(String.join(NEWLINE, + "Provided nursing care across medical and surgical units.", + "Administered medications, monitored vital signs, and escalated " + + "deteriorating patients promptly.", + "Supported infection control procedures and safe discharge " + + "planning.", + "Built strong rapport with patients and families during " + + "treatment and recovery.")) + .build(), + CvEntry.builder("Community Nurse") + .subtitle("NorthCare Community Health, Greater Manchester, UK") + .date("2016" + EN_DASH + "2018") + .body(String.join(NEWLINE, + "Delivered home-based nursing care and patient education.", + "Managed wound care, medication support, and follow-up visits " + + "for vulnerable patients.", + "Worked closely with GPs, social services, and families to " + + "coordinate holistic care.")) + .build())); + } + + static EntriesSection education() { + return new EntriesSection("EDUCATION", List.of( + CvEntry.builder("BSc (Hons) Adult Nursing") + .subtitle("University of Manchester") + .date("2013" + EN_DASH + "2016") + .build())); + } + + static EntriesSection certifications() { + List entries = new ArrayList<>(); + for (String name : new String[] { + "NMC Registered Nurse", "Immediate Life Support (ILS)", + "Venepuncture and Cannulation Certification", "Safeguarding Adults Level 3"}) { + entries.add(CvEntry.builder(name).build()); + } + return new EntriesSection("CERTIFICATIONS", entries); + } + + static EntriesSection facts() { + return new EntriesSection("ADDITIONAL INFORMATION", List.of( + CvEntry.builder("Languages:") + .body("English (Native), Polish (Conversational)") + .build(), + CvEntry.builder("Right to Work:").body("United Kingdom").build(), + CvEntry.builder("Availability:").body("1 month notice").build())); + } + + /** + * The closing line. Its title names the berth and is not drawn — only the + * body reaches the sheet. + */ + static ParagraphSection tagline() { + return new ParagraphSection("TAGLINE", + "COMPASSIONATE CARE. CLINICAL EXCELLENCE. BETTER OUTCOMES."); + } +} diff --git a/qa/src/test/java/com/demcha/compose/document/templates/cv/presets/TealPulseLayoutSnapshotTest.java b/qa/src/test/java/com/demcha/compose/document/templates/cv/presets/TealPulseLayoutSnapshotTest.java new file mode 100644 index 000000000..2725dc099 --- /dev/null +++ b/qa/src/test/java/com/demcha/compose/document/templates/cv/presets/TealPulseLayoutSnapshotTest.java @@ -0,0 +1,32 @@ +package com.demcha.compose.document.templates.cv.presets; + +import com.demcha.compose.GraphCompose; +import com.demcha.compose.document.api.DocumentSession; +import com.demcha.compose.document.templates.TemplateTestSupport; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Exact layout snapshot gate for {@link TealPulse} — freezes the resolved + * geometry of the canonical one-page CV, which the pixel budget cannot see: a + * small column or spacing shift stays under the visual-diff budget but changes + * the snapshot. + * + *

The preset owns its page geometry, so the session starts unconfigured.

+ * + *

Refresh with {@code -Dgraphcompose.updateSnapshots=true} after a + * deliberate layout change, and commit the JSON with the change.

+ */ +class TealPulseLayoutSnapshotTest { + + @Test + void canonicalCvMatchesLayoutSnapshot() throws Exception { + try (DocumentSession session = GraphCompose.document().create()) { + TealPulse.create().compose(session, TealPulseFixtures.canonicalCv()); + assertThat(session.layoutSnapshot().totalPages()).isEqualTo(1); + TemplateTestSupport.assertCanonicalSnapshot( + session, "teal_pulse_layout", "cv-v2"); + } + } +} diff --git a/qa/src/test/java/com/demcha/compose/document/templates/cv/presets/TealPulseSmokeTest.java b/qa/src/test/java/com/demcha/compose/document/templates/cv/presets/TealPulseSmokeTest.java new file mode 100644 index 000000000..99be8f790 --- /dev/null +++ b/qa/src/test/java/com/demcha/compose/document/templates/cv/presets/TealPulseSmokeTest.java @@ -0,0 +1,265 @@ +package com.demcha.compose.document.templates.cv.presets; + +import com.demcha.compose.GraphCompose; +import com.demcha.compose.document.api.DocumentPageSize; +import com.demcha.compose.document.api.DocumentSession; +import com.demcha.compose.document.templates.api.DocumentTemplate; +import com.demcha.compose.document.templates.core.identity.Contact; +import com.demcha.compose.document.templates.cv.data.CvDocument; +import com.demcha.compose.document.templates.cv.data.CvEntry; +import com.demcha.compose.document.templates.cv.data.CvIdentity; +import com.demcha.compose.document.templates.cv.data.CvName; +import com.demcha.compose.document.templates.cv.data.EntriesSection; +import com.demcha.compose.document.templates.cv.data.Slot; +import org.apache.pdfbox.Loader; +import org.apache.pdfbox.pdmodel.PDDocument; +import org.apache.pdfbox.pdmodel.PDPage; +import org.apache.pdfbox.pdmodel.interactive.action.PDActionURI; +import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotation; +import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationLink; +import org.apache.pdfbox.text.PDFTextStripper; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.Set; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +/** + * Smoke test for {@link TealPulse} — proves the preset renders a + * {@link CvDocument} end-to-end with its packaged marks, sets its own page + * over whatever the caller configured, draws only the tagline's body, and + * carries the channels and the titles as link annotations, which move no pixel + * and no layout node so neither gate would notice them going missing. + * + *

It also pins what happens past one page, which is not what the other + * ported CV presets do: the bands flow, so a longer CV runs onto a second + * page, while the body row itself cannot be split.

+ */ +class TealPulseSmokeTest { + + private static byte[] render(CvDocument doc) throws Exception { + // The preset owns its page geometry, so the session starts unconfigured. + try (DocumentSession session = GraphCompose.document().create()) { + TealPulse.create().compose(session, doc); + assertThat(session.roots()).isNotEmpty(); + byte[] pdfBytes = session.toPdfBytes(); + assertThat(pdfBytes).isNotEmpty(); + return pdfBytes; + } + } + + private static String textOf(byte[] pdfBytes) throws Exception { + try (PDDocument document = Loader.loadPDF(pdfBytes)) { + return new PDFTextStripper().getText(document); + } + } + + /** + * The text with its spaces removed. The sheet's headings and its name are + * letter-spaced with real space characters, so what is being asserted is + * the wording, not the tracking. + */ + private static String compact(String text) { + return text.replaceAll("(?U)\\s", ""); + } + + private static List linkTargets(byte[] pdfBytes) throws Exception { + List targets = new ArrayList<>(); + try (PDDocument document = Loader.loadPDF(pdfBytes)) { + for (PDPage page : document.getPages()) { + for (PDAnnotation annotation : page.getAnnotations()) { + if (annotation instanceof PDAnnotationLink link + && link.getAction() instanceof PDActionURI uri) { + targets.add(uri.getURI()); + } + } + } + } + return targets; + } + + /** The canonical document with one section replaced. */ + private static CvDocument withSection(Slot slot, EntriesSection replacement, + String replacedTitle) { + List placements = new ArrayList<>(); + for (CvDocument.Placement placement : TealPulseFixtures.canonicalCv().placements()) { + if (placement.section().title().equals(replacedTitle)) { + placements.add(new CvDocument.Placement(slot, replacement)); + } else { + placements.add(placement); + } + } + return new CvDocument(TealPulseFixtures.identity(), placements); + } + + @Test + void exposesStableIdentity() { + DocumentTemplate template = TealPulse.create(); + assertThat(template.id()).isEqualTo(TealPulse.ID); + assertThat(template.displayName()).isEqualTo(TealPulse.DISPLAY_NAME); + } + + @Test + void rendersCanonicalCvWithPackagedMarks() throws Exception { + render(TealPulseFixtures.canonicalCv()); + } + + @Test + void canonicalRenderCarriesEveryBandsText() throws Exception { + String text = textOf(render(TealPulseFixtures.canonicalCv())); + assertThat(text) + .contains("isabella.moore@email.com") + .contains("Patient Assessment") + .contains("Compassionate Registered Nurse") + .contains("Manchester Royal Infirmary") + .contains("BSc (Hons) Adult Nursing") + .contains("NMC Registered Nurse") + .contains("Right to Work:"); + assertThat(compact(text)) + .contains("ISABELLAMOORE") + .contains("REGISTEREDNURSE") + .contains("PROFESSIONALEXPERIENCE") + .contains("COMPASSIONATECARE.CLINICALEXCELLENCE.BETTEROUTCOMES."); + } + + @Test + void theTaglineBerthDrawsItsBodyAndNotItsTitle() throws Exception { + // The title is how a document names the berth; only the line reaches + // the sheet, so a document is free to call the berth what it likes. + String text = compact(textOf(render(TealPulseFixtures.canonicalCv()))); + assertThat(text) + .contains("COMPASSIONATECARE.") + .doesNotContain("TAGLINE"); + } + + @Test + void thePresetSetsItsOwnPageOverTheCallersOwn() throws Exception { + // The sheet is a fixed composition on the design's own page, so a + // caller who configures A4 still gets the design's proportion. + try (DocumentSession session = GraphCompose.document() + .pageSize(DocumentPageSize.A4) + .margin(40f, 40f, 40f, 40f) + .create()) { + TealPulse.create().compose(session, TealPulseFixtures.canonicalCv()); + byte[] pdfBytes = session.toPdfBytes(); + try (PDDocument document = Loader.loadPDF(pdfBytes)) { + float height = document.getPage(0).getMediaBox().getHeight(); + assertThat(height).isCloseTo((float) TealPulseStyles.PAGE_HEIGHT, + org.assertj.core.data.Offset.offset(0.5f)); + assertThat(height).isNotCloseTo((float) DocumentPageSize.A4.height(), + org.assertj.core.data.Offset.offset(1.0f)); + } + } + } + + @Test + void contactChannelsAndLinksAreClickable() throws Exception { + List targets = linkTargets(render(TealPulseFixtures.canonicalCv())); + assertThat(targets) + .contains("mailto:isabella.moore@email.com") + .contains("tel:+447700900123") + .contains("https://www.linkedin.com/in/isabellamoore-rn"); + } + + @Test + void aLinkShowsItsLabelAndHidesItsAddress() throws Exception { + // The strip draws the label, so its gaps are the same whatever the + // profile behind it is called; the address is reachable, not written. + byte[] pdfBytes = render(TealPulseFixtures.canonicalCv()); + assertThat(textOf(pdfBytes)) + .contains("LinkedIn") + .doesNotContain("linkedin.com/in/isabellamoore-rn"); + assertThat(linkTargets(pdfBytes)) + .contains("https://www.linkedin.com/in/isabellamoore-rn"); + } + + @Test + void aRoleAndADegreeBecomeLinksWhenTheirEntryCarriesOne() throws Exception { + CvDocument linked = withSection(Slot.MAIN, + new EntriesSection("PROFESSIONAL EXPERIENCE", List.of( + CvEntry.builder("Senior Staff Nurse") + .subtitle("Manchester Royal Infirmary, Manchester, UK") + .date("2021") + .link("https://example.test/mri") + .body("Deliver direct nursing care.") + .build())), + "PROFESSIONAL EXPERIENCE"); + List placements = new ArrayList<>(); + for (CvDocument.Placement placement : linked.placements()) { + if (placement.section().title().equals("EDUCATION")) { + placements.add(new CvDocument.Placement(Slot.FOOTER, + new EntriesSection("EDUCATION", List.of( + CvEntry.builder("BSc (Hons) Adult Nursing") + .subtitle("University of Manchester") + .date("2013") + .link("https://example.test/manchester") + .build())))); + } else { + placements.add(placement); + } + } + + List targets = linkTargets( + render(new CvDocument(TealPulseFixtures.identity(), placements))); + assertThat(targets) + .contains("https://example.test/mri") + .contains("https://example.test/manchester"); + } + + @Test + void aDocumentWithNothingButAnIdentityStillRenders() throws Exception { + // Every berth empty: no badge, no heading, and nothing drawn but the + // mark, the name and the rules that frame them. + render(new CvDocument(TealPulseFixtures.identity(), List.of())); + } + + @Test + void anIdentityWithoutLinksDrawsTheThreeChannels() throws Exception { + CvIdentity noLinks = new CvIdentity(CvName.of("Ada", "Lovelace"), "Analyst", + new Contact("+44 20 7946 0000", "ada@example.test", "London, UK"), + List.of(), Optional.empty()); + + // A run that breaks at a space carries one annotation per piece, so it + // is the set of targets that is asserted, not the count. + byte[] pdfBytes = render(new CvDocument(noLinks, List.of())); + assertThat(Set.copyOf(linkTargets(pdfBytes))) + .containsExactlyInAnyOrder("mailto:ada@example.test", "tel:+442079460000"); + } + + @Test + void aLongerCvCarriesTheClosingBandOntoASecondPage() throws Exception { + // The bands are stacked in the page flow, so a CV with more roles than + // the design holds runs over rather than losing anything. + try (DocumentSession session = GraphCompose.document().create()) { + TealPulse.create().compose(session, withRoles(6)); + assertThat(session.layoutSnapshot().totalPages()).isEqualTo(2); + } + } + + @Test + void refusesABodyTallerThanOnePage() { + // What cannot flow is the body itself: its two columns are one row, + // and a row is atomic, so it raises rather than being cut in half. + assertThatThrownBy(() -> render(withRoles(14))) + .hasMessageContaining("BodyGrid"); + } + + /** The canonical CV with its experience replaced by {@code count} roles. */ + private static CvDocument withRoles(int count) { + List many = new ArrayList<>(); + for (int index = 0; index < count; index++) { + many.add(CvEntry.builder("Senior Staff Nurse " + index) + .subtitle("Manchester Royal Infirmary, Manchester, UK") + .date("2021") + .body("Deliver direct nursing care for adult patients on a busy ward.") + .build()); + } + return withSection(Slot.MAIN, + new EntriesSection("PROFESSIONAL EXPERIENCE", many), + "PROFESSIONAL EXPERIENCE"); + } +} diff --git a/qa/src/test/java/com/demcha/compose/document/templates/cv/presets/TealPulseVisualParityTest.java b/qa/src/test/java/com/demcha/compose/document/templates/cv/presets/TealPulseVisualParityTest.java new file mode 100644 index 000000000..70dd5fd05 --- /dev/null +++ b/qa/src/test/java/com/demcha/compose/document/templates/cv/presets/TealPulseVisualParityTest.java @@ -0,0 +1,41 @@ +package com.demcha.compose.document.templates.cv.presets; + +import com.demcha.compose.GraphCompose; +import com.demcha.compose.document.api.DocumentSession; +import com.demcha.compose.testing.visual.PdfVisualRegression; +import org.junit.jupiter.api.Test; + +import java.nio.file.Path; + +/** + * Pixel-diff visual parity gate for {@link TealPulse} — the canonical + * one-page CV against a checked-in baseline. + * + *

Re-blessing baselines — after a deliberate visual + * change, re-run with {@code -Dgraphcompose.visual.approve=true} and commit + * the updated PNG with the change. Baselines live under + * {@code src/test/resources/visual-baselines/cv-v2-layered/}.

+ */ +class TealPulseVisualParityTest { + + private static final Path BASELINE_ROOT = Path.of( + "src", "test", "resources", "visual-baselines", "cv-v2-layered"); + + private static final long PIXEL_DIFF_BUDGET = 50_000L; + private static final int PER_PIXEL_TOLERANCE = 8; + + @Test + void rendersWithinPixelDiffBudget() throws Exception { + byte[] pdfBytes; + try (DocumentSession document = GraphCompose.document().create()) { + TealPulse.create().compose(document, TealPulseFixtures.canonicalCv()); + pdfBytes = document.toPdfBytes(); + } + + PdfVisualRegression.standard() + .baselineRoot(BASELINE_ROOT) + .perPixelTolerance(PER_PIXEL_TOLERANCE) + .mismatchedPixelBudget(PIXEL_DIFF_BUDGET) + .assertMatchesBaseline("teal_pulse", pdfBytes); + } +} diff --git a/qa/src/test/java/com/demcha/compose/document/templates/cv/presets/TerracottaRailFixtures.java b/qa/src/test/java/com/demcha/compose/document/templates/cv/presets/TerracottaRailFixtures.java new file mode 100644 index 000000000..4852b5601 --- /dev/null +++ b/qa/src/test/java/com/demcha/compose/document/templates/cv/presets/TerracottaRailFixtures.java @@ -0,0 +1,205 @@ +package com.demcha.compose.document.templates.cv.presets; + +import com.demcha.compose.document.templates.core.identity.Contact; +import com.demcha.compose.document.templates.core.identity.Link; +import com.demcha.compose.document.templates.cv.data.CvDocument; +import com.demcha.compose.document.templates.cv.data.CvEntry; +import com.demcha.compose.document.templates.cv.data.CvIdentity; +import com.demcha.compose.document.templates.cv.data.CvName; +import com.demcha.compose.document.templates.cv.data.CvSkill; +import com.demcha.compose.document.templates.cv.data.EntriesSection; +import com.demcha.compose.document.templates.cv.data.ParagraphSection; +import com.demcha.compose.document.templates.cv.data.SkillGroup; +import com.demcha.compose.document.templates.cv.data.SkillsSection; +import com.demcha.compose.document.templates.cv.data.Slot; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.OptionalDouble; + +/** + * Shared fixture data for the {@link TerracottaRail} gates — the SAME + * document feeds the pixel parity test and the layout snapshot test, so a + * geometry shift the pixel budget absorbs still trips the exact snapshot, + * and vice versa. + */ +final class TerracottaRailFixtures { + + /** The break a fact stacks its values on. */ + private static final String NEWLINE = String.valueOf((char) 10); + + /** The dash this sheet writes a date range with. */ + private static final String EN_DASH = String.valueOf((char) 0x2013); + + private TerracottaRailFixtures() { + } + + /** + * The canonical one-page CV — the monogram, four contact rows including + * a link, two bulleted lists, two credentials, three facts behind their + * marks, a three-paragraph summary, three roles on the rail, three + * marked projects and two degrees. + */ + static CvDocument canonicalCv() { + List placements = new ArrayList<>(); + placements.add(new CvDocument.Placement(Slot.SIDEBAR, competencies())); + placements.add(new CvDocument.Placement(Slot.SIDEBAR, software())); + placements.add(new CvDocument.Placement(Slot.SIDEBAR, certifications())); + placements.add(new CvDocument.Placement(Slot.SIDEBAR, facts())); + placements.add(new CvDocument.Placement(Slot.MAIN, summary())); + placements.add(new CvDocument.Placement(Slot.MAIN, experience())); + placements.add(new CvDocument.Placement(Slot.MAIN, projects())); + placements.add(new CvDocument.Placement(Slot.MAIN, education())); + return new CvDocument(identity(), placements); + } + + static CvIdentity identity() { + return new CvIdentity( + CvName.of("OLIVER", "BENNETT"), + "SENIOR ARCHITECT", + new Contact("+44 7700 900123", "oliver.bennett@email.com", + "Bristol, United Kingdom"), + List.of(new Link("LinkedIn", + "https://linkedin.com/in/oliverbennett-architect")), + Optional.empty()); + } + + static SkillsSection competencies() { + return plainSkills("CORE COMPETENCIES", + "Architectural Design", "Concept Development", "Planning Applications", + "Technical Drawings", "Revit", "AutoCAD", "BIM Coordination", + "Design Presentations", "Project Delivery", "Stakeholder Management", + "Sustainable Design", "Building Regulations"); + } + + static SkillsSection software() { + return plainSkills("SOFTWARE", + "Revit", "AutoCAD", "Rhino", "SketchUp", "Adobe InDesign", "Photoshop", + "Microsoft Office"); + } + + static EntriesSection certifications() { + return new EntriesSection("CERTIFICATIONS", List.of( + CvEntry.builder("ARB Registered Architect").build(), + CvEntry.builder("RIBA Chartered Member").build())); + } + + static EntriesSection facts() { + return new EntriesSection("ADDITIONAL INFORMATION", List.of( + CvEntry.builder("Languages:") + .icon("globe") + .body("English (Native)," + NEWLINE + "Spanish (Conversational)") + .build(), + CvEntry.builder("Right to Work:") + .icon("badge") + .body("United Kingdom") + .build(), + CvEntry.builder("Availability:") + .icon("clock") + .body("1 month notice") + .build())); + } + + static ParagraphSection summary() { + return new ParagraphSection("PROFESSIONAL SUMMARY", String.join(NEWLINE, + "Senior Architect with over 8 years of experience delivering residential, " + + "mixed-use, and commercial projects across all RIBA work stages.", + "Proven expertise in design development, planning applications, and technical " + + "delivery with a strong focus on sustainability, quality, and user " + + "experience.", + "Collaborative communicator skilled in coordinating consultants, engaging " + + "stakeholders, and leading multidisciplinary teams to achieve " + + "successful outcomes.")); + } + + static EntriesSection experience() { + return new EntriesSection("PROFESSIONAL EXPERIENCE", List.of( + CvEntry.builder("Senior Architect") + .subtitle("Northline Studio, Bristol, UK") + .date("2021" + EN_DASH + "Present") + .body(String.join(NEWLINE, + "Lead design packages from concept through to detailed design " + + "across mid-scale residential and mixed-use projects.", + "Coordinate structural, M&E, landscape and planning consultants " + + "to ensure integrated and buildable solutions.", + "Prepare and manage planning applications, design and access " + + "statements, and supporting documentation.", + "Present and communicate design proposals to clients and " + + "stakeholders, securing approvals and driving projects " + + "forward.")) + .build(), + CvEntry.builder("Architect") + .subtitle("Urban Form Partners, Bath, UK") + .date("2018" + EN_DASH + "2021") + .body(String.join(NEWLINE, + "Developed concept and technical designs for a range of " + + "residential, commercial, and education projects.", + "Prepared coordinated drawing sets, schedules, and " + + "specifications to support planning and construction.", + "Supported project delivery on site, resolving design queries " + + "and ensuring quality outcomes.", + "Coordinated with contractors and consultants to maintain " + + "programme, budget and design intent.")) + .build(), + CvEntry.builder("Part II Architectural Assistant") + .subtitle("Axis Design Workshop, Cardiff, UK") + .date("2016" + EN_DASH + "2018") + .body(String.join(NEWLINE, + "Supported senior architects across all RIBA stages on a " + + "variety of project types.", + "Produced CAD and Revit drawings, models, and visualisations to " + + "communicate design intent.", + "Undertook research, prepared presentation boards, and assisted " + + "with planning documentation.")) + .build())); + } + + static EntriesSection projects() { + return new EntriesSection("SELECTED PROJECTS", List.of( + CvEntry.builder("Harbour Point") + .subtitle("Mixed-Use Regeneration") + .place("Bristol") + .icon("building") + .body("Mixed-use development delivering 142 homes, retail space and " + + "public realm improvements; secured planning consent and is " + + "now on site.") + .build(), + CvEntry.builder("The Assembly Hotel") + .subtitle("Boutique Hotel Refurbishment") + .place("Bath") + .icon("hotel") + .body("Sensitive refurbishment of listed building to create a " + + "36-bedroom boutique hotel; enhanced heritage features and " + + "guest experience.") + .build(), + CvEntry.builder("Elmwood Mews") + .subtitle("Residential Development") + .place("Clifton, Bristol") + .icon("house") + .body("Design of 28 high-quality homes within a sustainable masterplan; " + + "achieved high environmental standards and strong sales " + + "performance.") + .build())); + } + + static EntriesSection education() { + return new EntriesSection("EDUCATION", List.of( + CvEntry.builder("MArch Architecture") + .subtitle("University of Sheffield") + .date("2014" + EN_DASH + "2016") + .build(), + CvEntry.builder("BA (Hons) Architecture") + .subtitle("University of the West of England") + .date("2011" + EN_DASH + "2014") + .build())); + } + + private static SkillsSection plainSkills(String title, String... names) { + List entries = new ArrayList<>(); + for (String name : names) { + entries.add(new CvSkill(name, OptionalDouble.empty())); + } + return new SkillsSection(title, List.of(new SkillGroup(title, entries))); + } +} diff --git a/qa/src/test/java/com/demcha/compose/document/templates/cv/presets/TerracottaRailLayoutSnapshotTest.java b/qa/src/test/java/com/demcha/compose/document/templates/cv/presets/TerracottaRailLayoutSnapshotTest.java new file mode 100644 index 000000000..bb2a3af80 --- /dev/null +++ b/qa/src/test/java/com/demcha/compose/document/templates/cv/presets/TerracottaRailLayoutSnapshotTest.java @@ -0,0 +1,38 @@ +package com.demcha.compose.document.templates.cv.presets; + +import com.demcha.compose.GraphCompose; +import com.demcha.compose.document.api.DocumentPageSize; +import com.demcha.compose.document.api.DocumentSession; +import com.demcha.compose.document.templates.TemplateTestSupport; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Exact layout snapshot gate for {@link TerracottaRail} — freezes the + * resolved geometry of the canonical one-page CV, which the pixel budget + * cannot see: a small column or spacing shift stays under the visual-diff + * budget but changes the snapshot. + * + *

This preset leaves the page to the caller, so the session sets A4 with + * no margin: both columns run to the paper edge and each carries its own + * padding.

+ * + *

Refresh with {@code -Dgraphcompose.updateSnapshots=true} after a + * deliberate layout change, and commit the JSON with the change.

+ */ +class TerracottaRailLayoutSnapshotTest { + + @Test + void canonicalCvMatchesLayoutSnapshot() throws Exception { + try (DocumentSession session = GraphCompose.document() + .pageSize(DocumentPageSize.A4) + .margin(0f, 0f, 0f, 0f) + .create()) { + TerracottaRail.create().compose(session, TerracottaRailFixtures.canonicalCv()); + assertThat(session.layoutSnapshot().totalPages()).isEqualTo(1); + TemplateTestSupport.assertCanonicalSnapshot( + session, "terracotta_rail_layout", "cv-v2"); + } + } +} diff --git a/qa/src/test/java/com/demcha/compose/document/templates/cv/presets/TerracottaRailSmokeTest.java b/qa/src/test/java/com/demcha/compose/document/templates/cv/presets/TerracottaRailSmokeTest.java new file mode 100644 index 000000000..963a3be40 --- /dev/null +++ b/qa/src/test/java/com/demcha/compose/document/templates/cv/presets/TerracottaRailSmokeTest.java @@ -0,0 +1,299 @@ +package com.demcha.compose.document.templates.cv.presets; + +import com.demcha.compose.GraphCompose; +import com.demcha.compose.document.api.DocumentPageSize; +import com.demcha.compose.document.api.DocumentSession; +import com.demcha.compose.document.templates.api.DocumentTemplate; +import com.demcha.compose.document.templates.core.identity.Contact; +import com.demcha.compose.document.templates.core.identity.Link; +import com.demcha.compose.document.templates.cv.data.CvDocument; +import com.demcha.compose.document.templates.cv.data.CvEntry; +import com.demcha.compose.document.templates.cv.data.CvIdentity; +import com.demcha.compose.document.templates.cv.data.CvName; +import com.demcha.compose.document.templates.cv.data.EntriesSection; +import com.demcha.compose.document.templates.cv.data.Slot; +import org.apache.pdfbox.Loader; +import org.apache.pdfbox.pdmodel.PDDocument; +import org.apache.pdfbox.pdmodel.PDPage; +import org.apache.pdfbox.pdmodel.interactive.action.PDActionURI; +import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotation; +import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationLink; +import org.apache.pdfbox.text.PDFTextStripper; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.Set; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +/** + * Smoke test for {@link TerracottaRail} — proves the preset renders a + * {@link CvDocument} end-to-end with its packaged marks, draws the monogram + * from the name, reports an unknown icon token as a data error, refuses a CV + * longer than its sheet rather than losing an entry, and carries the contact + * channels and a project title as link annotations, which move no pixel and + * no layout node so neither gate would notice them going missing. + */ +class TerracottaRailSmokeTest { + + private static byte[] render(CvDocument doc) throws Exception { + try (DocumentSession session = GraphCompose.document() + .pageSize(DocumentPageSize.A4) + .margin(0f, 0f, 0f, 0f) + .create()) { + TerracottaRail.create().compose(session, doc); + assertThat(session.roots()).isNotEmpty(); + byte[] pdfBytes = session.toPdfBytes(); + assertThat(pdfBytes).isNotEmpty(); + return pdfBytes; + } + } + + private static String textOf(byte[] pdfBytes) throws Exception { + try (PDDocument document = Loader.loadPDF(pdfBytes)) { + return new PDFTextStripper().getText(document); + } + } + + /** + * The text with its spaces removed. The sheet's headings and its masthead + * are letter-spaced with real space characters, so what is being asserted + * is the wording, not the tracking. + */ + private static String compact(String text) { + return text.replaceAll("(?U)\\s", ""); + } + + private static List linkTargets(byte[] pdfBytes) throws Exception { + List targets = new ArrayList<>(); + try (PDDocument document = Loader.loadPDF(pdfBytes)) { + for (PDPage page : document.getPages()) { + for (PDAnnotation annotation : page.getAnnotations()) { + if (annotation instanceof PDAnnotationLink link + && link.getAction() instanceof PDActionURI uri) { + targets.add(uri.getURI()); + } + } + } + } + return targets; + } + + /** The canonical document with one section replaced. */ + private static CvDocument withSection(Slot slot, EntriesSection replacement, + String replacedTitle) { + List placements = new ArrayList<>(); + for (CvDocument.Placement placement : TerracottaRailFixtures.canonicalCv().placements()) { + if (placement.section().title().equals(replacedTitle)) { + placements.add(new CvDocument.Placement(slot, replacement)); + } else { + placements.add(placement); + } + } + return new CvDocument(TerracottaRailFixtures.identity(), placements); + } + + @Test + void exposesStableIdentity() { + DocumentTemplate template = TerracottaRail.create(); + assertThat(template.id()).isEqualTo(TerracottaRail.ID); + assertThat(template.displayName()).isEqualTo(TerracottaRail.DISPLAY_NAME); + } + + @Test + void rendersCanonicalCvWithPackagedMarks() throws Exception { + render(TerracottaRailFixtures.canonicalCv()); + } + + @Test + void canonicalRenderCarriesEveryColumnsText() throws Exception { + String text = textOf(render(TerracottaRailFixtures.canonicalCv())); + assertThat(text) + .contains("oliver.bennett@email.com") + .contains("Architectural Design") + .contains("Microsoft Office") + .contains("ARB Registered Architect") + .contains("Northline Studio, Bristol, UK") + .contains("Harbour Point") + .contains("University of Sheffield"); + assertThat(compact(text)) + .contains("OLIVERBENNETT") + .contains("SENIORARCHITECT") + .contains("PROFESSIONALEXPERIENCE"); + } + + @Test + void theMonogramIsDrawnFromTheName() throws Exception { + // Two capitals on their own line, and no field of their own in the + // model: the initials are the whole source. + String text = textOf(render(TerracottaRailFixtures.canonicalCv())); + assertThat(text.lines().map(String::strip).toList()).contains("OB"); + } + + @Test + void contactChannelsAndLinksAreClickable() throws Exception { + List targets = linkTargets(render(TerracottaRailFixtures.canonicalCv())); + assertThat(targets) + .contains("mailto:oliver.bennett@email.com") + .contains("tel:+447700900123") + .contains("https://linkedin.com/in/oliverbennett-architect"); + } + + @Test + void aLinkShowsItsLabelAndHidesItsAddress() throws Exception { + // The row draws the label, so its width is the same whatever the + // profile behind it is called; the address is reachable, not written. + byte[] pdfBytes = render(TerracottaRailFixtures.canonicalCv()); + assertThat(textOf(pdfBytes)) + .contains("LinkedIn") + .doesNotContain("linkedin.com/in/oliverbennett-architect"); + assertThat(linkTargets(pdfBytes)) + .contains("https://linkedin.com/in/oliverbennett-architect"); + } + + @Test + void everyContactRowStartsOnTheSameAxis() throws Exception { + // Four rows, one mark width and one gap: a link that was set smaller + // or nudged in would put its text on an axis of its own. + try (DocumentSession session = GraphCompose.document() + .pageSize(DocumentPageSize.A4) + .margin(0f, 0f, 0f, 0f) + .create()) { + TerracottaRail.create().compose(session, TerracottaRailFixtures.canonicalCv()); + List heights = session.layoutSnapshot().nodes().stream() + // A timeline contributes nodes of its own, and an engine-generated node + // carries no entity name. + .filter(node -> node.entityName() != null + && node.entityName().startsWith("Contact_")) + .map(node -> node.placementHeight()) + .toList(); + assertThat(heights).hasSize(4); + assertThat(heights).allMatch(height -> Math.abs(height - heights.get(0)) < 0.01, + "every contact row is as tall as the first"); + } + } + + @Test + void everyTitleBecomesALinkWhenItsEntryCarriesOne() throws Exception { + // A role, a degree and a credential, each pointing somewhere. None of + // it moves a pixel or a layout node, so this is the only thing that + // would notice the annotations going missing. + List placements = new ArrayList<>(); + for (CvDocument.Placement placement : TerracottaRailFixtures.canonicalCv().placements()) { + placements.add(switch (placement.section().title()) { + case "PROFESSIONAL EXPERIENCE" -> new CvDocument.Placement(Slot.MAIN, + new EntriesSection("PROFESSIONAL EXPERIENCE", List.of( + CvEntry.builder("Senior Architect") + .subtitle("Northline Studio, Bristol, UK") + .date("2021") + .link("https://example.test/northline") + .body("Lead design packages.") + .build()))); + case "EDUCATION" -> new CvDocument.Placement(Slot.MAIN, + new EntriesSection("EDUCATION", List.of( + CvEntry.builder("MArch Architecture") + .subtitle("University of Sheffield") + .date("2014") + .link("https://example.test/sheffield") + .build()))); + case "CERTIFICATIONS" -> new CvDocument.Placement(Slot.SIDEBAR, + new EntriesSection("CERTIFICATIONS", List.of( + CvEntry.builder("ARB Registered Architect") + .link("https://example.test/arb") + .build()))); + default -> placement; + }); + } + + List targets = linkTargets( + render(new CvDocument(TerracottaRailFixtures.identity(), placements))); + assertThat(targets) + .contains("https://example.test/northline") + .contains("https://example.test/sheffield") + .contains("https://example.test/arb"); + } + + @Test + void aProjectTitleBecomesALinkWhenItsEntryCarriesOne() throws Exception { + EntriesSection linked = new EntriesSection("SELECTED PROJECTS", List.of( + CvEntry.builder("Harbour Point") + .subtitle("Mixed-Use Regeneration") + .place("Bristol") + .icon("building") + .link("https://example.test/harbour-point") + .body("Mixed-use development delivering 142 homes.") + .build())); + + List targets = linkTargets( + render(withSection(Slot.MAIN, linked, "SELECTED PROJECTS"))); + assertThat(targets).contains("https://example.test/harbour-point"); + } + + @Test + void anEntryWithoutAMarkIsDrawnWithoutOne() throws Exception { + EntriesSection unmarked = new EntriesSection("ADDITIONAL INFORMATION", List.of( + CvEntry.builder("Right to Work:").body("United Kingdom").build())); + + String text = textOf(render(withSection(Slot.SIDEBAR, unmarked, + "ADDITIONAL INFORMATION"))); + assertThat(text).contains("Right to Work:").contains("United Kingdom"); + } + + @Test + void anUnknownMarkIsReportedAsADataError() { + EntriesSection wrong = new EntriesSection("ADDITIONAL INFORMATION", List.of( + CvEntry.builder("Languages:").icon("telescope").body("English").build())); + + assertThatThrownBy(() -> render(withSection(Slot.SIDEBAR, wrong, + "ADDITIONAL INFORMATION"))) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("telescope") + .hasMessageContaining("badge"); + } + + @Test + void aDocumentWithNothingButAnIdentityStillRenders() throws Exception { + // Every berth empty: no heading, no divider, and nothing drawn but + // the monogram, its rule and the masthead. + render(new CvDocument(TerracottaRailFixtures.identity(), List.of())); + } + + @Test + void anIdentityWithoutLinksDrawsTheThreeChannelsAndNothingElse() throws Exception { + // The contact triple is non-blank by construction, so the three + // channels are always there; the links are what a document may omit. + CvIdentity noLinks = new CvIdentity(CvName.of("Ada", "Lovelace"), "Analyst", + new Contact("+44 20 7946 0000", "ada@example.test", "London, UK"), + List.of(), Optional.empty()); + + // A run that wraps or breaks at a space carries one annotation per + // piece, so it is the set of targets that is asserted, not the count. + byte[] pdfBytes = render(new CvDocument(noLinks, List.of())); + assertThat(Set.copyOf(linkTargets(pdfBytes))) + .containsExactlyInAnyOrder("mailto:ada@example.test", "tel:+442079460000"); + assertThat(textOf(pdfBytes).lines().map(String::strip).toList()).contains("AL"); + } + + @Test + void refusesACvLongerThanTheSheet() { + // The two columns are one row, and a row is atomic: a CV that outgrows + // the sheet raises rather than losing an entry to a silent cap. + List many = new ArrayList<>(); + for (int index = 0; index < 14; index++) { + many.add(CvEntry.builder("Senior Architect " + index) + .subtitle("Northline Studio, Bristol, UK") + .date("2021") + .body("Lead design packages from concept through to detailed design.") + .build()); + } + CvDocument tooLong = withSection(Slot.MAIN, + new EntriesSection("PROFESSIONAL EXPERIENCE", many), + "PROFESSIONAL EXPERIENCE"); + + assertThatThrownBy(() -> render(tooLong)) + .hasMessageContaining("Body"); + } +} diff --git a/qa/src/test/java/com/demcha/compose/document/templates/cv/presets/TerracottaRailVisualParityTest.java b/qa/src/test/java/com/demcha/compose/document/templates/cv/presets/TerracottaRailVisualParityTest.java new file mode 100644 index 000000000..e4833448e --- /dev/null +++ b/qa/src/test/java/com/demcha/compose/document/templates/cv/presets/TerracottaRailVisualParityTest.java @@ -0,0 +1,45 @@ +package com.demcha.compose.document.templates.cv.presets; + +import com.demcha.compose.GraphCompose; +import com.demcha.compose.document.api.DocumentPageSize; +import com.demcha.compose.document.api.DocumentSession; +import com.demcha.compose.testing.visual.PdfVisualRegression; +import org.junit.jupiter.api.Test; + +import java.nio.file.Path; + +/** + * Pixel-diff visual parity gate for {@link TerracottaRail} — the canonical + * one-page CV against a checked-in baseline. + * + *

Re-blessing baselines — after a deliberate visual + * change, re-run with {@code -Dgraphcompose.visual.approve=true} and commit + * the updated PNG with the change. Baselines live under + * {@code src/test/resources/visual-baselines/cv-v2-layered/}.

+ */ +class TerracottaRailVisualParityTest { + + private static final Path BASELINE_ROOT = Path.of( + "src", "test", "resources", "visual-baselines", "cv-v2-layered"); + + private static final long PIXEL_DIFF_BUDGET = 50_000L; + private static final int PER_PIXEL_TOLERANCE = 8; + + @Test + void rendersWithinPixelDiffBudget() throws Exception { + byte[] pdfBytes; + try (DocumentSession document = GraphCompose.document() + .pageSize(DocumentPageSize.A4) + .margin(0f, 0f, 0f, 0f) + .create()) { + TerracottaRail.create().compose(document, TerracottaRailFixtures.canonicalCv()); + pdfBytes = document.toPdfBytes(); + } + + PdfVisualRegression.standard() + .baselineRoot(BASELINE_ROOT) + .perPixelTolerance(PER_PIXEL_TOLERANCE) + .mismatchedPixelBudget(PIXEL_DIFF_BUDGET) + .assertMatchesBaseline("terracotta_rail", pdfBytes); + } +} diff --git a/qa/src/test/java/com/demcha/compose/document/templates/cv/presets/VioletGridFixtures.java b/qa/src/test/java/com/demcha/compose/document/templates/cv/presets/VioletGridFixtures.java new file mode 100644 index 000000000..82ef20428 --- /dev/null +++ b/qa/src/test/java/com/demcha/compose/document/templates/cv/presets/VioletGridFixtures.java @@ -0,0 +1,276 @@ +package com.demcha.compose.document.templates.cv.presets; + +import com.demcha.compose.document.templates.core.identity.Contact; +import com.demcha.compose.document.templates.core.identity.Link; +import com.demcha.compose.document.templates.cv.data.CvDocument; +import com.demcha.compose.document.templates.cv.data.CvEntry; +import com.demcha.compose.document.templates.cv.data.CvIdentity; +import com.demcha.compose.document.templates.cv.data.CvName; +import com.demcha.compose.document.templates.cv.data.CvSkill; +import com.demcha.compose.document.templates.cv.data.EntriesSection; +import com.demcha.compose.document.templates.cv.data.ParagraphSection; +import com.demcha.compose.document.templates.cv.data.SkillGroup; +import com.demcha.compose.document.templates.cv.data.SkillsSection; +import com.demcha.compose.document.templates.cv.data.Slot; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +/** + * Shared fixture data for the {@link VioletGrid} gates — the SAME document + * feeds the pixel parity test and the layout snapshot test, so a geometry + * shift the pixel budget absorbs still trips the exact snapshot, and vice + * versa. + */ +final class VioletGridFixtures { + + /** The break a body stacks its lines on. */ + private static final String NEWLINE = String.valueOf((char) 10); + + private VioletGridFixtures() { + } + + /** + * The canonical one-page CV — five contact lines including two links, + * three opening lines, six marked skills, eleven tools, three roles, two + * projects, a degree, three rated languages and the closing quotation. + */ + static CvDocument canonicalCv() { + List placements = new ArrayList<>(); + placements.add(new CvDocument.Placement(Slot.MAIN, summary())); + placements.add(new CvDocument.Placement(Slot.MAIN, skills())); + placements.add(new CvDocument.Placement(Slot.MAIN, tools())); + placements.add(new CvDocument.Placement(Slot.MAIN, experience())); + placements.add(new CvDocument.Placement(Slot.MAIN, projects())); + placements.add(new CvDocument.Placement(Slot.FOOTER, education())); + placements.add(new CvDocument.Placement(Slot.FOOTER, languages())); + placements.add(new CvDocument.Placement(Slot.FOOTER, quote())); + return new CvDocument(identity(), placements); + } + + /** + * The canonical CV with six earlier roles appended, which is enough to run the sheet + * past one page and to carry the role list itself across the break. + * + *

Only the experience list grows. Every other section, the identity and every layout + * constant are the canonical ones, so what this fixture exercises is the preset's stated + * overflow behaviour — a CV with more content than the design holds runs onto a second + * page rather than losing anything — and not some other design.

+ */ + static CvDocument overflowCv() { + List placements = new ArrayList<>(); + placements.add(new CvDocument.Placement(Slot.MAIN, summary())); + placements.add(new CvDocument.Placement(Slot.MAIN, skills())); + placements.add(new CvDocument.Placement(Slot.MAIN, tools())); + placements.add(new CvDocument.Placement(Slot.MAIN, experienceWithEarlierRoles())); + placements.add(new CvDocument.Placement(Slot.MAIN, projects())); + placements.add(new CvDocument.Placement(Slot.FOOTER, education())); + placements.add(new CvDocument.Placement(Slot.FOOTER, languages())); + placements.add(new CvDocument.Placement(Slot.FOOTER, quote())); + return new CvDocument(identity(), placements); + } + + /** + * The canonical three roles, then six earlier ones of the same shape. + * + *

Six rather than three on purpose: with three the roles still fit the first page and + * only the sections after them move, which exercises the bands but never breaks the rail. + * Six carries the list itself across the boundary, which is the case worth pinning.

+ */ + private static EntriesSection experienceWithEarlierRoles() { + List entries = new ArrayList<>(experience().entries()); + entries.add(earlierRole("Product Designer", "Harbour Labs", "2018 - 2020")); + entries.add(earlierRole("Interaction Designer", "Ridgeway Studio", "2016 - 2018")); + entries.add(earlierRole("Junior Designer", "Fieldnote Agency", "2014 - 2016")); + entries.add(earlierRole("Design Intern", "Fieldnote Agency", "2013 - 2014")); + entries.add(earlierRole("Visual Designer", "Copperline Co", "2012 - 2013")); + entries.add(earlierRole("Design Assistant", "Copperline Co", "2011 - 2012")); + return new EntriesSection("EXPERIENCE", entries); + } + + private static CvEntry earlierRole(String title, String employer, String dates) { + return CvEntry.builder(title) + .subtitle(employer) + .place("San Francisco, CA") + .date(dates) + .body(String.join(NEWLINE, + "Designed and shipped product surfaces across web and mobile, " + + "working from research through to handover.", + "Ran usability sessions and fed the findings back into the next " + + "iteration of the interface.", + "Maintained the shared component library and its documentation.")) + .build(); + } + + static CvIdentity identity() { + return new CvIdentity( + CvName.of("SOFIA", "MARTINEZ"), + "UX / UI DESIGNER", + new Contact("+1 (415) 555-7842", + "sofia.martinez.design@gmail.com", + "San Francisco, CA, USA"), + List.of( + new Link("sofiamartinez.design", "https://sofiamartinez.design"), + new Link("LinkedIn", "https://www.linkedin.com/in/sofia-martinez-ux")), + Optional.empty()); + } + + /** The opening lines, set as written rather than wrapped. */ + static ParagraphSection summary() { + return new ParagraphSection("SUMMARY", String.join(NEWLINE, + "User-centered designer with 5+ years of experience " + + "crafting intuitive digital experiences for web and " + + "mobile products.", + "I combine research, design thinking, and visual design " + + "to solve complex problems and deliver accessible, " + + "elegant solutions.", + "Passionate about collaborating with cross-functional " + + "teams to create products that delight users and drive " + + "business impact.")); + } + + /** The six-up grid: a mark, a label and a description each. */ + static EntriesSection skills() { + return new EntriesSection("DESIGN SKILLS", List.of( + CvEntry.builder("UX RESEARCH") + .icon("ux-research") + .body("User interviews, surveys, personas, journey maps, " + + "competitive analysis") + .build(), + CvEntry.builder("INFORMATION ARCHITECTURE") + .icon("information-architecture") + .body("Sitemaps, user flows, card sorting, content strategy") + .build(), + CvEntry.builder("WIREFRAMING") + .icon("wireframing") + .body("Low to high-fidelity wireframes, layout design, " + + "interaction flows") + .build(), + CvEntry.builder("PROTOTYPING") + .icon("prototyping") + .body("Interactive prototypes, microinteractions, transitions") + .build(), + CvEntry.builder("USABILITY TESTING") + .icon("usability-testing") + .body("Test planning, usability tests, heuristic evaluation, " + + "insights & iteration") + .build(), + CvEntry.builder("DESIGN SYSTEMS") + .icon("design-systems") + .body("Component libraries, design tokens, pattern " + + "documentation") + .build())); + } + + /** The tools strip — names without levels. */ + static SkillsSection tools() { + List entries = new ArrayList<>(); + for (String name : new String[] { + "Figma", "FigJam", "Adobe XD", "Sketch", "Miro", "Notion", "Jira", "Slack", "Maze", "Hotjar", "Airtable"}) { + entries.add(CvSkill.of(name)); + } + return new SkillsSection("TOOLS", + List.of(new SkillGroup("TOOLS", entries))); + } + + /** The roles held, each a headline over its bullets. */ + static EntriesSection experience() { + return new EntriesSection("EXPERIENCE", List.of( + CvEntry.builder("Senior UX/UI Designer") + .subtitle("NovaFin (FinTech Startup)") + .place("San Francisco, CA") + .date("2022 - Present") + .body(String.join(NEWLINE, + "Led end-to-end design for a personal finance platform, " + + "from discovery to launch, improving task success rate by " + + "32%.", + "Conducted user research, built personas and journey " + + "maps, and translated insights into clear product " + + "opportunities.", + "Designed wireframes, prototypes, and high-fidelity " + + "interfaces for web and mobile using Figma.", + "Established and maintained a scalable design system, " + + "increasing design consistency and development speed.", + "Collaborated with product managers, engineers, and data " + + "analysts in an agile environment.")) + .build(), + CvEntry.builder("UX Designer") + .subtitle("BrightHealth (HealthTech)") + .place("San Francisco, CA") + .date("2020 - 2022") + .body(String.join(NEWLINE, + "Redesigned patient portal experience, reducing support " + + "tickets by 28% and improving user satisfaction.", + "Planned and facilitated usability tests and synthesized " + + "findings to drive iterative improvements.", + "Partnered with engineers to ensure feasible solutions " + + "and pixel-perfect implementation.", + "Created reusable components and style guides to support " + + "a cohesive cross-platform experience.")) + .build(), + CvEntry.builder("Junior UX/UI Designer") + .subtitle("Lumen Digital Agency") + .place("Austin, TX") + .date("2018 - 2020") + .body(String.join(NEWLINE, + "Supported discovery workshops, user flows, and " + + "wireframes for clients across e-commerce and SaaS.", + "Designed responsive websites and landing pages following " + + "best practices in accessibility and usability.", + "Collaborated with copywriters and developers to deliver " + + "high-quality, user-centered solutions.")) + .build())); + } + + /** The projects, each behind its tinted tile. */ + static EntriesSection projects() { + return new EntriesSection("SELECTED PROJECTS", List.of( + CvEntry.builder("NovaFin Mobile App") + .subtitle("Personal Finance Management") + .date("2023") + .icon("project-wallet") + .body("Led UX/UI design for budgeting, goal tracking, and " + + "insights features. Conducted user interviews and " + + "usability tests that informed a simplified navigation " + + "and data visualization. Resulted in 32% improvement in " + + "task success rate and 22% increase in weekly active " + + "users.") + .build(), + CvEntry.builder("BrightHealth Patient Portal") + .subtitle("Healthcare Platform Redesign") + .date("2021") + .icon("project-health") + .body("Redesigned key patient flows including appointment " + + "scheduling, test results, and messaging. Created a new " + + "design system and component library used across web and " + + "mobile. Reduced support tickets by 28%.") + .build())); + } + + /** The degree, with the place and the years on its third line. */ + static EntriesSection education() { + return new EntriesSection("EDUCATION", List.of( + CvEntry.builder("Bachelor of Fine Arts in Graphic Design") + .subtitle("California College of the Arts") + .place("San Francisco, CA") + .date("2014 - 2018") + .icon("graduation") + .build())); + } + + /** The languages, each carrying both the rating and the word for it. */ + static SkillsSection languages() { + return new SkillsSection("LANGUAGES", List.of(new SkillGroup("LANGUAGES", List.of( + CvSkill.of("English", 1.0, "Native"), + CvSkill.of("Spanish", 1.0, "Native"), + CvSkill.of("Portuguese", 0.6, "Conversational"))))); + } + + /** The closing line. Its title names the berth and is not drawn. */ + static ParagraphSection quote() { + return new ParagraphSection("QUOTE", + "I design with empathy, iterate with purpose, and " + + "collaborate to build products people love."); + } +} diff --git a/qa/src/test/java/com/demcha/compose/document/templates/cv/presets/VioletGridLayoutSnapshotTest.java b/qa/src/test/java/com/demcha/compose/document/templates/cv/presets/VioletGridLayoutSnapshotTest.java new file mode 100644 index 000000000..c47638b85 --- /dev/null +++ b/qa/src/test/java/com/demcha/compose/document/templates/cv/presets/VioletGridLayoutSnapshotTest.java @@ -0,0 +1,32 @@ +package com.demcha.compose.document.templates.cv.presets; + +import com.demcha.compose.GraphCompose; +import com.demcha.compose.document.api.DocumentSession; +import com.demcha.compose.document.templates.TemplateTestSupport; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Exact layout snapshot gate for {@link VioletGrid} — freezes the resolved + * geometry of the canonical one-page CV, which the pixel budget cannot see: a + * small column or spacing shift stays under the visual-diff budget but changes + * the snapshot. + * + *

The preset owns its page geometry, so the session starts unconfigured.

+ * + *

Refresh with {@code -Dgraphcompose.updateSnapshots=true} after a + * deliberate layout change, and commit the JSON with the change.

+ */ +class VioletGridLayoutSnapshotTest { + + @Test + void canonicalCvMatchesLayoutSnapshot() throws Exception { + try (DocumentSession session = GraphCompose.document().create()) { + VioletGrid.create().compose(session, VioletGridFixtures.canonicalCv()); + assertThat(session.layoutSnapshot().totalPages()).isEqualTo(1); + TemplateTestSupport.assertCanonicalSnapshot( + session, "violet_grid_layout", "cv-v2"); + } + } +} diff --git a/qa/src/test/java/com/demcha/compose/document/templates/cv/presets/VioletGridOverflowPaginationTest.java b/qa/src/test/java/com/demcha/compose/document/templates/cv/presets/VioletGridOverflowPaginationTest.java new file mode 100644 index 000000000..45a3d004d --- /dev/null +++ b/qa/src/test/java/com/demcha/compose/document/templates/cv/presets/VioletGridOverflowPaginationTest.java @@ -0,0 +1,192 @@ +package com.demcha.compose.document.templates.cv.presets; + +import com.demcha.compose.GraphCompose; +import com.demcha.compose.document.api.DocumentSession; +import com.demcha.compose.document.snapshot.LayoutNodeSnapshot; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; +import java.util.TreeSet; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.within; + +/** + * What {@link VioletGrid} promises past its one page. + * + *

The preset is drawn for a single sheet, and says so: a CV with more content than the + * design holds "runs onto a second page rather than losing anything — each experience entry + * is held together, so a role is never cut in half". The canonical fixture fits one page, so + * nothing exercised that promise and nothing would notice it breaking. This does, on + * {@link VioletGridFixtures#overflowCv()}, which is the canonical document with six earlier + * roles appended and no other change.

+ * + *

A role is identified here by the parts that carry its index — its dates, its disc, its + * title line and its bullets — and never by the box that happens to hold them. The promise + * is about the role, and a test that named the container would be pinning one way of + * drawing it: it would have to be rewritten to say the same thing the moment the container + * changed, which is exactly when it is supposed to be watching.

+ * + *

Three things are pinned. The sheet grows a page rather than dropping content, and every + * part of a role lands on one page. Which role sits on which page is recorded too, because a + * change there is a change to the design's pagination and should have to be argued for rather + * than discovered later. And the three columns hold their x on both pages: the dates + * hang in the left margin and the disc rides the rail by construction on a sheet that never + * breaks, but whether they still do on a page the design was not drawn for is a separate + * question.

+ */ +class VioletGridOverflowPaginationTest { + + /** + * The parts a role is made of, each named {@code _} or deeper. + * + *

None of these is a prefix of another: {@code Highlights_0} and {@code + * HighlightDot_0_1} both survive the {@code Highlight_} test, because what follows the + * prefix has to be digits and then either nothing or an underscore.

+ */ + private static final List ROLE_PARTS = List.of( + "Period_", "Marker_", "TitleTable_", "Role_", "Location_", + "Highlights_", "HighlightDot_", "Highlight_"); + + @Test + void theSheetGrowsAPageAndNoRoleIsCutInHalf() throws Exception { + try (DocumentSession session = GraphCompose.document().create()) { + VioletGrid.create().compose(session, VioletGridFixtures.overflowCv()); + var snapshot = session.layoutSnapshot(); + + assertThat(snapshot.totalPages()) + .as("the overflow document runs onto a second page rather than losing content") + .isEqualTo(2); + + // Every page any part of a role was placed on. A role held together contributes + // exactly one; a role cut in half contributes two, and so does one whose bullets + // were left behind on the page above. + Map> pagesPerRole = new TreeMap<>(); + for (LayoutNodeSnapshot node : snapshot.nodes()) { + int role = roleOf(node.entityName()); + if (role < 0) { + continue; + } + TreeSet pages = + pagesPerRole.computeIfAbsent(role, key -> new TreeSet<>()); + pages.add(node.startPage()); + pages.add(node.endPage()); + } + + assertThat(pagesPerRole.keySet()) + .as("nine roles, three canonical and six earlier") + .containsExactly(0, 1, 2, 3, 4, 5, 6, 7, 8); + assertThat(pagesPerRole) + .as("every role is held together: a role is never cut in half") + .allSatisfy((role, pages) -> assertThat(pages) + .as("role %d spans pages %s", role, pages).hasSize(1)); + + Map ownership = new TreeMap<>(); + pagesPerRole.forEach((role, pages) -> ownership.put(role, pages.first())); + assertThat(ownership) + .as("which role sits on which page is part of the design's pagination: the" + + " break falls between the sixth and the seventh") + .containsExactlyInAnyOrderEntriesOf(Map.of( + 0, 0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, + 6, 1, 7, 1, 8, 1)); + } + } + + @Test + void theSectionsAfterTheRolesFollowThemOntoTheSecondPage() throws Exception { + try (DocumentSession session = GraphCompose.document().create()) { + VioletGrid.create().compose(session, VioletGridFixtures.overflowCv()); + Map pages = new TreeMap<>(); + for (LayoutNodeSnapshot node : session.layoutSnapshot().nodes()) { + String name = node.entityName(); + if (name != null && (name.equals("ProjectsHeading") || name.equals("Project_0") + || name.equals("CredentialHeadings") || name.equals("QuoteBand"))) { + pages.put(name, node.startPage()); + } + } + assertThat(pages) + .as("downstream pagination: everything after the roles is on the second page") + .containsExactlyInAnyOrderEntriesOf(Map.of( + "ProjectsHeading", 1, + "Project_0", 1, + "CredentialHeadings", 1, + "QuoteBand", 1)); + } + } + + @Test + void theThreeColumnsKeepTheirXOnBothPages() throws Exception { + try (DocumentSession session = GraphCompose.document().create()) { + VioletGrid.create().compose(session, VioletGridFixtures.overflowCv()); + + Map> columns = new TreeMap<>(); + for (LayoutNodeSnapshot node : session.layoutSnapshot().nodes()) { + String name = node.entityName(); + if (name == null) { + continue; + } + if (name.startsWith("Period_")) { + column(columns, "date").add(node.placementX()); + } else if (name.startsWith("Marker_")) { + // The disc's centre, which is the line the rail is drawn on. + column(columns, "marker") + .add(node.placementX() + node.placementWidth() / 2.0); + } else if (name.startsWith("TitleTable_")) { + column(columns, "content").add(node.placementX()); + column(columns, "contentWidth").add(node.placementWidth()); + } + } + + // All nine roles, not one sample per column: a single sample would pass on a + // document whose second page had drifted, and a second page is the whole point + // of this fixture. + assertColumn(columns, "date", 29.905, + "the dates hang in the left margin, flush with the page margin"); + assertColumn(columns, "marker", 105.796, "the disc's centre is the rail"); + assertColumn(columns, "content", 125.826, + "the copy starts one entry indent right of the rail"); + assertColumn(columns, "contentWidth", 439.535, + "and runs the full entry width on both pages"); + } + } + + /** + * Which role a node belongs to, or -1 for a node that is not part of one. + * + * @param name the node's entity name, possibly {@code null} + * @return the role's index, or -1 + */ + private static int roleOf(String name) { + if (name == null) { + return -1; + } + for (String part : ROLE_PARTS) { + if (!name.startsWith(part)) { + continue; + } + String rest = name.substring(part.length()); + int digits = 0; + while (digits < rest.length() && Character.isDigit(rest.charAt(digits))) { + digits++; + } + if (digits == 0 || (digits < rest.length() && rest.charAt(digits) != '_')) { + continue; + } + return Integer.parseInt(rest.substring(0, digits)); + } + return -1; + } + + private static List column(Map> columns, String key) { + return columns.computeIfAbsent(key, name -> new ArrayList<>()); + } + + private static void assertColumn(Map> columns, String key, + double expected, String because) { + assertThat(columns.get(key)).as(because).hasSize(9) + .allSatisfy(value -> assertThat(value).isCloseTo(expected, within(0.01))); + } +} diff --git a/qa/src/test/java/com/demcha/compose/document/templates/cv/presets/VioletGridSmokeTest.java b/qa/src/test/java/com/demcha/compose/document/templates/cv/presets/VioletGridSmokeTest.java new file mode 100644 index 000000000..f6a36a2cd --- /dev/null +++ b/qa/src/test/java/com/demcha/compose/document/templates/cv/presets/VioletGridSmokeTest.java @@ -0,0 +1,266 @@ +package com.demcha.compose.document.templates.cv.presets; + +import com.demcha.compose.GraphCompose; +import com.demcha.compose.document.api.DocumentSession; +import com.demcha.compose.document.templates.api.DocumentTemplate; +import com.demcha.compose.document.templates.core.identity.Contact; +import com.demcha.compose.document.templates.cv.data.CvDocument; +import com.demcha.compose.document.templates.cv.data.CvEntry; +import com.demcha.compose.document.templates.cv.data.CvIdentity; +import com.demcha.compose.document.templates.cv.data.CvName; +import com.demcha.compose.document.templates.cv.data.EntriesSection; +import com.demcha.compose.document.templates.cv.data.Slot; +import org.apache.pdfbox.Loader; +import org.apache.pdfbox.pdmodel.PDDocument; +import org.apache.pdfbox.pdmodel.PDPage; +import org.apache.pdfbox.pdmodel.interactive.action.PDActionURI; +import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotation; +import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationLink; +import org.apache.pdfbox.text.PDFTextStripper; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.Set; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +/** + * Smoke test for {@link VioletGrid} — proves the preset renders a + * {@link CvDocument} end-to-end with its packaged marks, sets the two-tone + * name from the structured one, draws only the quotation's body, reports an + * unknown mark as a data error, and carries the channels and the titles as + * link annotations, which move no pixel and no layout node so neither gate + * would notice them going missing. + */ +class VioletGridSmokeTest { + + private static byte[] render(CvDocument doc) throws Exception { + // The preset owns its page geometry, so the session starts unconfigured. + try (DocumentSession session = GraphCompose.document().create()) { + VioletGrid.create().compose(session, doc); + assertThat(session.roots()).isNotEmpty(); + byte[] pdfBytes = session.toPdfBytes(); + assertThat(pdfBytes).isNotEmpty(); + return pdfBytes; + } + } + + private static String textOf(byte[] pdfBytes) throws Exception { + try (PDDocument document = Loader.loadPDF(pdfBytes)) { + return new PDFTextStripper().getText(document); + } + } + + /** + * The text with its spaces removed. The name and every heading are + * letter-spaced with real space runs, so what is being asserted is the + * wording, not the tracking. + */ + private static String compact(String text) { + return text.replaceAll("(?U)\\s", ""); + } + + private static List linkTargets(byte[] pdfBytes) throws Exception { + List targets = new ArrayList<>(); + try (PDDocument document = Loader.loadPDF(pdfBytes)) { + for (PDPage page : document.getPages()) { + for (PDAnnotation annotation : page.getAnnotations()) { + if (annotation instanceof PDAnnotationLink link + && link.getAction() instanceof PDActionURI uri) { + targets.add(uri.getURI()); + } + } + } + } + return targets; + } + + /** The canonical document with one section replaced. */ + private static CvDocument withSection(Slot slot, EntriesSection replacement, + String replacedTitle) { + List placements = new ArrayList<>(); + for (CvDocument.Placement placement : VioletGridFixtures.canonicalCv().placements()) { + if (placement.section().title().equals(replacedTitle)) { + placements.add(new CvDocument.Placement(slot, replacement)); + } else { + placements.add(placement); + } + } + return new CvDocument(VioletGridFixtures.identity(), placements); + } + + @Test + void exposesStableIdentity() { + DocumentTemplate template = VioletGrid.create(); + assertThat(template.id()).isEqualTo(VioletGrid.ID); + assertThat(template.displayName()).isEqualTo(VioletGrid.DISPLAY_NAME); + } + + @Test + void rendersCanonicalCvWithPackagedMarks() throws Exception { + render(VioletGridFixtures.canonicalCv()); + } + + @Test + void canonicalRenderCarriesEveryBandsText() throws Exception { + String text = textOf(render(VioletGridFixtures.canonicalCv())); + assertThat(text) + .contains("sofia.martinez.design@gmail.com") + .contains("User-centered designer") + .contains("Figma") + .contains("NovaFin (FinTech Startup)") + .contains("NovaFin Mobile App") + .contains("Bachelor of Fine Arts in Graphic Design") + .contains("Conversational") + .contains("I design with empathy"); + assertThat(compact(text)) + .contains("SOFIAMARTINEZ") + .contains("UX/UIDESIGNER") + .contains("DESIGNSKILLS") + .doesNotContain("QUOTE"); + } + + @Test + void theNameIsSetFromTheStructuredNameInTwoTones() throws Exception { + // Both names are runs of one paragraph, so the gap between them is a + // real word space; the two tones are what the design asks for. + String text = textOf(render(VioletGridFixtures.canonicalCv())); + assertThat(text.lines().map(String::strip).toList()) + .anyMatch(line -> line.contains("SOFIA") && line.contains("MARTINEZ")); + } + + @Test + void contactChannelsAndLinksAreClickable() throws Exception { + List targets = linkTargets(render(VioletGridFixtures.canonicalCv())); + assertThat(targets) + .contains("mailto:sofia.martinez.design@gmail.com") + .contains("tel:+14155557842") + .contains("https://sofiamartinez.design") + .contains("https://www.linkedin.com/in/sofia-martinez-ux"); + } + + @Test + void aLinkShowsItsLabelAndHidesItsAddress() throws Exception { + // The column draws the label, so its width is the same whatever the + // profile behind it is called; the address is reachable, not written. + byte[] pdfBytes = render(VioletGridFixtures.canonicalCv()); + assertThat(textOf(pdfBytes)) + .contains("LinkedIn") + .doesNotContain("linkedin.com/in/sofia-martinez-ux"); + assertThat(linkTargets(pdfBytes)) + .contains("https://www.linkedin.com/in/sofia-martinez-ux"); + } + + @Test + void aRoleAProjectAndADegreeBecomeLinksWhenTheirEntryCarriesOne() throws Exception { + List placements = new ArrayList<>(); + for (CvDocument.Placement placement : VioletGridFixtures.canonicalCv().placements()) { + placements.add(switch (placement.section().title()) { + case "EXPERIENCE" -> new CvDocument.Placement(Slot.MAIN, + new EntriesSection("EXPERIENCE", List.of( + CvEntry.builder("Senior UX/UI Designer") + .subtitle("NovaFin") + .place("San Francisco, CA") + .date("2022") + .link("https://example.test/novafin") + .body("Led end-to-end design.") + .build()))); + case "SELECTED PROJECTS" -> new CvDocument.Placement(Slot.MAIN, + new EntriesSection("SELECTED PROJECTS", List.of( + CvEntry.builder("NovaFin Mobile App") + .subtitle("Personal Finance") + .date("2023") + .icon("project-wallet") + .link("https://example.test/app") + .body("Led UX/UI design.") + .build()))); + case "EDUCATION" -> new CvDocument.Placement(Slot.FOOTER, + new EntriesSection("EDUCATION", List.of( + CvEntry.builder("Bachelor of Fine Arts") + .subtitle("California College of the Arts") + .place("San Francisco, CA") + .date("2014 - 2018") + .icon("graduation") + .link("https://example.test/cca") + .build()))); + default -> placement; + }); + } + + List targets = linkTargets( + render(new CvDocument(VioletGridFixtures.identity(), placements))); + assertThat(targets) + .contains("https://example.test/novafin") + .contains("https://example.test/app") + .contains("https://example.test/cca"); + } + + @Test + void anUnknownMarkIsReportedAsADataError() { + EntriesSection wrong = new EntriesSection("DESIGN SKILLS", List.of( + CvEntry.builder("Telepathy").icon("telescope").body("Reading minds.").build())); + + assertThatThrownBy(() -> render(withSection(Slot.MAIN, wrong, "DESIGN SKILLS"))) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("telescope") + .hasMessageContaining("wireframing"); + } + + @Test + void aSkillWithoutAMarkIsDrawnWithoutOne() throws Exception { + EntriesSection unmarked = new EntriesSection("DESIGN SKILLS", List.of( + CvEntry.builder("UX RESEARCH").body("User interviews and surveys.").build())); + + String text = textOf(render(withSection(Slot.MAIN, unmarked, "DESIGN SKILLS"))); + // The description wraps inside its narrow column, so a fragment + // of one line is what is asserted. + assertThat(text).contains("User interviews and"); + } + + @Test + void aDocumentWithNothingButAnIdentityStillRenders() throws Exception { + // Every berth empty: the name, the discipline line and its rule, and + // nothing else on the sheet. + render(new CvDocument(VioletGridFixtures.identity(), List.of())); + } + + @Test + void anIdentityWithoutLinksDrawsTheThreeChannels() throws Exception { + CvIdentity noLinks = new CvIdentity(CvName.of("Ada", "Lovelace"), "Analyst", + new Contact("+44 20 7946 0000", "ada@example.test", "London, UK"), + List.of(), Optional.empty()); + + // A run that breaks at a space carries one annotation per piece, so it + // is the set of targets that is asserted, not the count. + byte[] pdfBytes = render(new CvDocument(noLinks, List.of())); + assertThat(Set.copyOf(linkTargets(pdfBytes))) + .containsExactlyInAnyOrder("mailto:ada@example.test", "tel:+442079460000"); + } + + @Test + void aLongerCvRunsOntoASecondPageRatherThanLosingARole() throws Exception { + // The page is a stack of bands, so more content than the design holds + // runs over; each entry is held together, so a role is never cut in two. + List many = new ArrayList<>(); + for (int index = 0; index < 8; index++) { + many.add(CvEntry.builder("Senior UX/UI Designer " + index) + .subtitle("NovaFin") + .place("San Francisco, CA") + .date("2022") + .body("Led end-to-end design for a personal finance platform.") + .build()); + } + + try (DocumentSession session = GraphCompose.document().create()) { + VioletGrid.create().compose(session, withSection(Slot.MAIN, + new EntriesSection("EXPERIENCE", many), "EXPERIENCE")); + assertThat(session.layoutSnapshot().totalPages()).isGreaterThan(1); + } + String text = textOf(render(withSection(Slot.MAIN, + new EntriesSection("EXPERIENCE", many), "EXPERIENCE"))); + assertThat(text).contains("Senior UX/UI Designer 7"); + } +} diff --git a/qa/src/test/java/com/demcha/compose/document/templates/cv/presets/VioletGridVisualParityTest.java b/qa/src/test/java/com/demcha/compose/document/templates/cv/presets/VioletGridVisualParityTest.java new file mode 100644 index 000000000..9645814b1 --- /dev/null +++ b/qa/src/test/java/com/demcha/compose/document/templates/cv/presets/VioletGridVisualParityTest.java @@ -0,0 +1,41 @@ +package com.demcha.compose.document.templates.cv.presets; + +import com.demcha.compose.GraphCompose; +import com.demcha.compose.document.api.DocumentSession; +import com.demcha.compose.testing.visual.PdfVisualRegression; +import org.junit.jupiter.api.Test; + +import java.nio.file.Path; + +/** + * Pixel-diff visual parity gate for {@link VioletGrid} — the canonical + * one-page CV against a checked-in baseline. + * + *

Re-blessing baselines — after a deliberate visual + * change, re-run with {@code -Dgraphcompose.visual.approve=true} and commit + * the updated PNG with the change. Baselines live under + * {@code src/test/resources/visual-baselines/cv-v2-layered/}.

+ */ +class VioletGridVisualParityTest { + + private static final Path BASELINE_ROOT = Path.of( + "src", "test", "resources", "visual-baselines", "cv-v2-layered"); + + private static final long PIXEL_DIFF_BUDGET = 50_000L; + private static final int PER_PIXEL_TOLERANCE = 8; + + @Test + void rendersWithinPixelDiffBudget() throws Exception { + byte[] pdfBytes; + try (DocumentSession document = GraphCompose.document().create()) { + VioletGrid.create().compose(document, VioletGridFixtures.canonicalCv()); + pdfBytes = document.toPdfBytes(); + } + + PdfVisualRegression.standard() + .baselineRoot(BASELINE_ROOT) + .perPixelTolerance(PER_PIXEL_TOLERANCE) + .mismatchedPixelBudget(PIXEL_DIFF_BUDGET) + .assertMatchesBaseline("violet_grid", pdfBytes); + } +} diff --git a/qa/src/test/java/com/demcha/compose/document/templates/data/invoice/StructuredInvoiceCompatibilityTest.java b/qa/src/test/java/com/demcha/compose/document/templates/data/invoice/StructuredInvoiceCompatibilityTest.java new file mode 100644 index 000000000..0df48ae90 --- /dev/null +++ b/qa/src/test/java/com/demcha/compose/document/templates/data/invoice/StructuredInvoiceCompatibilityTest.java @@ -0,0 +1,212 @@ +package com.demcha.compose.document.templates.data.invoice; + +import org.junit.jupiter.api.Test; + +import java.math.BigDecimal; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Pins the compatibility promise made when the structured invoice model grew + * the fields a second sheet needed: the constructors that predate them are + * still there and still mean what they meant, so a caller written against + * any of them keeps compiling and linking. + */ +class StructuredInvoiceCompatibilityTest { + + @Test + void theBrandConstructorThatPredatesTheMonogramLeavesItBlank() { + InvoiceBrand brand = new InvoiceBrand(null, "Luma", "Studio", "Design."); + assertThat(brand.monogramTop()).isEmpty(); + assertThat(brand.monogramBottom()).isEmpty(); + assertThat(brand.hasMonogram()).isFalse(); + assertThat(brand.name()).isEqualTo("Luma"); + } + + @Test + void aBrandWithEitherMonogramLineHasOne() { + assertThat(new InvoiceBrand(null, "Luma", "", "", "L", "").hasMonogram()).isTrue(); + assertThat(new InvoiceBrand(null, "Luma", "", "", "", "&Co.").hasMonogram()).isTrue(); + } + + @Test + void theContactConstructorThatPredatesTheSecondRegistrationLeavesItBlank() { + InvoiceContactBlock supplier = new InvoiceContactBlock( + "Luma Ltd", List.of("14 Gower Street"), "+44", "a@b.c", "b.c", + "Company No.", "12578934"); + assertThat(supplier.taxRegistrationLabel()).isEmpty(); + assertThat(supplier.taxRegistrationNumber()).isEmpty(); + assertThat(supplier.registrationNumber()).isEqualTo("12578934"); + } + + @Test + void theContactConstructorThatPredatesTheLegalFootnoteLeavesItBlank() { + InvoiceContactBlock supplier = new InvoiceContactBlock( + "Luma Ltd", List.of("14 Gower Street"), "+44", "a@b.c", "b.c", + "Company No.", "12578934", "VAT No.", "GB123"); + assertThat(supplier.legalFootnote()).isEmpty(); + assertThat(supplier.taxRegistrationNumber()).isEqualTo("GB123"); + } + + @Test + void aContactCarriesTheLegalFootnoteItIsGiven() { + InvoiceContactBlock supplier = new InvoiceContactBlock( + "Luma Ltd", List.of("14 Gower Street"), "+44", "a@b.c", "b.c", + "Company No.", "12578934", "VAT No.", "GB123", + "Luma Ltd is a subsidiary of Luma Group plc."); + assertThat(supplier.legalFootnote()) + .isEqualTo("Luma Ltd is a subsidiary of Luma Group plc."); + } + + @Test + void aNullLegalFootnoteNormalizesToBlankLikeTheFieldsBesideIt() { + InvoiceContactBlock supplier = new InvoiceContactBlock( + "Luma Ltd", List.of("14 Gower Street"), "+44", "a@b.c", "b.c", + "Company No.", "12578934", "VAT No.", "GB123", null); + assertThat(supplier.legalFootnote()).isEmpty(); + } + + @Test + void thePaymentConstructorThatPredatesTheAccountHolderLeavesItBlank() { + InvoicePaymentBlock payment = new InvoicePaymentBlock( + "PAYMENT", List.of(new InvoicePaymentBlock.Field("BANK", "Starling")), + "Pay by transfer.", "Due in 30 days", "30 days"); + assertThat(payment.accountHolder()).isEmpty(); + assertThat(payment.signOff()).isEmpty(); + assertThat(payment.dueNoticeEmphasis()).isEqualTo("30 days"); + } + + @Test + void theColumnsConstructorThatPredatesTheVatColumnLeavesItBlank() { + InvoiceServiceLines.Columns columns = new InvoiceServiceLines.Columns( + "#", "DESCRIPTION", "PERIOD", "QTY", "UNIT", "AMOUNT"); + assertThat(columns.vat()).isEmpty(); + assertThat(columns.amount()).isEqualTo("AMOUNT"); + } + + @Test + void theLineConstructorThatPredatesTheVatRateLeavesItAndTheMarkBlank() { + InvoiceServiceLines.Line line = new InvoiceServiceLines.Line( + 1, "Workshop", "A day of it", "May", BigDecimal.ONE, "day", + new BigDecimal("1200"), new BigDecimal("1200")); + assertThat(line.vatRate()).isEmpty(); + assertThat(line.icon()).isEmpty(); + assertThat(line.amount()).isEqualByComparingTo("1200"); + } + + @Test + void theLineConstructorThatPredatesTheMarkLeavesItBlank() { + // The tax rate arrived before the mark did, so a caller written against + // that version keeps compiling and keeps drawing no mark. + InvoiceServiceLines.Line line = new InvoiceServiceLines.Line( + 1, "Workshop", "A day of it", "May", BigDecimal.ONE, "day", + new BigDecimal("1200"), new BigDecimal("1200"), "20%"); + assertThat(line.vatRate()).isEqualTo("20%"); + assertThat(line.icon()).isEmpty(); + } + + @Test + void aLineCarriesTheMarkItIsGiven() { + InvoiceServiceLines.Line line = new InvoiceServiceLines.Line( + 1, "Workshop", "A day of it", "May", BigDecimal.ONE, "day", + new BigDecimal("1200"), new BigDecimal("1200"), "20%", "card"); + assertThat(line.icon()).isEqualTo("card"); + } + + @Test + void aNullMarkNormalizesToBlankLikeTheFieldsBesideIt() { + InvoiceServiceLines.Line line = new InvoiceServiceLines.Line( + 1, "Workshop", "A day of it", "May", BigDecimal.ONE, "day", + new BigDecimal("1200"), new BigDecimal("1200"), "20%", null); + assertThat(line.icon()).isEmpty(); + } + + @Test + void theColumnsConstructorThatPredatesTheRegionColumnLeavesItBlank() { + InvoiceServiceLines.Columns columns = new InvoiceServiceLines.Columns( + "#", "DESCRIPTION", "PERIOD", "QTY", "UNIT PRICE", "AMOUNT", "VAT"); + assertThat(columns.region()).isEmpty(); + assertThat(columns.vat()).isEqualTo("VAT"); + } + + @Test + void theLineConstructorThatPredatesTheRegionLeavesItBlank() { + InvoiceServiceLines.Line line = new InvoiceServiceLines.Line( + 1, "Compute", "On-demand", "May 2026", BigDecimal.ONE, "Hrs", + BigDecimal.ONE, BigDecimal.ONE, "20%", "compute"); + assertThat(line.region()).isEmpty(); + assertThat(line.icon()).isEqualTo("compute"); + } + + @Test + void aLineCarriesTheRegionItIsGiven() { + InvoiceServiceLines.Line line = new InvoiceServiceLines.Line( + 1, "Compute", "On-demand", "May 2026", BigDecimal.ONE, "Hrs", + BigDecimal.ONE, BigDecimal.ONE, "20%", "compute", "eu-west1"); + assertThat(line.region()).isEqualTo("eu-west1"); + } + + @Test + void aNullRegionNormalizesToBlankLikeTheFieldsBesideIt() { + InvoiceServiceLines.Line line = new InvoiceServiceLines.Line( + 1, "Compute", "On-demand", "May 2026", BigDecimal.ONE, "Hrs", + BigDecimal.ONE, BigDecimal.ONE, "20%", "compute", null); + assertThat(line.region()).isEmpty(); + assertThat(new InvoiceServiceLines.Columns( + "#", "DESCRIPTION", "PERIOD", "QTY", "UNIT PRICE", "AMOUNT", "VAT", null) + .region()).isEmpty(); + } + + @Test + void theRecipientConstructorThatPredatesTheRegistrationLeavesItBlank() { + InvoiceRecipient recipient = new InvoiceRecipient("BILL TO", "Northwind Ltd.", "", + List.of("42 Bridgewater Street"), "Email", "ap@example.test"); + assertThat(recipient.registrationLabel()).isEmpty(); + assertThat(recipient.registrationNumber()).isEmpty(); + assertThat(recipient.hasRegistration()).isFalse(); + } + + @Test + void aRecipientCarriesTheRegistrationItIsGiven() { + InvoiceRecipient recipient = new InvoiceRecipient("BILL TO", "Northwind Ltd.", "", + List.of("42 Bridgewater Street"), "", "", "VAT Number", "GB 987 6543 21"); + assertThat(recipient.registrationLabel()).isEqualTo("VAT Number"); + assertThat(recipient.registrationNumber()).isEqualTo("GB 987 6543 21"); + assertThat(recipient.hasRegistration()).isTrue(); + } + + @Test + void aRecipientWithALabelAndNoNumberPrintsNothing() { + // The number is what makes the row worth drawing; a label on its own is + // a heading over an absence. + InvoiceRecipient recipient = new InvoiceRecipient("BILL TO", "Northwind Ltd.", "", + List.of(), "", "", "VAT Number", null); + assertThat(recipient.hasRegistration()).isFalse(); + } + + @Test + void theDataConstructorThatPredatesShipToLeavesItEmptyRatherThanNull() { + // The record normalizes an absent block to its empty form, so preset + // code reads it without a null check — the promise the whole model + // makes. + StructuredInvoiceData data = new StructuredInvoiceData( + null, null, null, + new InvoiceRecipient("BILL TO", "Northfield", "", List.of("21 Jubilee Way"), + "", ""), + null, null, null, null, null, "GBP"); + assertThat(data.shipTo()).isNotNull(); + assertThat(data.shipTo().name()).isEmpty(); + assertThat(data.billTo().name()).isEqualTo("Northfield"); + } + + @Test + void theBuilderCarriesShipToThrough() { + StructuredInvoiceData data = StructuredInvoiceData.builder() + .billTo(new InvoiceRecipient("BILL TO", "Northfield", "", List.of(), "", "")) + .shipTo(new InvoiceRecipient("SHIP TO", "The Foundry", "", List.of(), "", "")) + .build(); + assertThat(data.shipTo().name()).isEqualTo("The Foundry"); + assertThat(data.shipTo().heading()).isEqualTo("SHIP TO"); + } +} diff --git a/qa/src/test/java/com/demcha/compose/document/templates/data/proposal/ProposalHeaderSpineTest.java b/qa/src/test/java/com/demcha/compose/document/templates/data/proposal/ProposalHeaderSpineTest.java new file mode 100644 index 000000000..f0102e100 --- /dev/null +++ b/qa/src/test/java/com/demcha/compose/document/templates/data/proposal/ProposalHeaderSpineTest.java @@ -0,0 +1,206 @@ +package com.demcha.compose.document.templates.data.proposal; + +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * The header spine the one-page sales proposals share. + * + *

Each case is one of the seven designs the spine was measured against, so a + * field that stops carrying what a design states fails here rather than in that + * design's own render.

+ */ +class ProposalHeaderSpineTest { + + // -- the addressed block -------------------------------------------- + + @Test + void aRecipientCarriesTheCaptionTheNameAndTheAddressSeparately() { + ProposalRecipient recipient = new ProposalRecipient("PREPARED FOR", "Bright Future Ltd.", + List.of("45 King Street", "Manchester, M2 4WU", "United Kingdom")); + assertThat(recipient.label()).isEqualTo("PREPARED FOR"); + assertThat(recipient.name()).isEqualTo("Bright Future Ltd."); + assertThat(recipient.addressLines()).hasSize(3); + assertThat(recipient.isPresent()).isTrue(); + } + + @Test + void aRecipientThatNamesNothingIsNotPresent() { + assertThat(new ProposalRecipient(null, null, null).isPresent()).isFalse(); + assertThat(new ProposalRecipient("PREPARED FOR", "", List.of()).isPresent()).isFalse(); + } + + // -- the attention line --------------------------------------------- + + @Test + void anAttentionLineCarriesTheRoleAndBothChannels() { + ProposalAttention attention = new ProposalAttention("ATTN", "A. Mitchell", "Founder", + "a@brightfuture.example", "+44 7700 900123"); + assertThat(attention.role()).isEqualTo("Founder"); + assertThat(attention.email()).isEqualTo("a@brightfuture.example"); + assertThat(attention.phone()).isEqualTo("+44 7700 900123"); + assertThat(attention.isPresent()).isTrue(); + } + + @Test + void aDesignThatFoldsTheNameIntoItsCaptionStillReads() { + // One of the seven prints "Attn: A. Mitchell" as the caption and gives + // the name no line of its own, which is why the caption is the design's + // own string rather than a fixed word. + ProposalAttention attention = new ProposalAttention("Attn: A. Mitchell", "", "Founder", + "a@brightfuture.example", "+44 7700 900123"); + assertThat(attention.label()).contains("A. Mitchell"); + assertThat(attention.name()).isEmpty(); + assertThat(attention.isPresent()).isTrue(); + } + + // -- the header tiles ----------------------------------------------- + + @Test + void theTilesAreAListBecauseEveryOneOfTheDesignsSetsFour() { + ProposalMetaLine meta = new ProposalMetaLine(List.of( + new ProposalMetaLine.Entry("date", "DATE", "27 May 2026"), + new ProposalMetaLine.Entry("id", "PROPOSAL ID", "REV-2026-0527-01"), + new ProposalMetaLine.Entry("valid", "VALID UNTIL", "26 June 2026"), + new ProposalMetaLine.Entry("by", "PREPARED BY", "The Business Team"))); + assertThat(meta.entries()).hasSize(4); + assertThat(meta.entries().get(1).label()).isEqualTo("PROPOSAL ID"); + // The running-header trio is a different statement, and stays empty. + assertThat(meta.preparedFor()).isEmpty(); + } + + @Test + void theRunningHeaderTrioIsUntouchedByTheTiles() { + ProposalMetaLine meta = new ProposalMetaLine("Bright Future Ltd.", "The Team", "27 May"); + assertThat(meta.preparedFor()).isEqualTo("Bright Future Ltd."); + assertThat(meta.entries()).isEmpty(); + } + + // -- the title ------------------------------------------------------- + + @Test + void aTitleOfFourLinesKeepsAllFourAndStillReadsAsThree() { + ProposalTitleLines title = ProposalTitleLines.of("", List.of( + "Proposal for", "Financial Infrastructure", + "& Embedded Finance", "Partnership"), List.of("Empowering your business.")); + assertThat(title.lines()).hasSize(4); + // The three named lines are the first three of the list, so a preset + // written against them reads the same title. + assertThat(title.lead()).isEqualTo("Proposal for"); + assertThat(title.second()).isEqualTo("Financial Infrastructure"); + assertThat(title.third()).isEqualTo("& Embedded Finance"); + assertThat(title.standfirst()).containsExactly("Empowering your business."); + } + + @Test + void aTitleStatedAsThreeLinesReadsBackAsAListOfThree() { + ProposalTitleLines title = new ProposalTitleLines("Proposal for", "Cloud Services", ""); + // The blank third line is not a line of the title. + assertThat(title.lines()).containsExactly("Proposal for", "Cloud Services"); + assertThat(title.eyebrow()).isEmpty(); + } + + @Test + void statingBothATrioAndAListReadsTheTitleFromTheList() { + // Only the canonical constructor allows both, and it resolves rather + // than throws: the list is the one that can hold a title of any length, + // so reading the three back from it loses nothing the caller stated. + ProposalTitleLines title = new ProposalTitleLines("discarded", "also discarded", "", + "", List.of("Proposal for", "Cloud Services", "& Support"), List.of()); + assertThat(title.lines()).containsExactly("Proposal for", "Cloud Services", "& Support"); + assertThat(title.lead()).isEqualTo("Proposal for"); + } + + @Test + void aTitleCarriesTheEyebrowSomeDesignsSetAboveIt() { + ProposalTitleLines title = ProposalTitleLines.of("Proposal for", + List.of("Payments", "& Financial Infrastructure"), List.of()); + assertThat(title.eyebrow()).isEqualTo("Proposal for"); + assertThat(title.lead()).isEqualTo("Payments"); + } + + // -- the foot -------------------------------------------------------- + + @Test + void aFooterCarriesTheEntityTheAddressTheChannelsAndTheNotice() { + ProposalFooter footer = new ProposalFooter("Northwind UK Limited", + List.of("110 Bishopsgate, London EC2N 4AY, United Kingdom"), + List.of("www.northwind.example", "+44 800 092 1223"), + "This proposal is confidential and valid for 30 days."); + assertThat(footer.name()).isEqualTo("Northwind UK Limited"); + assertThat(footer.contacts()).hasSize(2); + assertThat(footer.confidentiality()).contains("confidential"); + assertThat(footer.isPresent()).isTrue(); + } + + @Test + void aFootThatCarriesOnlyANoticeIsStillPresent() { + // One of the seven states nothing else in its foot. + ProposalFooter footer = new ProposalFooter(null, null, null, + "This proposal is confidential and valid for 30 days."); + assertThat(footer.name()).isEmpty(); + assertThat(footer.isPresent()).isTrue(); + } + + @Test + void aFootThatStatesNothingIsNotPresent() { + assertThat(new ProposalFooter(null, null, null, null).isPresent()).isFalse(); + } + + // -- the paragraph that opens a block -------------------------------- + + @Test + void aBlockCarriesTheParagraphThatOpensItAboveTheItems() { + // Every one of the seven sets one, and five of them set two — once over + // the solution and once over the reasons to choose the issuer. + ProposalScope scope = new ProposalScope("PROJECT OVERVIEW", "", + List.of(new ProposalScope.Item("01", "Multi-currency accounts", + "Hold, send, receive in 30+ currencies")), + "This proposal outlines a tailored solution."); + assertThat(scope.intro()).isEqualTo("This proposal outlines a tailored solution."); + assertThat(scope.items()).hasSize(1); + + ProposalGlance glance = new ProposalGlance("ABOUT", + List.of(new ProposalGlance.Fact("globe", "Global", "Scale", "")), + "Modern financial infrastructure for forward-thinking companies."); + assertThat(glance.intro()).contains("Modern financial infrastructure"); + + ProposalGoals goals = new ProposalGoals("WHY US", "", + List.of(new ProposalGoals.Goal("", "Fast")), "Because it is faster."); + assertThat(goals.intro()).isEqualTo("Because it is faster."); + } + + @Test + void theConstructorsThatPredateTheOpeningParagraphLeaveItBlank() { + assertThat(new ProposalScope("PROJECT OVERVIEW", "", List.of()).intro()).isEmpty(); + assertThat(new ProposalGoals("WHY US", "", List.of()).intro()).isEmpty(); + assertThat(new ProposalGlance("ABOUT", List.of()).intro()).isEmpty(); + } + + // -- the aggregate --------------------------------------------------- + + @Test + void theConstructorThatPredatesTheSpineLeavesItEmptyRatherThanNull() { + StructuredProposalData data = new StructuredProposalData( + null, null, null, null, null, null, null, null, null, null, null, null); + assertThat(data.recipient().isPresent()).isFalse(); + assertThat(data.attention().isPresent()).isFalse(); + assertThat(data.footer().isPresent()).isFalse(); + } + + @Test + void theBuilderCarriesTheSpineThrough() { + StructuredProposalData data = StructuredProposalData.builder() + .recipient(new ProposalRecipient("PREPARED FOR", "Bright Future Ltd.", + List.of("45 King Street"))) + .attention(new ProposalAttention("ATTN", "A. Mitchell", "Founder", "", "")) + .footer(new ProposalFooter("Northwind UK Limited", List.of(), List.of(), "")) + .build(); + assertThat(data.recipient().name()).isEqualTo("Bright Future Ltd."); + assertThat(data.attention().role()).isEqualTo("Founder"); + assertThat(data.footer().name()).isEqualTo("Northwind UK Limited"); + } +} diff --git a/qa/src/test/java/com/demcha/compose/document/templates/data/receipt/ReceiptDataTest.java b/qa/src/test/java/com/demcha/compose/document/templates/data/receipt/ReceiptDataTest.java new file mode 100644 index 000000000..7689545eb --- /dev/null +++ b/qa/src/test/java/com/demcha/compose/document/templates/data/receipt/ReceiptDataTest.java @@ -0,0 +1,155 @@ +package com.demcha.compose.document.templates.data.receipt; + +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +/** + * Normalization contract of the receipt records. + * + *

These records are what a caller's payment system hands the template, so + * the interesting cases are the ones a caller gets wrong: a missing status, a + * blank party caption, a null collection. Each has to produce something the + * preset can render rather than a null the widget discovers at layout + * time.

+ */ +class ReceiptDataTest { + + @Test + void defaultsEveryOptionalField() { + ReceiptData data = ReceiptData.builder().build(); + + assertThat(data.documentTitle()).isEqualTo("Payment receipt"); + assertThat(data.amountLabel()).isEqualTo("Amount"); + assertThat(data.payerLabel()).isEqualTo("From"); + assertThat(data.beneficiaryLabel()).isEqualTo("To"); + assertThat(data.issuerName()).isEmpty(); + assertThat(data.amount()).isEmpty(); + assertThat(data.detailGroups()).isEmpty(); + assertThat(data.timeline()).isEmpty(); + assertThat(data.notes()).isEmpty(); + assertThat(data.supportLines()).isEmpty(); + } + + @Test + void missingStatusReadsAsInProgress() { + // The least-claiming reading: a receipt that does not say it settled + // must not be coloured as if it had. + ReceiptData data = ReceiptData.builder().build(); + assertThat(data.status().tone()).isEqualTo(ReceiptStatusTone.IN_PROGRESS); + assertThat(data.status().hasLabel()).isFalse(); + } + + @Test + void blankPartyCaptionsFallBackRatherThanRenderEmpty() { + ReceiptData data = ReceiptData.builder() + .payer(" ", party -> party.name("Alex Sample")) + .beneficiary("", party -> party.name("Harbour Finance Ltd")) + .build(); + + assertThat(data.payerLabel()).isEqualTo("From"); + assertThat(data.beneficiaryLabel()).isEqualTo("To"); + } + + @Test + void presenceFollowsContentNotTheObject() { + // A party builder always yields an object, so "has a payer" has to mean + // "carries something worth drawing" — otherwise every receipt renders an + // empty card. + ReceiptData empty = ReceiptData.builder() + .payer("Paid from", party -> { }) + .build(); + assertThat(empty.hasPayer()).isFalse(); + + ReceiptData named = ReceiptData.builder() + .payer("Paid from", party -> party.name("Alex Sample")) + .build(); + assertThat(named.hasPayer()).isTrue(); + + ReceiptData fieldsOnly = ReceiptData.builder() + .beneficiary("Paid to", party -> party.field("Account", "•••• 5604")) + .build(); + assertThat(fieldsOnly.hasBeneficiary()).isTrue(); + } + + @Test + void collectionsAreFrozen() { + ReceiptData data = ReceiptData.builder() + .summaryField("Value date", "07 Jul 2026") + .detailGroup("Transfer details", group -> group.field("Fee", "£0.00")) + .build(); + + assertThatThrownBy(() -> data.summaryFields().add(ReceiptField.of("x", "y"))) + .isInstanceOf(UnsupportedOperationException.class); + assertThatThrownBy(() -> data.detailGroups().clear()) + .isInstanceOf(UnsupportedOperationException.class); + } + + @Test + void blankNotesAndSupportLinesAreDropped() { + ReceiptData data = ReceiptData.builder() + .note(" ") + .note(null) + .note("Keep this for your records.") + .supportLine("") + .supportLine("help@example.com") + .build(); + + assertThat(data.notes()).containsExactly("Keep this for your records."); + assertThat(data.supportLines()).containsExactly("help@example.com"); + } + + @Test + void partyDropsBlankAddressLines() { + ReceiptParty party = ReceiptParty.builder() + .name("Alex Sample") + .addressLines("12 Example Way", " ", null, "Brentford TW0 0AA") + .build(); + + assertThat(party.addressLines()).containsExactly("12 Example Way", "Brentford TW0 0AA"); + } + + @Test + void nullCollectionsNormalizeToEmpty() { + ReceiptFieldGroup group = new ReceiptFieldGroup(null, null); + assertThat(group.title()).isEmpty(); + assertThat(group.fields()).isEmpty(); + + ReceiptParty party = new ReceiptParty(null, null, null); + assertThat(party.name()).isEmpty(); + assertThat(party.fields()).isEmpty(); + + ReceiptEvent event = new ReceiptEvent(null, null, null); + assertThat(event.label()).isEmpty(); + assertThat(event.timestamp()).isEmpty(); + assertThat(event.detail()).isEmpty(); + } + + @Test + void specWrapsMissingPayloadRatherThanCarryingNull() { + assertThat(new ReceiptDocumentSpec(null).receipt()).isNotNull(); + assertThat(ReceiptDocumentSpec.of(null).receipt().documentTitle()) + .isEqualTo("Payment receipt"); + assertThat(ReceiptDocumentSpec.from(ReceiptData.builder() + .documentTitle("Transfer confirmation") + .build()).receipt().documentTitle()) + .isEqualTo("Transfer confirmation"); + } + + @Test + void emphasisIsCarriedPerField() { + ReceiptFieldGroup group = ReceiptFieldGroup.builder("Amount breakdown") + .field("Fee", "£0.00") + .emphasized("Total debited", "£66.62") + .add(ReceiptField.of("Rate", "1.00")) + .build(); + + assertThat(group.fields()).extracting(ReceiptField::emphasized) + .containsExactly(false, true, false); + assertThat(group.fields()).extracting(ReceiptField::label) + .isEqualTo(List.of("Fee", "Total debited", "Rate")); + } +} diff --git a/qa/src/test/java/com/demcha/compose/document/templates/data/rota/StructuredRotaDataTest.java b/qa/src/test/java/com/demcha/compose/document/templates/data/rota/StructuredRotaDataTest.java new file mode 100644 index 000000000..5f2e89c49 --- /dev/null +++ b/qa/src/test/java/com/demcha/compose/document/templates/data/rota/StructuredRotaDataTest.java @@ -0,0 +1,244 @@ +package com.demcha.compose.document.templates.data.rota; + +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +/** + * Pins the rota model's two contracts: a partial document composes without null + * checks in preset code, and nothing a caller keeps a reference to can change + * the document afterwards. + */ +class StructuredRotaDataTest { + + @Test + void anEmptyRotaHasEveryComponentRatherThanNulls() { + // A preset reads six components and drawing guards on each of them is + // six chances to forget one. The model is what makes that unnecessary. + StructuredRotaData rota = StructuredRotaData.builder().build(); + assertThat(rota.venue()).isNotNull(); + assertThat(rota.week()).isNotNull(); + assertThat(rota.days()).isEmpty(); + assertThat(rota.legend()).isNotNull(); + assertThat(rota.groups()).isEmpty(); + assertThat(rota.footer()).isNotNull(); + assertThat(rota.venue().isPresent()).isFalse(); + assertThat(rota.week().isPresent()).isFalse(); + assertThat(rota.legend().isPresent()).isFalse(); + assertThat(rota.footer().isPresent()).isFalse(); + assertThat(StructuredRotaDocumentSpec.from(null).rota()).isNotNull(); + } + + @Test + void everyBuilderSetterReachesTheDocumentItBuilds() { + // Without this, a setter that dropped its argument passes the suite: + // the empty-rota test above asserts only the absent half of each block. + RotaVenue venue = new RotaVenue("HARBOUR", "Quayside", "Harbour Quayside"); + RotaWeek week = new RotaWeek("WEEKLY ROTA", "31 Aug - 6 Sep"); + RotaLegend legend = new RotaLegend("STATUS", "COVERS", "L", "D", + List.of(new RotaLegend.Entry("OFF", ShiftStatus.OFF))); + RotaFooter footer = new RotaFooter("Swaps to the duty manager by Thursday."); + RotaDay day = new RotaDay("MONDAY", "31", "ST", "Pianist 18:30", + new RotaCovers("104", "50")); + RotaGroup group = new RotaGroup("BAR", "glass", + List.of(new RotaStaff("Priya", List.of(List.of(RotaShift.hours("09:00-17:00")))))); + + StructuredRotaData rota = StructuredRotaData.builder() + .venue(venue).week(week).days(List.of(day)) + .legend(legend).groups(List.of(group)).footer(footer) + .build(); + + assertThat(rota.venue()).isEqualTo(venue); + assertThat(rota.week()).isEqualTo(week); + assertThat(rota.days()).containsExactly(day); + assertThat(rota.legend()).isEqualTo(legend); + assertThat(rota.groups()).containsExactly(group); + assertThat(rota.footer()).isEqualTo(footer); + assertThat(StructuredRotaDocumentSpec.from(rota).rota()).isEqualTo(rota); + } + + @Test + void aStatedBlockReportsItselfPresent() { + // The other half of every isPresent(): all three would pass the empty + // case if they simply always answered false. + assertThat(new RotaVenue("HARBOUR", "", "").isPresent()).isTrue(); + assertThat(new RotaVenue("", "Quayside", "").isPresent()).isTrue(); + assertThat(new RotaVenue("", "", "Harbour Quayside").isPresent()).isTrue(); + assertThat(new RotaWeek("WEEKLY ROTA", "").isPresent()).isTrue(); + assertThat(new RotaWeek("", "31 Aug - 6 Sep").isPresent()).isTrue(); + assertThat(new RotaFooter("Swaps to the duty manager.").isPresent()).isTrue(); + assertThat(new RotaLegend("STATUS", List.of()).isPresent()).isTrue(); + assertThat(new RotaLegend("", List.of( + new RotaLegend.Entry("OFF", ShiftStatus.OFF))).isPresent()).isTrue(); + } + + @Test + void aDayCarriesItsNoteAndItsCovers() { + RotaDay day = new RotaDay("MONDAY", "31", "ST", "Pianist 18:30", + new RotaCovers("104", "50")); + assertThat(day.note()).isEqualTo("Pianist 18:30"); + assertThat(day.covers().lunch()).isEqualTo("104"); + assertThat(day.covers().dinner()).isEqualTo("50"); + } + + @Test + void theCoversRowCarriesItsOwnLabels() { + // The covers row is a label and two marks, and all three are the + // document's words rather than the preset's. + RotaLegend legend = new RotaLegend("STATUS LEGEND", "COVERS", "L", "D", List.of()); + assertThat(legend.coversLabel()).isEqualTo("COVERS"); + assertThat(legend.coversLunchLabel()).isEqualTo("L"); + assertThat(legend.coversDinnerLabel()).isEqualTo("D"); + } + + @Test + void aDayIsAListSoAnEmptyDayAndASplitDayAreBothOrdinary() { + // The reason days() is a list of lists: a blank cell and a split shift + // are the two shapes a rota actually has, and neither is an exception. + RotaStaff staff = new RotaStaff("Priya", List.of( + List.of(), + List.of(RotaShift.hours("09:00-18:00")), + List.of(RotaShift.hours("12:00-16:00"), + new RotaShift("16:00-22:00", ShiftStatus.NONE, ShiftEmphasis.SOFT)))); + assertThat(staff.day(0)).isEmpty(); + assertThat(staff.day(1)).hasSize(1); + assertThat(staff.day(2)).hasSize(2); + } + + @Test + void askingForADayTheRotaDoesNotReachIsNotAnError() { + // A preset walks the rota's own day columns, and a person listed with + // fewer days than the sheet has is a short row, not a broken document. + RotaStaff staff = new RotaStaff("Priya", List.of(List.of(RotaShift.hours("09:00-17:00")))); + assertThat(staff.day(6)).isEmpty(); + assertThat(staff.day(-1)).isEmpty(); + } + + @Test + void theInnerDayListsAreFrozenAndNotOnlyTheOuterOne() { + // A frozen outer list of mutable days is not a frozen rota: the cell a + // preset reads twice has to be the cell the caller handed over. + List monday = new ArrayList<>(List.of(RotaShift.hours("09:00-17:00"))); + List> days = new ArrayList<>(List.of(monday)); + RotaStaff staff = new RotaStaff("Priya", days); + + monday.add(RotaShift.strong("OFF", ShiftStatus.OFF)); + days.add(List.of()); + + assertThat(staff.days()).hasSize(1); + assertThat(staff.day(0)).hasSize(1); + assertThatThrownBy(() -> staff.day(0).add(RotaShift.hours("18:00-23:00"))) + .isInstanceOf(UnsupportedOperationException.class); + } + + @Test + void aShiftSaysWhatItMeansSeparatelyFromWhatItPrints() { + // Two sites print different words for the same thing; a preset colours + // by the meaning, so both colour alike. + RotaShift annualLeave = RotaShift.strong("A/L", ShiftStatus.HOLIDAY); + RotaShift holiday = RotaShift.strong("HOL", ShiftStatus.HOLIDAY); + assertThat(annualLeave.status()).isEqualTo(holiday.status()); + assertThat(annualLeave.text()).isNotEqualTo(holiday.text()); + assertThat(annualLeave.emphasis()).isEqualTo(ShiftEmphasis.STRONG); + } + + @Test + void anEntryStatesItsOwnEmphasisBecauseNoFactoryCanGuessIt() { + // A split day is drawn loud then quiet, and sometimes the other way, + // and sometimes loud twice — so there is no marked(text, status) that + // picks for the caller. Both halves are named. + assertThat(RotaShift.strong("09:00-16:00", ShiftStatus.STOCK).emphasis()) + .isEqualTo(ShiftEmphasis.STRONG); + assertThat(RotaShift.soft("16:00-22:00", ShiftStatus.STOCK).emphasis()) + .isEqualTo(ShiftEmphasis.SOFT); + assertThat(RotaShift.soft("16:00-22:00", ShiftStatus.STOCK).status()) + .isEqualTo(ShiftStatus.STOCK); + } + + @Test + void aShiftThatStatesNothingIsPlainAndUnmarked() { + RotaShift shift = new RotaShift(null, null, null); + assertThat(shift.text()).isEmpty(); + assertThat(shift.status()).isEqualTo(ShiftStatus.NONE); + assertThat(shift.emphasis()).isEqualTo(ShiftEmphasis.PLAIN); + assertThat(RotaShift.hours("09:00-17:00").emphasis()).isEqualTo(ShiftEmphasis.PLAIN); + } + + @Test + void aBandStatesItsOwnMarkOrNone() { + assertThat(new RotaGroup("MANAGEMENT", List.of()).icon()).isEmpty(); + assertThat(new RotaGroup("BAR", "glass", List.of()).icon()).isEqualTo("glass"); + } + + @Test + void aDaysDateIsThreeFieldsSoASuffixCanBeSetApart() { + // A design that raises the suffix cannot split a string that arrived + // joined, so the day never joins them itself. + RotaDay day = new RotaDay("MONDAY", "31", "ST"); + assertThat(day.name()).isEqualTo("MONDAY"); + assertThat(day.ordinal()).isEqualTo("31"); + assertThat(day.ordinalSuffix()).isEqualTo("ST"); + assertThat(day.note()).isEmpty(); + assertThat(day.covers().isPresent()).isFalse(); + } + + @Test + void coversAreTwoCountsBecauseOneStringLosesWhichIsWhich() { + RotaCovers covers = new RotaCovers("104", "50"); + assertThat(covers.isPresent()).isTrue(); + assertThat(covers.lunch()).isEqualTo("104"); + assertThat(covers.dinner()).isEqualTo("50"); + assertThat(new RotaCovers(null, null).isPresent()).isFalse(); + } + + @Test + void aLegendEntryPairsThePrintedWordWithTheStatusItStandsFor() { + RotaLegend legend = new RotaLegend("STATUS", List.of( + new RotaLegend.Entry("A/L", ShiftStatus.HOLIDAY), + new RotaLegend.Entry("OFF", ShiftStatus.OFF))); + assertThat(legend.isPresent()).isTrue(); + assertThat(legend.coversLabel()).isEmpty(); + assertThat(legend.entries()).extracting(RotaLegend.Entry::status) + .containsExactly(ShiftStatus.HOLIDAY, ShiftStatus.OFF); + assertThat(new RotaLegend.Entry("?", null).status()).isEqualTo(ShiftStatus.NONE); + } + + @Test + void theBuildersListsAreCopiedRatherThanKept() { + List days = new ArrayList<>(List.of(new RotaDay("MONDAY", "31", "st"))); + List groups = new ArrayList<>(List.of(new RotaGroup("BAR", List.of()))); + StructuredRotaData rota = StructuredRotaData.builder() + .days(days) + .groups(groups) + .build(); + + days.add(new RotaDay("TUESDAY", "1", "st")); + groups.add(new RotaGroup("KITCHEN", List.of())); + + assertThat(rota.days()).hasSize(1); + assertThat(rota.groups()).hasSize(1); + assertThatThrownBy(() -> rota.days().add(new RotaDay("TUESDAY", "1", "ST"))) + .isInstanceOf(UnsupportedOperationException.class); + assertThatThrownBy(() -> rota.groups().add(new RotaGroup("KITCHEN", List.of()))) + .isInstanceOf(UnsupportedOperationException.class); + } + + @Test + void staffAndTheirGroupsAreReadInTheOrderTheyArePrinted() { + StructuredRotaData rota = StructuredRotaData.builder() + .groups(List.of( + new RotaGroup("MANAGEMENT", List.of(new RotaStaff("Priya", List.of()))), + new RotaGroup("BAR", "glass", List.of( + new RotaStaff("Tomas", List.of()), + new RotaStaff("Lena", List.of()))))) + .build(); + assertThat(rota.groups()).extracting(RotaGroup::label) + .containsExactly("MANAGEMENT", "BAR"); + assertThat(rota.groups().get(1).staff()).extracting(RotaStaff::name) + .containsExactly("Tomas", "Lena"); + } +} diff --git a/qa/src/test/java/com/demcha/compose/document/templates/invoice/presets/ClassicInvoiceLayoutSnapshotTest.java b/qa/src/test/java/com/demcha/compose/document/templates/invoice/presets/ClassicInvoiceLayoutSnapshotTest.java new file mode 100644 index 000000000..140c2f61f --- /dev/null +++ b/qa/src/test/java/com/demcha/compose/document/templates/invoice/presets/ClassicInvoiceLayoutSnapshotTest.java @@ -0,0 +1,58 @@ +package com.demcha.compose.document.templates.invoice.presets; + +import com.demcha.compose.GraphCompose; +import com.demcha.compose.document.api.DocumentPageSize; +import com.demcha.compose.document.api.DocumentSession; +import com.demcha.compose.document.templates.TemplateTestSupport; +import com.demcha.compose.document.templates.data.invoice.InvoiceDocumentSpec; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Exact layout snapshot gate for {@link ClassicInvoice} — freezes the + * resolved node geometry the pixel budget cannot see (a small column or + * spacing shift stays under the visual-diff budget but changes the + * snapshot), and the pagination contract of the overflow invoice: the + * forty-line-item fixture flows onto a second page with the repeated + * table header, and the summary + footer follow the table. + * + *

The multi-page contract is guarded here at snapshot level only — + * full-page pixel baselines drift across platforms far more than the + * geometry they would guard (see the budget note in + * {@code InvoiceV2VisualParityTest}), while the snapshot is exact on + * every platform.

+ * + *

Refresh with {@code -Dgraphcompose.updateSnapshots=true} after a + * deliberate layout change, and commit the JSON with the change.

+ */ +class ClassicInvoiceLayoutSnapshotTest { + + private static DocumentSession open() { + float m = (float) ClassicInvoice.RECOMMENDED_MARGIN; + return GraphCompose.document() + .pageSize(DocumentPageSize.A4) + .margin(m, m, m, m) + .create(); + } + + @Test + void canonicalInvoiceMatchesLayoutSnapshot() throws Exception { + try (DocumentSession session = open()) { + ClassicInvoice.create().compose(session, InvoicePresetFixtures.canonicalInvoice()); + assertThat(session.layoutSnapshot().totalPages()).isEqualTo(1); + TemplateTestSupport.assertCanonicalSnapshot( + session, "classic_invoice_layout", "invoice"); + } + } + + @Test + void overflowInvoicePaginatesOntoSecondPage() throws Exception { + try (DocumentSession session = open()) { + ClassicInvoice.create().compose(session, InvoicePresetFixtures.stressInvoice()); + assertThat(session.layoutSnapshot().totalPages()).isEqualTo(2); + TemplateTestSupport.assertCanonicalSnapshot( + session, "classic_invoice_stress_layout", "invoice"); + } + } +} diff --git a/qa/src/test/java/com/demcha/compose/document/templates/invoice/presets/ClassicInvoiceSmokeTest.java b/qa/src/test/java/com/demcha/compose/document/templates/invoice/presets/ClassicInvoiceSmokeTest.java new file mode 100644 index 000000000..2b1776029 --- /dev/null +++ b/qa/src/test/java/com/demcha/compose/document/templates/invoice/presets/ClassicInvoiceSmokeTest.java @@ -0,0 +1,83 @@ +package com.demcha.compose.document.templates.invoice.presets; + +import com.demcha.compose.GraphCompose; +import com.demcha.compose.document.api.DocumentPageSize; +import com.demcha.compose.document.api.DocumentSession; +import com.demcha.compose.document.templates.api.DocumentTemplate; +import com.demcha.compose.document.templates.core.theme.BrandTheme; +import com.demcha.compose.document.templates.data.invoice.InvoiceData; +import com.demcha.compose.document.templates.data.invoice.InvoiceDocumentSpec; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Smoke test for the layered invoice pipeline through + * {@link ClassicInvoice} — proves the preset renders an + * {@link InvoiceDocumentSpec} end-to-end on a {@link BrandTheme}, via + * both factory variants, with any theme, and on an empty invoice. + */ +class ClassicInvoiceSmokeTest { + + /** An invoice with no line items, summaries, notes, footer, or status — the empty paths. */ + private static InvoiceDocumentSpec minimalSpec() { + return InvoiceDocumentSpec.from(InvoiceData.builder() + .invoiceNumber("GC-2026-002") + .fromParty(from -> from.name("GraphCompose Studio")) + .billToParty(to -> to.name("Northwind Systems")) + .build()); + } + + private static void render(DocumentTemplate template, + InvoiceDocumentSpec spec) throws Exception { + float m = (float) ClassicInvoice.RECOMMENDED_MARGIN; + try (DocumentSession session = GraphCompose.document() + .pageSize(DocumentPageSize.A4) + .margin(m, m, m, m) + .create()) { + template.compose(session, spec); + assertThat(session.roots()).isNotEmpty(); + // Drive layout + render, not just composition — the zero-row + // summary table of an empty invoice only exists at layout time. + assertThat(session.toPdfBytes()).isNotEmpty(); + } + } + + @Test + void exposesStableIdentity() { + DocumentTemplate template = ClassicInvoice.create(); + assertThat(template.id()).isEqualTo(ClassicInvoice.ID); + assertThat(template.displayName()).isEqualTo(ClassicInvoice.DISPLAY_NAME); + } + + @Test + void defaultFactoryRendersWithInvoiceTheme() throws Exception { + // create() wires BrandTheme.invoiceModern() — the variant the example uses. + render(ClassicInvoice.create(), InvoicePresetFixtures.canonicalInvoice()); + } + + @Test + void rendersWithExplicitTheme() throws Exception { + render(ClassicInvoice.create(BrandTheme.invoiceModern()), + InvoicePresetFixtures.canonicalInvoice()); + } + + @Test + void readsAnyTheme() throws Exception { + // Renders under a non-invoice theme without crashing: the header, + // hero, labels, and footer follow the theme; the line-item body + // cells inherit the DSL default (as in ModernInvoice). + render(ClassicInvoice.create(BrandTheme.boxedClassic()), + InvoicePresetFixtures.canonicalInvoice()); + } + + @Test + void rendersEmptyInvoice() throws Exception { + // Exercises the empty-collection paths through full layout and + // render: the skipped Summary section (the engine rejects a + // zero-row table), the empty note / payment lists under their + // always-rendered headings, the skipped footer-note paragraph, + // and the em-dash status fallback. + render(ClassicInvoice.create(), minimalSpec()); + } +} diff --git a/qa/src/test/java/com/demcha/compose/document/templates/invoice/presets/ConsultingInvoiceFixtures.java b/qa/src/test/java/com/demcha/compose/document/templates/invoice/presets/ConsultingInvoiceFixtures.java new file mode 100644 index 000000000..17f39b942 --- /dev/null +++ b/qa/src/test/java/com/demcha/compose/document/templates/invoice/presets/ConsultingInvoiceFixtures.java @@ -0,0 +1,135 @@ +package com.demcha.compose.document.templates.invoice.presets; + +import com.demcha.compose.document.image.DocumentImageData; +import com.demcha.compose.document.templates.data.invoice.InvoiceBrand; +import com.demcha.compose.document.templates.data.invoice.InvoiceContactBlock; +import com.demcha.compose.document.templates.data.invoice.InvoiceMasthead; +import com.demcha.compose.document.templates.data.invoice.InvoiceNotesBlock; +import com.demcha.compose.document.templates.data.invoice.InvoicePaymentBlock; +import com.demcha.compose.document.templates.data.invoice.InvoiceRecipient; +import com.demcha.compose.document.templates.data.invoice.InvoiceServiceLines; +import com.demcha.compose.document.templates.data.invoice.InvoiceSummaryBlock; +import com.demcha.compose.document.templates.data.invoice.InvoiceTotalsBlock; +import com.demcha.compose.document.templates.data.invoice.StructuredInvoiceData; +import com.demcha.compose.document.templates.data.invoice.StructuredInvoiceDocumentSpec; + +import java.io.IOException; +import java.io.InputStream; +import java.io.UncheckedIOException; +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.List; + +/** + * Shared fixture data for the Consulting Invoice gates — the SAME spec + * feeds the smoke, layout-snapshot and pixel gates, so a geometry shift the + * pixel budget absorbs still trips the exact snapshot, and vice versa. + * + *

The canonical fixture is the preset's reference content: a + * professional-services invoice with a brand logo, five masthead metadata + * rows (one emphasized), five priced service lines, a subtotal / tax + * stack with the total band, five bank fields, and notes naming both query + * channels. The overflow fixture repeats the service lines until the table + * paginates, which is what freezes the repeating table header.

+ * + *

Kept in lockstep with the examples module's + * {@code ConsultingInvoiceSampleData} — the two modules cannot share a + * source file, so a content change here belongs there too.

+ */ +final class ConsultingInvoiceFixtures { + + private ConsultingInvoiceFixtures() { + } + + /** The single-page reference invoice. */ + static StructuredInvoiceDocumentSpec canonicalInvoice() { + return StructuredInvoiceDocumentSpec.from(baseBuilder(serviceLines(5)).build()); + } + + /** Enough service lines to push the table onto a second page. */ + static StructuredInvoiceDocumentSpec overflowInvoice() { + return StructuredInvoiceDocumentSpec.from(baseBuilder(serviceLines(26)).build()); + } + + /** The sample logo, read from the qa test resources. */ + static DocumentImageData sampleLogo() { + String path = "/sample-data/consulting-invoice-logo.png"; + try (InputStream input = ConsultingInvoiceFixtures.class.getResourceAsStream(path)) { + if (input == null) { + throw new IllegalStateException("Missing sample logo: " + path); + } + return DocumentImageData.fromBytes(input.readAllBytes()); + } catch (IOException e) { + throw new UncheckedIOException("Failed to read sample logo: " + path, e); + } + } + + private static StructuredInvoiceData.Builder baseBuilder(InvoiceServiceLines lines) { + return StructuredInvoiceData.builder() + .brand(new InvoiceBrand(sampleLogo(), "NORTHPOINT", "CONSULTING", + "Strategy. Solutions. Results.")) + .supplier(new InvoiceContactBlock("Northpoint Consulting Pty Ltd", + List.of("Level 8, 1 Collins Street", "Melbourne VIC 3000 Australia"), + "+61 3 9876 5432", "hello@northpoint.com.au", "northpoint.com.au", + "ABN", "12 345 678 901")) + .masthead(new InvoiceMasthead("INVOICE", List.of( + new InvoiceMasthead.Entry("Invoice Number:", "INV-2025-0478", false), + new InvoiceMasthead.Entry("Issue Date:", "26 May 2025", false), + new InvoiceMasthead.Entry("Due Date:", "25 June 2025", true), + new InvoiceMasthead.Entry("Project:", "Digital Strategy Engagement", false), + new InvoiceMasthead.Entry("PO Number:", "PO-7892", false)))) + .billTo(new InvoiceRecipient("BILLED TO", "Greenfield Industries Ltd.", + "Accounts Payable Department", + List.of("12 Innovation Drive", "Melbourne VIC 3000", "Australia"), + "Email:", "ap@greenfield.com.au")) + .summary(new InvoiceSummaryBlock("INVOICE SUMMARY", + "Professional services rendered in accordance with the statement of " + + "work for the period", + "1 May 2025 – 25 May 2025.")) + .serviceLines(lines) + .totals(new InvoiceTotalsBlock(List.of( + new InvoiceTotalsBlock.Row("SUBTOTAL", new BigDecimal("14000.00")), + new InvoiceTotalsBlock.Row("GST (10%)", new BigDecimal("1400.00"))), + "TOTAL DUE", new BigDecimal("15400.00"))) + .payment(new InvoicePaymentBlock("PAYMENT INFORMATION", List.of( + new InvoicePaymentBlock.Field("Bank Name:", "Example Bank"), + new InvoicePaymentBlock.Field("Account Name:", + "Northpoint Consulting Pty Ltd"), + new InvoicePaymentBlock.Field("BSB:", "123-456"), + new InvoicePaymentBlock.Field("Account Number:", "12345678"), + new InvoicePaymentBlock.Field("Reference:", "INV-2025-0478")), + "Please ensure the invoice number is included in the payment reference.", + "Payment is due within 30 days from the issue date.", "30 days")) + .notes(new InvoiceNotesBlock("NOTES", List.of( + "Thank you for your business.", + "If you have any questions regarding this invoice, please contact us " + + "at accounts@northpoint.com.au or +61 3 9876 5432."), + "accounts@northpoint.com.au", "+61 3 9876 5432")) + .currencyCode("AUD"); + } + + private static InvoiceServiceLines serviceLines(int count) { + String[][] source = { + {"Strategic Consultation", "Leadership alignment workshops", "1–10 May 2025", + "10.00", "hrs", "250.00", "2500.00"}, + {"Market & Competitive Analysis", "Research and analysis report", "1–15 May 2025", + "1.00", "ea", "3500.00", "3500.00"}, + {"Digital Roadmap Development", "Strategy and roadmap creation", "5–20 May 2025", + "1.00", "ea", "4800.00", "4800.00"}, + {"Stakeholder Review Sessions", "Facilitation and documentation", "15–22 May 2025", + "6.00", "hrs", "220.00", "1320.00"}, + {"Presentation & Final Report", "Executive presentation and delivery", + "20–25 May 2025", "1.00", "ea", "1880.00", "1880.00"}}; + List lines = new ArrayList<>(); + for (int index = 0; index < count; index++) { + String[] row = source[index % source.length]; + lines.add(new InvoiceServiceLines.Line(index + 1, row[0], row[1], row[2], + new BigDecimal(row[3]), row[4], new BigDecimal(row[5]), + new BigDecimal(row[6]))); + } + return new InvoiceServiceLines( + new InvoiceServiceLines.Columns("#", "DESCRIPTION", "SERVICE PERIOD", "QTY", + "UNIT PRICE", "AMOUNT"), + lines); + } +} diff --git a/qa/src/test/java/com/demcha/compose/document/templates/invoice/presets/ConsultingInvoiceLayoutSnapshotTest.java b/qa/src/test/java/com/demcha/compose/document/templates/invoice/presets/ConsultingInvoiceLayoutSnapshotTest.java new file mode 100644 index 000000000..2f616d80b --- /dev/null +++ b/qa/src/test/java/com/demcha/compose/document/templates/invoice/presets/ConsultingInvoiceLayoutSnapshotTest.java @@ -0,0 +1,43 @@ +package com.demcha.compose.document.templates.invoice.presets; + +import com.demcha.compose.GraphCompose; +import com.demcha.compose.document.api.DocumentSession; +import com.demcha.compose.document.templates.TemplateTestSupport; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Exact layout snapshot gate for {@link ConsultingInvoice} — freezes the + * resolved geometry the pixel budget cannot see (a small column or spacing + * shift stays under the visual-diff budget but changes the snapshot), and + * the pagination contract of the overflow invoice: the service lines flow + * onto a second page and the totals stack stays whole. + * + *

Refresh with {@code -Dgraphcompose.updateSnapshots=true} after a + * deliberate layout change, and commit the JSON with the change.

+ */ +class ConsultingInvoiceLayoutSnapshotTest { + + @Test + void canonicalInvoiceMatchesLayoutSnapshot() throws Exception { + try (DocumentSession session = GraphCompose.document().create()) { + ConsultingInvoice.create().compose( + session, ConsultingInvoiceFixtures.canonicalInvoice()); + assertThat(session.layoutSnapshot().totalPages()).isEqualTo(1); + TemplateTestSupport.assertCanonicalSnapshot( + session, "consulting_invoice_layout", "invoice"); + } + } + + @Test + void overflowInvoicePaginatesOntoASecondPage() throws Exception { + try (DocumentSession session = GraphCompose.document().create()) { + ConsultingInvoice.create().compose( + session, ConsultingInvoiceFixtures.overflowInvoice()); + assertThat(session.layoutSnapshot().totalPages()).isGreaterThan(1); + TemplateTestSupport.assertCanonicalSnapshot( + session, "consulting_invoice_overflow_layout", "invoice"); + } + } +} diff --git a/qa/src/test/java/com/demcha/compose/document/templates/invoice/presets/ConsultingInvoiceSmokeTest.java b/qa/src/test/java/com/demcha/compose/document/templates/invoice/presets/ConsultingInvoiceSmokeTest.java new file mode 100644 index 000000000..b6a841bef --- /dev/null +++ b/qa/src/test/java/com/demcha/compose/document/templates/invoice/presets/ConsultingInvoiceSmokeTest.java @@ -0,0 +1,234 @@ +package com.demcha.compose.document.templates.invoice.presets; + +import com.demcha.compose.GraphCompose; +import com.demcha.compose.document.api.DocumentSession; +import com.demcha.compose.document.templates.api.DocumentTemplate; +import com.demcha.compose.document.templates.data.invoice.InvoiceBrand; +import com.demcha.compose.document.templates.data.invoice.InvoiceContactBlock; +import com.demcha.compose.document.templates.data.invoice.InvoiceNotesBlock; +import com.demcha.compose.document.templates.data.invoice.InvoicePaymentBlock; +import com.demcha.compose.document.templates.data.invoice.StructuredInvoiceData; +import com.demcha.compose.document.templates.data.invoice.StructuredInvoiceDocumentSpec; +import org.apache.pdfbox.Loader; +import org.apache.pdfbox.pdmodel.PDDocument; +import org.apache.pdfbox.pdmodel.PDPage; +import org.apache.pdfbox.pdmodel.interactive.action.PDActionURI; +import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotation; +import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationLink; +import org.apache.pdfbox.text.PDFTextStripper; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Smoke test for the structured invoice pipeline through + * {@link ConsultingInvoice} — proves the preset renders a + * {@link StructuredInvoiceDocumentSpec} end-to-end with the packaged icon + * set and a caller-supplied logo, falls back to the wordmark lockup when + * no logo is supplied, renders an empty document through its guards, and + * puts the priced figures on the text layer, where the layout snapshot + * cannot see inside the table. + */ +class ConsultingInvoiceSmokeTest { + + private static byte[] render(StructuredInvoiceDocumentSpec spec) throws Exception { + // The preset owns its page geometry, so the session starts unconfigured. + try (DocumentSession session = GraphCompose.document().create()) { + ConsultingInvoice.create().compose(session, spec); + assertThat(session.roots()).isNotEmpty(); + byte[] pdfBytes = session.toPdfBytes(); + assertThat(pdfBytes).isNotEmpty(); + return pdfBytes; + } + } + + private static String textOf(byte[] pdfBytes) throws Exception { + try (PDDocument document = Loader.loadPDF(pdfBytes)) { + return new PDFTextStripper().getText(document); + } + } + + @Test + void exposesStableIdentity() { + DocumentTemplate template = ConsultingInvoice.create(); + assertThat(template.id()).isEqualTo(ConsultingInvoice.ID); + assertThat(template.displayName()).isEqualTo(ConsultingInvoice.DISPLAY_NAME); + } + + @Test + void rendersCanonicalInvoiceWithLogoAndPackagedIcons() throws Exception { + render(ConsultingInvoiceFixtures.canonicalInvoice()); + } + + @Test + void rendersWordmarkLockupWhenNoLogoIsSupplied() throws Exception { + StructuredInvoiceData canonical = + ConsultingInvoiceFixtures.canonicalInvoice().invoice(); + InvoiceBrand brand = canonical.brand(); + StructuredInvoiceData withoutLogo = rebuild(canonical) + .brand(new InvoiceBrand(null, brand.name(), brand.qualifier(), brand.tagline())) + .build(); + + String text = textOf(render(StructuredInvoiceDocumentSpec.from(withoutLogo))); + // The wordmark lockup draws the brand name as text; the logo path does not. + assertThat(text).contains("NORTHPOINT"); + } + + @Test + void rendersEmptyInvoice() throws Exception { + // Exercises the empty-collection paths through full layout and render: + // no metadata rows, no service lines, no totals rows, no bank fields, + // no notes — and the blank brand that has neither logo nor name. + render(StructuredInvoiceDocumentSpec.from(StructuredInvoiceData.builder().build())); + } + + @Test + void canonicalRenderCarriesTheTableAndTotalsText() throws Exception { + // The line-items table and the totals bands are leaf nodes in the + // layout snapshot, so their content is asserted on the text layer. + String text = textOf(render(ConsultingInvoiceFixtures.canonicalInvoice())); + assertThat(text) + .contains("Strategic Consultation") + .contains("Leadership alignment workshops") + .contains("1–10 May 2025") + .contains("2,500.00") + .contains("GST (10%)") + .contains("TOTAL DUE (AUD)") + .contains("15,400.00") + .contains("INV-2025-0478"); + } + + @Test + void aDocumentWithoutACurrencyPrintsNoEmptyParentheses() throws Exception { + StructuredInvoiceData canonical = ConsultingInvoiceFixtures.canonicalInvoice().invoice(); + StructuredInvoiceData withoutCurrency = rebuild(canonical).currencyCode("").build(); + + String text = textOf(render(StructuredInvoiceDocumentSpec.from(withoutCurrency))); + assertThat(text).doesNotContain("()").contains("TOTAL DUE"); + } + + @Test + void aSupplierWithoutARegistrationPrintsNoDanglingSeparator() throws Exception { + StructuredInvoiceData canonical = ConsultingInvoiceFixtures.canonicalInvoice().invoice(); + InvoiceContactBlock supplier = canonical.supplier(); + StructuredInvoiceData withoutRegistration = rebuild(canonical) + .supplier(new InvoiceContactBlock(supplier.legalName(), supplier.addressLines(), + supplier.phone(), supplier.email(), supplier.website(), "", "")) + .build(); + + String text = textOf(render(StructuredInvoiceDocumentSpec.from(withoutRegistration))); + assertThat(text) + .contains("Northpoint Consulting Pty Ltd") + .doesNotContain("Pty Ltd |"); + } + + @Test + void theBankBandGrowsWithTheNumberOfPaymentFields() throws Exception { + StructuredInvoiceData canonical = ConsultingInvoiceFixtures.canonicalInvoice().invoice(); + InvoicePaymentBlock payment = canonical.payment(); + List more = new ArrayList<>(payment.fields()); + more.add(new InvoicePaymentBlock.Field("SWIFT:", "EXAMPLEAU")); + more.add(new InvoicePaymentBlock.Field("IBAN:", "AU00 1234 5678 9012")); + StructuredInvoiceData withMoreFields = rebuild(canonical) + .payment(new InvoicePaymentBlock(payment.heading(), more, payment.instruction(), + payment.dueNotice(), payment.dueNoticeEmphasis())) + .build(); + + // The band is sized from the field count, so the last field is drawn + // inside it rather than over the instruction beneath. + String text = textOf(render(StructuredInvoiceDocumentSpec.from(withMoreFields))); + assertThat(text).contains("SWIFT:").contains("IBAN:") + .contains("Please ensure the invoice number"); + } + + /** Copies every component of a spec so one of them can be replaced. */ + private static StructuredInvoiceData.Builder rebuild(StructuredInvoiceData data) { + return StructuredInvoiceData.builder() + .brand(data.brand()) + .supplier(data.supplier()) + .masthead(data.masthead()) + .billTo(data.billTo()) + .summary(data.summary()) + .serviceLines(data.serviceLines()) + .totals(data.totals()) + .payment(data.payment()) + .notes(data.notes()) + .currencyCode(data.currencyCode()); + } + + @Test + void aTrunkPrefixIsPrintedAndNotDialled() throws Exception { + // The sheet prints the number a reader at home would dial and links the + // one a caller abroad has to. The trunk prefix belongs to the first and + // not the second, and this preset used to hand the printed form's + // brackets straight to the link. + // + // The sheet dials in two places from two fields — the supplier in the + // masthead and the contact in the closing prose — so each is given its + // own number and both targets are named. One number would let either + // site alone satisfy the assertion. + StructuredInvoiceData canonical = ConsultingInvoiceFixtures.canonicalInvoice().invoice(); + InvoiceContactBlock supplier = canonical.supplier(); + InvoiceNotesBlock notes = canonical.notes(); + String inTheMasthead = "+44 (0)20 7946 0832"; + String inTheProse = "+44 (0)161 555 2200"; + + StructuredInvoiceData printed = rebuild(canonical) + .supplier(new InvoiceContactBlock(supplier.legalName(), supplier.addressLines(), + inTheMasthead, supplier.email(), supplier.website(), + supplier.registrationLabel(), supplier.registrationNumber(), + supplier.taxRegistrationLabel(), supplier.taxRegistrationNumber(), + supplier.legalFootnote())) + // The closing links the number where it finds it in its own + // prose, so the sentence has to carry the new one too. + .notes(new InvoiceNotesBlock(notes.heading(), + notes.paragraphs().stream() + .map(line -> line.replace(notes.contactPhone(), inTheProse)) + .toList(), + notes.contactEmail(), inTheProse)) + .build(); + + byte[] pdfBytes = render(StructuredInvoiceDocumentSpec.from(printed)); + assertThat(textOf(pdfBytes)).contains(inTheMasthead).contains(inTheProse); + assertThat(dialTargets(pdfBytes)) + .containsExactlyInAnyOrder("tel:+442079460832", "tel:+441615552200"); + } + + /** + * The {@code tel:} targets the file carries. A run that wraps at a space + * carries one annotation per piece, all pointing at the same place, so it + * is the set of targets that is asserted rather than their number. + */ + private static Set dialTargets(byte[] pdfBytes) throws Exception { + Set targets = new LinkedHashSet<>(); + try (PDDocument document = Loader.loadPDF(pdfBytes)) { + for (PDPage page : document.getPages()) { + for (PDAnnotation annotation : page.getAnnotations()) { + if (annotation instanceof PDAnnotationLink link + && link.getAction() instanceof PDActionURI uri + && uri.getURI().startsWith("tel:")) { + targets.add(uri.getURI()); + } + } + } + } + return targets; + } + + @Test + void overflowInvoiceRepeatsTheTableHeaderOnTheSecondPage() throws Exception { + byte[] pdfBytes = render(ConsultingInvoiceFixtures.overflowInvoice()); + try (PDDocument document = Loader.loadPDF(pdfBytes)) { + assertThat(document.getNumberOfPages()).isGreaterThan(1); + PDFTextStripper stripper = new PDFTextStripper(); + stripper.setStartPage(2); + stripper.setEndPage(2); + assertThat(stripper.getText(document)).contains("SERVICE PERIOD"); + } + } +} diff --git a/qa/src/test/java/com/demcha/compose/document/templates/invoice/presets/ConsultingInvoiceVisualParityTest.java b/qa/src/test/java/com/demcha/compose/document/templates/invoice/presets/ConsultingInvoiceVisualParityTest.java new file mode 100644 index 000000000..ebe9ee2d0 --- /dev/null +++ b/qa/src/test/java/com/demcha/compose/document/templates/invoice/presets/ConsultingInvoiceVisualParityTest.java @@ -0,0 +1,46 @@ +package com.demcha.compose.document.templates.invoice.presets; + +import com.demcha.compose.GraphCompose; +import com.demcha.compose.document.api.DocumentSession; +import com.demcha.compose.testing.visual.PdfVisualRegression; +import org.junit.jupiter.api.Test; + +import java.nio.file.Path; + +/** + * Pixel-diff visual parity gate for {@link ConsultingInvoice}. + * + *

Separate from {@code InvoiceV2VisualParityTest} because that gate is + * parameterised on the display {@code InvoiceDocumentSpec} while this + * preset consumes the structured spec. Budget and tolerance mirror the + * other template parity gates.

+ * + *

Re-blessing baselines — after a deliberate visual + * change, re-run with {@code -Dgraphcompose.visual.approve=true} and + * commit the updated PNGs with the change. Baselines live under + * {@code src/test/resources/visual-baselines/invoice-v2-layered/}.

+ */ +class ConsultingInvoiceVisualParityTest { + + private static final Path BASELINE_ROOT = Path.of( + "src", "test", "resources", "visual-baselines", "invoice-v2-layered"); + + private static final long PIXEL_DIFF_BUDGET = 50_000L; + private static final int PER_PIXEL_TOLERANCE = 8; + + @Test + void rendersWithinPixelDiffBudget() throws Exception { + byte[] pdfBytes; + try (DocumentSession document = GraphCompose.document().create()) { + ConsultingInvoice.create().compose( + document, ConsultingInvoiceFixtures.canonicalInvoice()); + pdfBytes = document.toPdfBytes(); + } + + PdfVisualRegression.standard() + .baselineRoot(BASELINE_ROOT) + .perPixelTolerance(PER_PIXEL_TOLERANCE) + .mismatchedPixelBudget(PIXEL_DIFF_BUDGET) + .assertMatchesBaseline("consulting_invoice", pdfBytes); + } +} diff --git a/qa/src/test/java/com/demcha/compose/document/templates/invoice/presets/InvoicePresetFixtures.java b/qa/src/test/java/com/demcha/compose/document/templates/invoice/presets/InvoicePresetFixtures.java new file mode 100644 index 000000000..e5b401de9 --- /dev/null +++ b/qa/src/test/java/com/demcha/compose/document/templates/invoice/presets/InvoicePresetFixtures.java @@ -0,0 +1,112 @@ +package com.demcha.compose.document.templates.invoice.presets; + +import com.demcha.compose.document.templates.data.invoice.InvoiceData; +import com.demcha.compose.document.templates.data.invoice.InvoiceDocumentSpec; + +/** + * Shared fixture data for the invoice preset gates — the SAME specs feed + * the pixel parity test and the layout snapshot test, so a geometry shift + * the pixel budget absorbs still trips the exact snapshot, and vice versa. + */ +final class InvoicePresetFixtures { + + private InvoicePresetFixtures() { + } + + /** + * Canonical sample invoice — exercises the hero, both parties, a + * multi-row line-items table, subtotal / tax / total summary, and the + * notes / payment-terms footer. Kept in qa so the gates depend only + * on main + main-test code. + */ + static InvoiceDocumentSpec canonicalInvoice() { + return InvoiceDocumentSpec.from(InvoiceData.builder() + .title("Invoice") + .invoiceNumber("GC-2026-041") + .issueDate("02 Apr 2026") + .dueDate("16 Apr 2026") + .status("Pending") + .fromParty(from -> from + .name("GraphCompose Studio") + .addressLines("18 Layout Street", "London, UK", "EC1A 4GC") + .email("billing@graphcompose.dev") + .phone("+44 20 5555 1000") + .taxId("GB-99887766")) + .billToParty(to -> to + .name("Northwind Systems") + .addressLines("Attn: Finance Team", "410 Market Avenue", "Manchester, UK") + .email("ap@northwind.example") + .phone("+44 161 555 2200") + .taxId("NW-2026-01")) + .lineItem("Discovery workshop", "Stakeholder interviews", + "1", "GBP 1,450", "GBP 1,450") + .lineItem("Template architecture", "Reusable document flows", + "2", "GBP 980", "GBP 1,960") + .lineItem("Render QA", "Cross-platform pixel diffing", + "3", "GBP 320", "GBP 960") + .lineItem("Developer enablement", "Authoring docs + examples", + "1", "GBP 780", "GBP 780") + .summaryRow("Subtotal", "GBP 5,150") + .summaryRow("VAT (20%)", "GBP 1,030") + .totalRow("Total", "GBP 6,180") + .note("Please include the invoice number on your remittance advice.") + .note("All work was delivered as agreed during the April implementation window.") + .paymentTerm("Payment due within 14 calendar days.") + .paymentTerm("Bank transfer preferred; contact billing@graphcompose.dev for remittance details.") + .paymentTerm("Late payments may delay additional template customization work.") + .footerNote("Thank you for choosing GraphCompose for production document rendering.") + .build()); + } + + /** + * Overflow invoice — forty line items so the table paginates naturally + * and the repeated header + cross-page flow become part of the frozen + * contract. No manual page breaks. + */ + static InvoiceDocumentSpec stressInvoice() { + String[] services = { + "Discovery workshop", "Design system audit", "Template architecture", + "Layout engine tuning", "Render QA pass", "Accessibility review", + "Font pipeline setup", "Chart integration", "Data mapping", + "Pagination hardening", "Visual regression wiring", "Docs authoring", + "Stakeholder demo", "Performance profiling", "Release engineering", + "Support retainer"}; + InvoiceData.Builder builder = InvoiceData.builder() + .title("Invoice") + .invoiceNumber("GC-2026-042") + .issueDate("04 May 2026") + .dueDate("18 May 2026") + .status("Pending") + .fromParty(from -> from + .name("GraphCompose Studio") + .addressLines("18 Layout Street", "London, UK", "EC1A 4GC") + .email("billing@graphcompose.dev") + .phone("+44 20 5555 1000")) + .billToParty(to -> to + .name("Northwind Systems") + .addressLines("Attn: Finance Team", "410 Market Avenue", "Manchester, UK") + .email("ap@northwind.example") + .phone("+44 161 555 2200")); + for (int i = 0; i < 40; i++) { + String service = services[i % services.length]; + int quantity = 1 + i % 4; + int unit = 180 + (i * 37) % 900; + builder.lineItem( + service + " — sprint " + (1 + i / services.length), + "Delivered under the framework agreement", + String.valueOf(quantity), + "GBP " + unit, + "GBP " + (quantity * unit)); + } + return InvoiceDocumentSpec.from(builder + .summaryRow("Subtotal", "GBP 44,890") + .summaryRow("VAT (20%)", "GBP 8,978") + .totalRow("Total", "GBP 53,868") + .note("Sprints 1-3 were delivered under the framework agreement.") + .note("Hardware and third-party licences are billed separately.") + .paymentTerm("Payment due within 14 calendar days.") + .paymentTerm("Bank transfer preferred; contact billing@graphcompose.dev for remittance details.") + .footerNote("Thank you for choosing GraphCompose for production document rendering.") + .build()); + } +} diff --git a/qa/src/test/java/com/demcha/compose/document/templates/invoice/presets/InvoiceV2VisualParityTest.java b/qa/src/test/java/com/demcha/compose/document/templates/invoice/presets/InvoiceV2VisualParityTest.java index 05c05ba7b..b04bd4c37 100644 --- a/qa/src/test/java/com/demcha/compose/document/templates/invoice/presets/InvoiceV2VisualParityTest.java +++ b/qa/src/test/java/com/demcha/compose/document/templates/invoice/presets/InvoiceV2VisualParityTest.java @@ -4,7 +4,6 @@ import com.demcha.compose.document.api.DocumentPageSize; import com.demcha.compose.document.api.DocumentSession; import com.demcha.compose.document.templates.api.DocumentTemplate; -import com.demcha.compose.document.templates.data.invoice.InvoiceData; import com.demcha.compose.document.templates.data.invoice.InvoiceDocumentSpec; import com.demcha.compose.testing.visual.PdfVisualRegression; import org.junit.jupiter.params.ParameterizedTest; @@ -69,51 +68,17 @@ private static Stream presets() { return Stream.of( Arguments.of("modern_invoice", ModernInvoice.RECOMMENDED_MARGIN, - (Supplier>) ModernInvoice::create)); + (Supplier>) ModernInvoice::create), + Arguments.of("classic_invoice", + ClassicInvoice.RECOMMENDED_MARGIN, + (Supplier>) ClassicInvoice::create)); } /** - * Canonical sample invoice — exercises the hero, both parties, a - * multi-row line-items table, subtotal / tax / total summary, and the - * notes / payment-terms footer. Kept inline so the test depends only - * on main + main-test code. + * Canonical sample invoice — shared with the layout snapshot gate via + * {@link InvoicePresetFixtures}, so both gates freeze the same render. */ private static InvoiceDocumentSpec canonicalInvoice() { - return InvoiceDocumentSpec.from(InvoiceData.builder() - .title("Invoice") - .invoiceNumber("GC-2026-041") - .issueDate("02 Apr 2026") - .dueDate("16 Apr 2026") - .status("Pending") - .fromParty(from -> from - .name("GraphCompose Studio") - .addressLines("18 Layout Street", "London, UK", "EC1A 4GC") - .email("billing@graphcompose.dev") - .phone("+44 20 5555 1000") - .taxId("GB-99887766")) - .billToParty(to -> to - .name("Northwind Systems") - .addressLines("Attn: Finance Team", "410 Market Avenue", "Manchester, UK") - .email("ap@northwind.example") - .phone("+44 161 555 2200") - .taxId("NW-2026-01")) - .lineItem("Discovery workshop", "Stakeholder interviews", - "1", "GBP 1,450", "GBP 1,450") - .lineItem("Template architecture", "Reusable document flows", - "2", "GBP 980", "GBP 1,960") - .lineItem("Render QA", "Cross-platform pixel diffing", - "3", "GBP 320", "GBP 960") - .lineItem("Developer enablement", "Authoring docs + examples", - "1", "GBP 780", "GBP 780") - .summaryRow("Subtotal", "GBP 5,150") - .summaryRow("VAT (20%)", "GBP 1,030") - .totalRow("Total", "GBP 6,180") - .note("Please include the invoice number on your remittance advice.") - .note("All work was delivered as agreed during the April implementation window.") - .paymentTerm("Payment due within 14 calendar days.") - .paymentTerm("Bank transfer preferred; contact billing@graphcompose.dev for remittance details.") - .paymentTerm("Late payments may delay additional template customization work.") - .footerNote("Thank you for choosing GraphCompose for production document rendering.") - .build()); + return InvoicePresetFixtures.canonicalInvoice(); } } diff --git a/qa/src/test/java/com/demcha/compose/document/templates/invoice/presets/LumaStudioInvoiceFixtures.java b/qa/src/test/java/com/demcha/compose/document/templates/invoice/presets/LumaStudioInvoiceFixtures.java new file mode 100644 index 000000000..b0115f9bb --- /dev/null +++ b/qa/src/test/java/com/demcha/compose/document/templates/invoice/presets/LumaStudioInvoiceFixtures.java @@ -0,0 +1,130 @@ +package com.demcha.compose.document.templates.invoice.presets; + +import com.demcha.compose.document.templates.data.invoice.InvoiceBrand; +import com.demcha.compose.document.templates.data.invoice.InvoiceContactBlock; +import com.demcha.compose.document.templates.data.invoice.InvoiceMasthead; +import com.demcha.compose.document.templates.data.invoice.InvoiceNotesBlock; +import com.demcha.compose.document.templates.data.invoice.InvoicePaymentBlock; +import com.demcha.compose.document.templates.data.invoice.InvoiceRecipient; +import com.demcha.compose.document.templates.data.invoice.InvoiceServiceLines; +import com.demcha.compose.document.templates.data.invoice.InvoiceTotalsBlock; +import com.demcha.compose.document.templates.data.invoice.StructuredInvoiceData; +import com.demcha.compose.document.templates.data.invoice.StructuredInvoiceDocumentSpec; + +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.List; + +/** + * Shared fixture data for the {@link LumaStudioInvoice} gates — the SAME + * specs feed the pixel parity test and the layout snapshot test, so a + * geometry shift the pixel budget absorbs still trips the exact snapshot, + * and vice versa. + */ +final class LumaStudioInvoiceFixtures { + + /** The break the sheet stacks its multi-line notes on. */ + private static final String NEWLINE = String.valueOf((char) 10); + + private LumaStudioInvoiceFixtures() { + } + + /** + * Canonical studio invoice — five priced service lines on one page, + * exercising the sidebar lockup, both party blocks, the VAT column, the + * total-due band, the notes and payment pair, and the sign-off band. + */ + static StructuredInvoiceDocumentSpec canonicalInvoice() { + return StructuredInvoiceDocumentSpec.from(base().build()); + } + + /** + * Overflow studio invoice — the same sheet billed phase by phase, so the + * table paginates over three pages and the repeated header, the per-page + * chrome, a middle page carrying nothing but table, and the whole-block + * closing all become part of the frozen contract. No manual page breaks. + */ + static StructuredInvoiceDocumentSpec overflowInvoice() { + List lines = new ArrayList<>(); + for (int phase = 1; phase <= 4; phase++) { + for (InvoiceServiceLines.Line line : serviceLines().lines()) { + lines.add(new InvoiceServiceLines.Line(lines.size() + 1, line.title(), + line.description() + " Phase " + phase + ".", line.servicePeriod(), + line.quantity(), line.unit(), line.unitPrice(), line.amount(), + line.vatRate())); + } + } + return StructuredInvoiceDocumentSpec.from(base() + .serviceLines(new InvoiceServiceLines(serviceLines().columns(), lines)) + .build()); + } + + private static StructuredInvoiceData.Builder base() { + return StructuredInvoiceData.builder() + .brand(new InvoiceBrand(null, "LUMA & CO. STUDIO", null, + "Branding. Design. Digital.", "L", "&Co.")) + .supplier(new InvoiceContactBlock("LUMA & CO. STUDIO", + List.of("Studio 3.02, The Loom", "14 Gower Street", + "London WC1E 6BT", "United Kingdom"), + "+44 (0)20 7946 0832", "hello@lumaandco.studio", + "www.lumaandco.studio", + "Company No.", "12578934", "VAT No.", "369 4567 89")) + .masthead(new InvoiceMasthead("INVOICE", List.of( + new InvoiceMasthead.Entry("INVOICE NO.", "INV-2024-0587", false), + new InvoiceMasthead.Entry("ISSUE DATE", "20 May 2024", false), + new InvoiceMasthead.Entry("DUE DATE", "19 June 2024", false), + new InvoiceMasthead.Entry("PAYMENT TERMS", "30 Days", false), + new InvoiceMasthead.Entry("CURRENCY", "GBP", false)))) + .billTo(new InvoiceRecipient("BILL TO", "Northfield Consulting Ltd", + "Attn: Sarah Mitchell", + List.of("21 Jubilee Way", "London SE1 3SS", "United Kingdom"), "", "")) + .shipTo(new InvoiceRecipient("SHIP TO", "Northfield Consulting Ltd", "", + List.of("The Foundry, 2nd Floor", "17-19 Great Suffolk Street", + "London SE1 0NS", "United Kingdom"), "", "")) + .serviceLines(serviceLines()) + .totals(new InvoiceTotalsBlock(List.of( + new InvoiceTotalsBlock.Row("SUBTOTAL", new BigDecimal("8500.00")), + new InvoiceTotalsBlock.Row("VAT (20%)", new BigDecimal("1700.00"))), + "TOTAL DUE", new BigDecimal("10200.00"))) + .notes(new InvoiceNotesBlock("NOTES", List.of( + "Thank you for your business." + NEWLINE + + "If you have any questions regarding" + NEWLINE + + "this invoice, please get in touch.", + "All work remains the intellectual property" + NEWLINE + + "of LUMA & CO. STUDIO until payment" + NEWLINE + + "has been received in full."), "", "")) + .payment(new InvoicePaymentBlock("PAYMENT DETAILS", List.of( + new InvoicePaymentBlock.Field("BANK", "Starling Bank"), + new InvoicePaymentBlock.Field("SORT CODE", "60-83-71"), + new InvoicePaymentBlock.Field("ACCOUNT NO.", "98765432"), + new InvoicePaymentBlock.Field("IBAN", "GB36 SRLG 6083 7198 7654 32"), + new InvoicePaymentBlock.Field("BIC", "SRLGGB2L")), + "Please make payment by bank transfer to:", + "Payment is due by 19 June 2024.", "", + "LUMA & CO. STUDIO LTD", + "Thank you for choosing LUMA & CO. STUDIO.")) + .currencyCode("GBP"); + } + + private static InvoiceServiceLines serviceLines() { + return new InvoiceServiceLines( + new InvoiceServiceLines.Columns("", "DESCRIPTION", "", "QTY", "UNIT PRICE", + "AMOUNT", "VAT"), + List.of( + line(1, "Brand Strategy Workshop", + "Discovery session, research & brand positioning.", "1200.00"), + line(2, "Visual Identity Design", + "Logo suite, colour palette, typography & guidelines.", "2650.00"), + line(3, "Website Design (Up to 8 pages)", + "UX/UI design for desktop and mobile.", "3200.00"), + line(4, "Copywriting", "Website copy & key messaging.", "850.00"), + line(5, "Project Management", + "Planning, coordination & client liaison.", "600.00"))); + } + + private static InvoiceServiceLines.Line line(int number, String title, String description, + String price) { + return new InvoiceServiceLines.Line(number, title, description, "", BigDecimal.ONE, "", + new BigDecimal(price), new BigDecimal(price), "20%"); + } +} diff --git a/qa/src/test/java/com/demcha/compose/document/templates/invoice/presets/LumaStudioInvoiceLayoutSnapshotTest.java b/qa/src/test/java/com/demcha/compose/document/templates/invoice/presets/LumaStudioInvoiceLayoutSnapshotTest.java new file mode 100644 index 000000000..73077112e --- /dev/null +++ b/qa/src/test/java/com/demcha/compose/document/templates/invoice/presets/LumaStudioInvoiceLayoutSnapshotTest.java @@ -0,0 +1,46 @@ +package com.demcha.compose.document.templates.invoice.presets; + +import com.demcha.compose.GraphCompose; +import com.demcha.compose.document.api.DocumentSession; +import com.demcha.compose.document.templates.TemplateTestSupport; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Exact layout snapshot gate for {@link LumaStudioInvoice} — freezes the + * resolved geometry the pixel budget cannot see (a small column or spacing + * shift stays under the visual-diff budget but changes the snapshot), and + * the pagination contract of the overflow invoice: the service lines flow + * onto further pages while the totals stack and each closing block stay + * whole. + * + *

Refresh with {@code -Dgraphcompose.updateSnapshots=true} after a + * deliberate layout change, and commit the JSON with the change.

+ */ +class LumaStudioInvoiceLayoutSnapshotTest { + + @Test + void canonicalInvoiceMatchesLayoutSnapshot() throws Exception { + try (DocumentSession session = GraphCompose.document().create()) { + LumaStudioInvoice.create().compose( + session, LumaStudioInvoiceFixtures.canonicalInvoice()); + assertThat(session.layoutSnapshot().totalPages()).isEqualTo(1); + TemplateTestSupport.assertCanonicalSnapshot( + session, "luma_studio_invoice_layout", "invoice"); + } + } + + @Test + void overflowInvoicePaginatesOverThreePages() throws Exception { + try (DocumentSession session = GraphCompose.document().create()) { + LumaStudioInvoice.create().compose( + session, LumaStudioInvoiceFixtures.overflowInvoice()); + // Three pages exactly: the table spans the first two, and the + // closing blocks move whole onto the third rather than splitting. + assertThat(session.layoutSnapshot().totalPages()).isEqualTo(3); + TemplateTestSupport.assertCanonicalSnapshot( + session, "luma_studio_invoice_overflow_layout", "invoice"); + } + } +} diff --git a/qa/src/test/java/com/demcha/compose/document/templates/invoice/presets/LumaStudioInvoiceSmokeTest.java b/qa/src/test/java/com/demcha/compose/document/templates/invoice/presets/LumaStudioInvoiceSmokeTest.java new file mode 100644 index 000000000..3ca52c7d8 --- /dev/null +++ b/qa/src/test/java/com/demcha/compose/document/templates/invoice/presets/LumaStudioInvoiceSmokeTest.java @@ -0,0 +1,251 @@ +package com.demcha.compose.document.templates.invoice.presets; + +import com.demcha.compose.GraphCompose; +import com.demcha.compose.document.api.DocumentSession; +import com.demcha.compose.document.snapshot.LayoutNodeSnapshot; +import com.demcha.compose.document.templates.api.DocumentTemplate; +import com.demcha.compose.document.templates.data.invoice.InvoiceContactBlock; +import com.demcha.compose.document.templates.data.invoice.StructuredInvoiceData; +import com.demcha.compose.document.templates.data.invoice.StructuredInvoiceDocumentSpec; +import org.apache.pdfbox.Loader; +import org.apache.pdfbox.pdmodel.PDDocument; +import org.apache.pdfbox.pdmodel.PDPage; +import org.apache.pdfbox.pdmodel.interactive.action.PDActionURI; +import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotation; +import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationLink; +import org.apache.pdfbox.text.PDFTextStripper; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Smoke test for {@link LumaStudioInvoice} — proves the preset renders a + * {@link StructuredInvoiceDocumentSpec} end-to-end with its packaged icon + * set, renders an empty document through its guards, writes the currency + * mark its data's code names, puts the priced figures on the text layer + * where the layout snapshot cannot see inside the table, and carries the + * contact channels as link annotations — which move no pixel and no layout + * node, so neither gate would notice them going missing. + */ +class LumaStudioInvoiceSmokeTest { + + private static byte[] render(StructuredInvoiceDocumentSpec spec) throws Exception { + // The preset owns its page geometry, so the session starts unconfigured. + try (DocumentSession session = GraphCompose.document().create()) { + LumaStudioInvoice.create().compose(session, spec); + assertThat(session.roots()).isNotEmpty(); + byte[] pdfBytes = session.toPdfBytes(); + assertThat(pdfBytes).isNotEmpty(); + return pdfBytes; + } + } + + private static String textOf(byte[] pdfBytes) throws Exception { + try (PDDocument document = Loader.loadPDF(pdfBytes)) { + return new PDFTextStripper().getText(document); + } + } + + /** + * The text with its spaces removed. The sheet's headings and labels are + * letter-spaced runs, so the extractor can report a gap between each pair + * of letters; what is being asserted is the wording, not the tracking. + */ + private static String compact(String text) { + return text.replace(" ", ""); + } + + /** The text as one run, so an assertion need not know where a line broke. */ + private static String unwrapped(String text) { + return text.replace(String.valueOf((char) 13), "") + .replace(String.valueOf((char) 10), " "); + } + + private static List linkTargets(byte[] pdfBytes) throws Exception { + List targets = new ArrayList<>(); + try (PDDocument document = Loader.loadPDF(pdfBytes)) { + for (PDPage page : document.getPages()) { + for (PDAnnotation annotation : page.getAnnotations()) { + if (annotation instanceof PDAnnotationLink link + && link.getAction() instanceof PDActionURI uri) { + targets.add(uri.getURI()); + } + } + } + } + return targets; + } + + @Test + void exposesStableIdentity() { + DocumentTemplate template = LumaStudioInvoice.create(); + assertThat(template.id()).isEqualTo(LumaStudioInvoice.ID); + assertThat(template.displayName()).isEqualTo(LumaStudioInvoice.DISPLAY_NAME); + } + + @Test + void rendersCanonicalInvoiceWithPackagedIcons() throws Exception { + render(LumaStudioInvoiceFixtures.canonicalInvoice()); + } + + @Test + void rendersEmptyInvoice() throws Exception { + // Exercises the empty-collection paths through full layout and render: + // no metadata rows, no service lines, no totals rows, no bank fields, + // no notes, and a brand with neither monogram nor wordmark. + render(StructuredInvoiceDocumentSpec.from(StructuredInvoiceData.builder().build())); + } + + @Test + void canonicalRenderCarriesTheTableTotalsAndBankText() throws Exception { + // The line-items table, the totals bands and the bank rows are leaf + // nodes in the layout snapshot, so their content is asserted here. + String text = textOf(render(LumaStudioInvoiceFixtures.canonicalInvoice())); + assertThat(text) + .contains("Brand Strategy Workshop") + .contains("1,200.00") + .contains("10,200.00") + .contains("GB36 SRLG 6083 7198 7654 32") + .contains("Starling Bank"); + // The description wraps inside its cell, so it is asserted unwrapped. + assertThat(unwrapped(text)) + .contains("Discovery session, research & brand positioning."); + assertThat(compact(text)) + .contains("TOTALDUE") + .contains("INV-2024-0587") + .contains("PAYMENTDETAILS"); + } + + @Test + void amountsCarryTheMarkOfTheStatedCurrency() throws Exception { + String text = textOf(render(LumaStudioInvoiceFixtures.canonicalInvoice())); + assertThat(text).contains("£10,200.00"); + } + + @Test + void anUnknownCurrencyCodePrintsItselfRatherThanNothing() throws Exception { + StructuredInvoiceData data = rebuild( + LumaStudioInvoiceFixtures.canonicalInvoice().invoice()) + .currencyCode("ZZZ") + .build(); + + String text = textOf(render(StructuredInvoiceDocumentSpec.from(data))); + assertThat(text).contains("ZZZ10,200.00"); + } + + @Test + void aDocumentWithoutACurrencyLeavesTheFiguresBare() throws Exception { + StructuredInvoiceData data = rebuild( + LumaStudioInvoiceFixtures.canonicalInvoice().invoice()) + .currencyCode("") + .build(); + + String text = textOf(render(StructuredInvoiceDocumentSpec.from(data))); + assertThat(text).contains("10,200.00").doesNotContain("£10,200.00"); + } + + @Test + void contactChannelsAreClickable() throws Exception { + List targets = linkTargets(render(LumaStudioInvoiceFixtures.canonicalInvoice())); + assertThat(targets) + .contains("tel:+442079460832") + .contains("mailto:hello@lumaandco.studio") + .contains("https://www.lumaandco.studio"); + } + + @Test + void aSupplierWithoutRegistrationsPrintsNoDanglingSeparator() throws Exception { + StructuredInvoiceData canonical = + LumaStudioInvoiceFixtures.canonicalInvoice().invoice(); + InvoiceContactBlock supplier = canonical.supplier(); + StructuredInvoiceData data = rebuild(canonical) + .supplier(new InvoiceContactBlock(supplier.legalName(), supplier.addressLines(), + supplier.phone(), supplier.email(), supplier.website(), + "", "", "", "")) + .build(); + + String text = textOf(render(StructuredInvoiceDocumentSpec.from(data))); + assertThat(text).contains("Studio 3.02, The Loom").doesNotContain("|"); + } + + @Test + void aSupplierWithOneRegistrationPrintsNoSeparator() throws Exception { + StructuredInvoiceData canonical = + LumaStudioInvoiceFixtures.canonicalInvoice().invoice(); + InvoiceContactBlock supplier = canonical.supplier(); + StructuredInvoiceData data = rebuild(canonical) + .supplier(new InvoiceContactBlock(supplier.legalName(), supplier.addressLines(), + supplier.phone(), supplier.email(), supplier.website(), + supplier.registrationLabel(), supplier.registrationNumber(), "", "")) + .build(); + + String text = textOf(render(StructuredInvoiceDocumentSpec.from(data))); + assertThat(text).contains("Company No. 12578934").doesNotContain("|"); + } + + @Test + void overflowInvoiceRepeatsTheTableHeaderAndTheFolioOnTheSecondPage() throws Exception { + byte[] pdfBytes = render(LumaStudioInvoiceFixtures.overflowInvoice()); + try (PDDocument document = Loader.loadPDF(pdfBytes)) { + assertThat(document.getNumberOfPages()).isGreaterThan(1); + PDFTextStripper stripper = new PDFTextStripper(); + stripper.setStartPage(2); + stripper.setEndPage(2); + String secondPage = compact(stripper.getText(document)); + assertThat(secondPage).contains("DESCRIPTION").contains("UNITPRICE"); + assertThat(secondPage).contains("Page2of"); + } + } + + @Test + void overflowInvoicePricesEveryLine() throws Exception { + String text = textOf(render(LumaStudioInvoiceFixtures.overflowInvoice())); + int occurrences = text.split("Project Management", -1).length - 1; + assertThat(occurrences).isEqualTo(4); + } + + @Test + void theSignOffLandsOnItsOwnDarkGroundOnTheLastPage() throws Exception { + // The words are white. The dark foot band is a page background pinned + // to the paper's edge, and on a multi-page invoice the flow ends well + // above it — so the sign-off carries its own strip, and this is what + // says the strip is still under the words rather than the page's. + try (DocumentSession session = GraphCompose.document().create()) { + LumaStudioInvoice.create().compose( + session, LumaStudioInvoiceFixtures.overflowInvoice()); + LayoutNodeSnapshot band = node(session, "BannerBand"); + LayoutNodeSnapshot signOff = node(session, "SignOff"); + + assertThat(signOff.startPage()).isEqualTo(band.startPage()); + assertThat(signOff.placementY()).isGreaterThanOrEqualTo(band.placementY()); + assertThat(signOff.placementY() + signOff.placementHeight()) + .isLessThanOrEqualTo(band.placementY() + band.placementHeight()); + } + } + + private static LayoutNodeSnapshot node(DocumentSession session, String entityName) { + return session.layoutSnapshot().nodes().stream() + .filter(candidate -> entityName.equals(candidate.entityName())) + .findFirst() + .orElseThrow(() -> new AssertionError("No node named " + entityName)); + } + + /** Copies every component of a spec so one of them can be replaced. */ + private static StructuredInvoiceData.Builder rebuild(StructuredInvoiceData data) { + return StructuredInvoiceData.builder() + .brand(data.brand()) + .supplier(data.supplier()) + .masthead(data.masthead()) + .billTo(data.billTo()) + .shipTo(data.shipTo()) + .summary(data.summary()) + .serviceLines(data.serviceLines()) + .totals(data.totals()) + .payment(data.payment()) + .notes(data.notes()) + .currencyCode(data.currencyCode()); + } +} diff --git a/qa/src/test/java/com/demcha/compose/document/templates/invoice/presets/LumaStudioInvoiceVisualParityTest.java b/qa/src/test/java/com/demcha/compose/document/templates/invoice/presets/LumaStudioInvoiceVisualParityTest.java new file mode 100644 index 000000000..5f470eb41 --- /dev/null +++ b/qa/src/test/java/com/demcha/compose/document/templates/invoice/presets/LumaStudioInvoiceVisualParityTest.java @@ -0,0 +1,42 @@ +package com.demcha.compose.document.templates.invoice.presets; + +import com.demcha.compose.GraphCompose; +import com.demcha.compose.document.api.DocumentSession; +import com.demcha.compose.testing.visual.PdfVisualRegression; +import org.junit.jupiter.api.Test; + +import java.nio.file.Path; + +/** + * Pixel-diff visual parity gate for {@link LumaStudioInvoice}. Budget and + * tolerance mirror the other structured-invoice parity gates. + * + *

Re-blessing baselines — after a deliberate visual + * change, re-run with {@code -Dgraphcompose.visual.approve=true} and + * commit the updated PNGs with the change. Baselines live under + * {@code src/test/resources/visual-baselines/invoice-v2-layered/}.

+ */ +class LumaStudioInvoiceVisualParityTest { + + private static final Path BASELINE_ROOT = Path.of( + "src", "test", "resources", "visual-baselines", "invoice-v2-layered"); + + private static final long PIXEL_DIFF_BUDGET = 50_000L; + private static final int PER_PIXEL_TOLERANCE = 8; + + @Test + void rendersWithinPixelDiffBudget() throws Exception { + byte[] pdfBytes; + try (DocumentSession document = GraphCompose.document().create()) { + LumaStudioInvoice.create().compose( + document, LumaStudioInvoiceFixtures.canonicalInvoice()); + pdfBytes = document.toPdfBytes(); + } + + PdfVisualRegression.standard() + .baselineRoot(BASELINE_ROOT) + .perPixelTolerance(PER_PIXEL_TOLERANCE) + .mismatchedPixelBudget(PIXEL_DIFF_BUDGET) + .assertMatchesBaseline("luma_studio_invoice", pdfBytes); + } +} diff --git a/qa/src/test/java/com/demcha/compose/document/templates/invoice/presets/MerchantInvoiceFixtures.java b/qa/src/test/java/com/demcha/compose/document/templates/invoice/presets/MerchantInvoiceFixtures.java new file mode 100644 index 000000000..adbe400e5 --- /dev/null +++ b/qa/src/test/java/com/demcha/compose/document/templates/invoice/presets/MerchantInvoiceFixtures.java @@ -0,0 +1,109 @@ +package com.demcha.compose.document.templates.invoice.presets; + +import com.demcha.compose.document.templates.data.invoice.InvoiceBrand; +import com.demcha.compose.document.templates.data.invoice.InvoiceContactBlock; +import com.demcha.compose.document.templates.data.invoice.InvoiceMasthead; +import com.demcha.compose.document.templates.data.invoice.InvoiceNotesBlock; +import com.demcha.compose.document.templates.data.invoice.InvoicePaymentBlock; +import com.demcha.compose.document.templates.data.invoice.InvoiceRecipient; +import com.demcha.compose.document.templates.data.invoice.InvoiceServiceLines; +import com.demcha.compose.document.templates.data.invoice.InvoiceTotalsBlock; +import com.demcha.compose.document.templates.data.invoice.StructuredInvoiceData; + +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.List; + +/** + * The one invoice the Merchant preset's tests render. + * + *

Both gates read it: the exact layout snapshot and the pixel baseline. One + * fixture behind both is what makes the two comparable — a shift the snapshot + * catches is a shift in the same document the baseline was recorded from.

+ * + *

One of its lines counts nothing, because a fee charged as it falls rather + * than per unit is what the design's dash is for and a fixture without one would + * not exercise it.

+ */ +final class MerchantInvoiceFixtures { + + private MerchantInvoiceFixtures() { + } + + /** A month of commerce-platform subscription, billed to one merchant. */ + static StructuredInvoiceData invoice() { + return StructuredInvoiceData.builder() + .brand(new InvoiceBrand(null, "Coastline", "", "", "C", "")) + .supplier(new InvoiceContactBlock( + "Coastline Commerce Inc.", + List.of("150 Elgin Street, 8th Floor", "Ottawa, ON K2P 1L4", "Canada"), + "+1 613-555-0142", + "billing@coastline.example", + "www.coastline.example", + "", "", + "VAT ID:", "CA 813 9020 285 RT0001")) + .masthead(new InvoiceMasthead("INVOICE", List.of( + new InvoiceMasthead.Entry("Invoice Number:", "INV-2026-000789", true), + new InvoiceMasthead.Entry("Invoice Date:", "27 May 2026", false), + new InvoiceMasthead.Entry("Due Date:", "26 June 2026", false), + new InvoiceMasthead.Entry("Billing Period:", "01 May 2026 – 31 May 2026", false), + new InvoiceMasthead.Entry("Payment Terms:", "Net 30 Days", false), + new InvoiceMasthead.Entry("Currency:", "USD", false)))) + .billTo(new InvoiceRecipient("BILL TO", "Bright Future Ltd.", "", + List.of("45 King Street", "Manchester, M2 4WU", "United Kingdom"), + "", "", "VAT ID:", "GB 987 6543 21")) + .shipTo(new InvoiceRecipient("SHIP TO", "Bright Future Ltd.", "", + List.of("Warehouse 7", "North Industrial Estate", + "Manchester, M2 4WU", "United Kingdom"), + "", "", "", "")) + .serviceLines(new InvoiceServiceLines( + new InvoiceServiceLines.Columns("", "DESCRIPTION", "PLAN / SERVICE", + "QTY", "UNIT PRICE", "AMOUNT"), + lines())) + .totals(new InvoiceTotalsBlock(List.of( + new InvoiceTotalsBlock.Row("Subtotal", new BigDecimal("444.00")), + new InvoiceTotalsBlock.Row("Tax (0%)", new BigDecimal("0.00"))), + "TOTAL DUE", new BigDecimal("444.00"))) + .payment(new InvoicePaymentBlock("PAYMENT DETAILS", List.of( + new InvoicePaymentBlock.Field("Bank Name:", "Harbour Bank of Canada"), + new InvoicePaymentBlock.Field("Account Name:", "Coastline Commerce Inc."), + new InvoicePaymentBlock.Field("Account Number:", "1001-4267-153"), + new InvoicePaymentBlock.Field("SWIFT Code:", "HRBRCAT2"), + new InvoicePaymentBlock.Field("Institution Number:", "003"), + new InvoicePaymentBlock.Field("Transit Number:", "00016"), + new InvoicePaymentBlock.Field("Account Type:", "CAD Account"), + new InvoicePaymentBlock.Field("Remittance Email:", + "billing@coastline.example"), + new InvoicePaymentBlock.Field("Reference:", "INV-2026-000789")), + "Please include the invoice number\nin your payment reference.", + "PAYMENT DUE BY", "26 June 2026", "", "")) + .notes(new InvoiceNotesBlock("Thank you for choosing Coastline.", + List.of("If you have any questions about this invoice," + + " please contact us at billing@coastline.example."), + "billing@coastline.example", "")) + .currencyCode("USD") + .build(); + } + + private static List lines() { + List lines = new ArrayList<>(); + lines.add(line(1, "Coastline Advanced", "Monthly subscription", "Advanced Plan", + "1", "399.00", "399.00", "bag")); + // Charged as it falls rather than per unit, which is what the dash is for. + lines.add(line(2, "Payment Processing", "Transaction fee", "As per usage", + "0", "0.00", "0.00", "tag")); + lines.add(line(3, "Additional Staff Accounts", "Staff account", "Additional", + "3", "15.00", "45.00", "globe")); + lines.add(line(4, "Priority Support", "24/7 support", "Included", + "1", "0.00", "0.00", "support")); + return lines; + } + + private static InvoiceServiceLines.Line line(int number, String title, String subtitle, + String plan, String quantity, + String unitPrice, String amount, String icon) { + return new InvoiceServiceLines.Line(number, title, subtitle, plan, + new BigDecimal(quantity), "", new BigDecimal(unitPrice), + new BigDecimal(amount), "", icon, ""); + } +} diff --git a/qa/src/test/java/com/demcha/compose/document/templates/invoice/presets/MerchantInvoiceLayoutSnapshotTest.java b/qa/src/test/java/com/demcha/compose/document/templates/invoice/presets/MerchantInvoiceLayoutSnapshotTest.java new file mode 100644 index 000000000..2514204b9 --- /dev/null +++ b/qa/src/test/java/com/demcha/compose/document/templates/invoice/presets/MerchantInvoiceLayoutSnapshotTest.java @@ -0,0 +1,21 @@ +package com.demcha.compose.document.templates.invoice.presets; + +import com.demcha.compose.GraphCompose; +import com.demcha.compose.document.api.DocumentSession; +import com.demcha.compose.document.templates.TemplateTestSupport; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +class MerchantInvoiceLayoutSnapshotTest { + + @Test + void theInvoiceMatchesItsLayoutSnapshot() throws Exception { + try (DocumentSession session = GraphCompose.document().create()) { + MerchantInvoice.create().compose(session, MerchantInvoiceFixtures.invoice()); + assertThat(session.layoutSnapshot().totalPages()).as("total pages").isEqualTo(1); + TemplateTestSupport.assertCanonicalSnapshot( + session, "merchant_invoice_layout", "invoice"); + } + } +} diff --git a/qa/src/test/java/com/demcha/compose/document/templates/invoice/presets/MerchantInvoiceSmokeTest.java b/qa/src/test/java/com/demcha/compose/document/templates/invoice/presets/MerchantInvoiceSmokeTest.java new file mode 100644 index 000000000..076c4b1d0 --- /dev/null +++ b/qa/src/test/java/com/demcha/compose/document/templates/invoice/presets/MerchantInvoiceSmokeTest.java @@ -0,0 +1,191 @@ +package com.demcha.compose.document.templates.invoice.presets; + +import com.demcha.compose.GraphCompose; +import com.demcha.compose.document.api.DocumentSession; +import com.demcha.compose.document.templates.data.invoice.InvoiceServiceLines; +import com.demcha.compose.document.templates.data.invoice.StructuredInvoiceData; +import org.apache.pdfbox.Loader; +import org.apache.pdfbox.pdmodel.PDDocument; +import org.apache.pdfbox.pdmodel.PDPage; +import org.apache.pdfbox.pdmodel.interactive.action.PDActionURI; +import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotation; +import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationLink; +import org.apache.pdfbox.text.PDFTextStripper; +import org.junit.jupiter.api.Test; + +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class MerchantInvoiceSmokeTest { + + // -- what the sheet says ------------------------------------------- + + @Test + void theSheetCarriesTheIssuerTheCustomerAndEveryLine() throws Exception { + String text = textOf(render(MerchantInvoiceFixtures.invoice())); + assertThat(text) + .contains("Coastline Commerce Inc.") + .contains("Bright Future Ltd.") + .contains("Coastline Advanced") + .contains("Priority Support") + .contains("TOTAL DUE"); + } + + @Test + void theCurrencyIsStatedInEachMoneyCaptionAndOnceMoreOnTheTotal() throws Exception { + String text = textOf(render(MerchantInvoiceFixtures.invoice())); + assertThat(text).contains("UNIT PRICE (USD)").contains("AMOUNT (USD)"); + // The grand total stands under no caption, so it names its own currency. + assertThat(text).contains("USD 444.00"); + // The figures under the captions are bare. + assertThat(text).contains("399.00").doesNotContain("USD 399.00"); + } + + @Test + void aLineThatCountsNothingPrintsADashRatherThanAZero() throws Exception { + String text = textOf(render(MerchantInvoiceFixtures.invoice())); + // A zero in a quantity column reads as none delivered rather than as not + // counted, which is a different thing to tell a reader. + assertThat(text).contains("—"); + } + + // -- what a reader can act on -------------------------------------- + + @Test + void everyAddressTheSheetPrintsIsReachable() throws Exception { + List targets = linkTargets(render(MerchantInvoiceFixtures.invoice())); + assertThat(targets) + .contains("mailto:billing@coastline.example") + // A site printed without a scheme still needs one to open. + .contains("https://www.coastline.example") + .contains("tel:+16135550142"); + } + + @Test + void theClosingNoteLinksTheAddressInsideItsSentence() throws Exception { + byte[] pdf = render(MerchantInvoiceFixtures.invoice()); + assertThat(textOf(pdf)).contains("If you have any questions about this invoice"); + assertThat(linkTargets(pdf)) + .filteredOn(t -> t.equals("mailto:billing@coastline.example")) + // Once in the supplier block, once in the payment panel, once in + // the note. + .hasSizeGreaterThanOrEqualTo(3); + } + + // -- what the document chooses, and what it may not ---------------- + + @Test + void aLineThatNamesNoMarkStillRenders() throws Exception { + StructuredInvoiceData data = withLines(new InvoiceServiceLines( + MerchantInvoiceFixtures.invoice().serviceLines().columns(), + List.of(line("Coastline Advanced", "")))); + assertThat(textOf(render(data))).contains("Coastline Advanced"); + } + + @Test + void anUnknownMarkIsReportedAsADataErrorNamingTheSet() { + StructuredInvoiceData data = withLines(new InvoiceServiceLines( + MerchantInvoiceFixtures.invoice().serviceLines().columns(), + List.of(line("Coastline Advanced", "rocket")))); + assertThatThrownBy(() -> render(data)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("rocket") + .hasMessageContaining("bag"); + } + + @Test + void anInvoiceThatShipsNowhereLeavesTheColumnEmptyRatherThanFailing() throws Exception { + StructuredInvoiceData base = MerchantInvoiceFixtures.invoice(); + StructuredInvoiceData data = StructuredInvoiceData.builder() + .brand(base.brand()).supplier(base.supplier()).masthead(base.masthead()) + .billTo(base.billTo()) + .serviceLines(base.serviceLines()).totals(base.totals()) + .payment(base.payment()).notes(base.notes()) + .currencyCode(base.currencyCode()).build(); + String text = textOf(render(data)); + assertThat(text).contains("BILL TO").doesNotContain("SHIP TO"); + } + + // -- how it flows --------------------------------------------------- + + @Test + void theDesignsOwnLineCountIsOneSheet() throws Exception { + assertThat(pagesOf(MerchantInvoiceFixtures.invoice())).isEqualTo(1); + } + + @Test + void enoughLinesPaginateAndTheHeaderComesBackWithThem() throws Exception { + List many = new ArrayList<>(); + for (int i = 1; i <= 30; i++) { + many.add(line("Coastline Advanced " + i, "bag")); + } + StructuredInvoiceData data = withLines(new InvoiceServiceLines( + MerchantInvoiceFixtures.invoice().serviceLines().columns(), many)); + assertThat(pagesOf(data)).isGreaterThan(1); + + String text = textOf(render(data)); + // The caps header is repeated, so it appears once per page the table reaches. + assertThat(text.split("PLAN / SERVICE", -1).length - 1).isGreaterThan(1); + } + + // -- helpers --------------------------------------------------------- + + private static InvoiceServiceLines.Line line(String title, String icon) { + return new InvoiceServiceLines.Line(1, title, "Monthly subscription", "Advanced Plan", + BigDecimal.ONE, "", new BigDecimal("399.00"), + new BigDecimal("399.00"), "", icon, ""); + } + + private static byte[] render(StructuredInvoiceData data) throws Exception { + // The preset owns its page geometry, so the session starts unconfigured. + try (DocumentSession session = GraphCompose.document().create()) { + MerchantInvoice.create().compose(session, data); + assertThat(session.roots()).isNotEmpty(); + byte[] pdfBytes = session.toPdfBytes(); + assertThat(pdfBytes).isNotEmpty(); + return pdfBytes; + } + } + + private static String textOf(byte[] pdfBytes) throws Exception { + try (PDDocument document = Loader.loadPDF(pdfBytes)) { + return new PDFTextStripper().getText(document); + } + } + + private static int pagesOf(StructuredInvoiceData data) throws Exception { + try (DocumentSession session = GraphCompose.document().create()) { + MerchantInvoice.create().compose(session, data); + return session.layoutSnapshot().totalPages(); + } + } + + private static List linkTargets(byte[] pdfBytes) throws Exception { + List targets = new ArrayList<>(); + try (PDDocument document = Loader.loadPDF(pdfBytes)) { + for (PDPage page : document.getPages()) { + for (PDAnnotation annotation : page.getAnnotations()) { + if (annotation instanceof PDAnnotationLink link + && link.getAction() instanceof PDActionURI uri) { + targets.add(uri.getURI()); + } + } + } + } + return targets; + } + + private static StructuredInvoiceData withLines(InvoiceServiceLines lines) { + StructuredInvoiceData base = MerchantInvoiceFixtures.invoice(); + return StructuredInvoiceData.builder() + .brand(base.brand()).supplier(base.supplier()).masthead(base.masthead()) + .billTo(base.billTo()).shipTo(base.shipTo()) + .serviceLines(lines).totals(base.totals()) + .payment(base.payment()).notes(base.notes()) + .currencyCode(base.currencyCode()).build(); + } +} diff --git a/qa/src/test/java/com/demcha/compose/document/templates/invoice/presets/MerchantInvoiceVisualParityTest.java b/qa/src/test/java/com/demcha/compose/document/templates/invoice/presets/MerchantInvoiceVisualParityTest.java new file mode 100644 index 000000000..7494319e8 --- /dev/null +++ b/qa/src/test/java/com/demcha/compose/document/templates/invoice/presets/MerchantInvoiceVisualParityTest.java @@ -0,0 +1,32 @@ +package com.demcha.compose.document.templates.invoice.presets; + +import com.demcha.compose.GraphCompose; +import com.demcha.compose.document.api.DocumentSession; +import com.demcha.compose.testing.visual.PdfVisualRegression; +import org.junit.jupiter.api.Test; + +import java.nio.file.Path; + +class MerchantInvoiceVisualParityTest { + + private static final Path BASELINE_ROOT = Path.of( + "src", "test", "resources", "visual-baselines", "invoice-v2-layered"); + + private static final long PIXEL_DIFF_BUDGET = 50_000L; + private static final int PER_PIXEL_TOLERANCE = 8; + + @Test + void rendersWithinPixelDiffBudget() throws Exception { + byte[] pdfBytes; + try (DocumentSession document = GraphCompose.document().create()) { + MerchantInvoice.create().compose(document, MerchantInvoiceFixtures.invoice()); + pdfBytes = document.toPdfBytes(); + } + + PdfVisualRegression.standard() + .baselineRoot(BASELINE_ROOT) + .perPixelTolerance(PER_PIXEL_TOLERANCE) + .mismatchedPixelBudget(PIXEL_DIFF_BUDGET) + .assertMatchesBaseline("merchant_invoice", pdfBytes); + } +} diff --git a/qa/src/test/java/com/demcha/compose/document/templates/invoice/presets/MeteredInvoiceFixtures.java b/qa/src/test/java/com/demcha/compose/document/templates/invoice/presets/MeteredInvoiceFixtures.java new file mode 100644 index 000000000..6b61f5759 --- /dev/null +++ b/qa/src/test/java/com/demcha/compose/document/templates/invoice/presets/MeteredInvoiceFixtures.java @@ -0,0 +1,107 @@ +package com.demcha.compose.document.templates.invoice.presets; + +import com.demcha.compose.document.templates.data.invoice.InvoiceBrand; +import com.demcha.compose.document.templates.data.invoice.InvoiceContactBlock; +import com.demcha.compose.document.templates.data.invoice.InvoiceMasthead; +import com.demcha.compose.document.templates.data.invoice.InvoiceNotesBlock; +import com.demcha.compose.document.templates.data.invoice.InvoicePaymentBlock; +import com.demcha.compose.document.templates.data.invoice.InvoiceRecipient; +import com.demcha.compose.document.templates.data.invoice.InvoiceServiceLines; +import com.demcha.compose.document.templates.data.invoice.InvoiceTotalsBlock; +import com.demcha.compose.document.templates.data.invoice.StructuredInvoiceData; + +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.List; + +/** + * The one invoice the Metered preset's tests render. + * + *

Both gates read it: the exact layout snapshot and the pixel baseline. One + * fixture behind both is what makes the two comparable — a shift the snapshot + * catches is a shift in the same document the baseline was recorded from.

+ */ +final class MeteredInvoiceFixtures { + + private MeteredInvoiceFixtures() { + } + + /** A month of metered platform usage, billed to one customer. */ + static StructuredInvoiceData invoice() { + return StructuredInvoiceData.builder() + .brand(new InvoiceBrand(null, "Northwind Cloud", "", "", "", "")) + .supplier(new InvoiceContactBlock( + "Northwind Cloud Services Ltd.", + List.of("120 Riverside Way", "Bristol, BS1 6QT", "United Kingdom"), + "+44 (0)117 496 2280", + "billing@northwind.example", + "https://northwind.example", + "", "", + "VAT ID:", "GB 412 8830 27", + "Northwind Cloud Services Ltd. is a subsidiary of Northwind Group plc.")) + .masthead(new InvoiceMasthead("INVOICE", List.of( + new InvoiceMasthead.Entry("Invoice Number:", "INV-2026-00041882", true), + new InvoiceMasthead.Entry("Invoice Date:", "31 July 2026", false), + new InvoiceMasthead.Entry("Due Date:", "30 August 2026", false), + new InvoiceMasthead.Entry("Bill Period:", "01 Jul 2026 – 31 Jul 2026", false), + new InvoiceMasthead.Entry("Payment Terms:", "Net 30 Days", false), + new InvoiceMasthead.Entry("Currency:", "GBP", false)))) + .billTo(new InvoiceRecipient("BILL TO", "Bright Future Ltd.", "", + List.of("45 King Street", "Manchester, M2 4WU", "United Kingdom"), + "", "", "VAT ID:", "GB 987 6543 21")) + .shipTo(new InvoiceRecipient("SHIP TO", "Bright Future Ltd.", "", + List.of("Warehouse 7", "North Industrial Estate", + "Manchester, M2 4WU", "United Kingdom"), + "", "", "", "")) + .serviceLines(new InvoiceServiceLines( + new InvoiceServiceLines.Columns("", "DESCRIPTION", "SERVICE", + "QTY", "UNIT PRICE", "AMOUNT"), + lines())) + .totals(new InvoiceTotalsBlock(List.of( + new InvoiceTotalsBlock.Row("Subtotal", new BigDecimal("214.71")), + new InvoiceTotalsBlock.Row("VAT (20%)", new BigDecimal("42.94"))), + "TOTAL DUE", new BigDecimal("257.65"))) + .payment(new InvoicePaymentBlock("PAYMENT DETAILS", List.of( + new InvoicePaymentBlock.Field("Account Name:", + "Northwind Cloud Services Ltd."), + new InvoicePaymentBlock.Field("Bank Name:", "Riverbank plc"), + new InvoicePaymentBlock.Field("Account Number:", "60415523"), + new InvoicePaymentBlock.Field("Sort Code:", "20-41-08"), + new InvoicePaymentBlock.Field("IBAN:", "GB29 RIVB 2041 0860 4155 23"), + new InvoicePaymentBlock.Field("Account Type:", "Business Current"), + new InvoicePaymentBlock.Field("Remittance Email:", + "billing@northwind.example")), + "", "PAYMENT DUE BY", "30 August 2026", "", "")) + .notes(new InvoiceNotesBlock( + "Thank you for running on Northwind Cloud.", + List.of("For billing and account support, visit" + + " https://northwind.example or write to" + + " billing@northwind.example."), + "billing@northwind.example", "")) + .currencyCode("GBP") + .build(); + } + + private static List lines() { + List lines = new ArrayList<>(); + lines.add(line(1, "Compute Instances", "On-demand usage", "London (eu-west)", + "744", "Hrs", "0.0710", "52.82", "compute")); + lines.add(line(2, "Object Storage", "Stored volume", "London (eu-west)", + "480", "GB", "0.0215", "10.32", "storage")); + lines.add(line(3, "Managed Database", "Primary and replica", "London (eu-west)", + "744", "Hrs", "0.1650", "122.76", "database")); + lines.add(line(4, "Egress Traffic", "Data transfer out", "Global", + "310", "GB", "0.0900", "27.90", "transfer")); + lines.add(line(5, "Support Plan", "Standard response", "Global", + "1", "", "0.91", "0.91", "support")); + return lines; + } + + private static InvoiceServiceLines.Line line(int number, String title, String description, + String service, String quantity, String unit, + String unitPrice, String amount, String icon) { + return new InvoiceServiceLines.Line(number, title, description, service, + new BigDecimal(quantity), unit, new BigDecimal(unitPrice), + new BigDecimal(amount), "", icon); + } +} diff --git a/qa/src/test/java/com/demcha/compose/document/templates/invoice/presets/MeteredInvoiceLayoutSnapshotTest.java b/qa/src/test/java/com/demcha/compose/document/templates/invoice/presets/MeteredInvoiceLayoutSnapshotTest.java new file mode 100644 index 000000000..d0a8bc4a8 --- /dev/null +++ b/qa/src/test/java/com/demcha/compose/document/templates/invoice/presets/MeteredInvoiceLayoutSnapshotTest.java @@ -0,0 +1,21 @@ +package com.demcha.compose.document.templates.invoice.presets; + +import com.demcha.compose.GraphCompose; +import com.demcha.compose.document.api.DocumentSession; +import com.demcha.compose.document.templates.TemplateTestSupport; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +class MeteredInvoiceLayoutSnapshotTest { + + @Test + void theInvoiceMatchesItsLayoutSnapshot() throws Exception { + try (DocumentSession session = GraphCompose.document().create()) { + MeteredInvoice.create().compose(session, MeteredInvoiceFixtures.invoice()); + assertThat(session.layoutSnapshot().totalPages()).as("total pages").isEqualTo(1); + TemplateTestSupport.assertCanonicalSnapshot( + session, "metered_invoice_layout", "invoice"); + } + } +} diff --git a/qa/src/test/java/com/demcha/compose/document/templates/invoice/presets/MeteredInvoiceSmokeTest.java b/qa/src/test/java/com/demcha/compose/document/templates/invoice/presets/MeteredInvoiceSmokeTest.java new file mode 100644 index 000000000..16dab1816 --- /dev/null +++ b/qa/src/test/java/com/demcha/compose/document/templates/invoice/presets/MeteredInvoiceSmokeTest.java @@ -0,0 +1,221 @@ +package com.demcha.compose.document.templates.invoice.presets; + +import com.demcha.compose.GraphCompose; +import com.demcha.compose.document.api.DocumentSession; +import com.demcha.compose.document.templates.data.invoice.InvoiceContactBlock; +import com.demcha.compose.document.templates.data.invoice.InvoiceServiceLines; +import com.demcha.compose.document.templates.data.invoice.StructuredInvoiceData; +import org.apache.pdfbox.Loader; +import org.apache.pdfbox.pdmodel.PDDocument; +import org.apache.pdfbox.pdmodel.PDPage; +import org.apache.pdfbox.pdmodel.interactive.action.PDActionURI; +import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotation; +import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationLink; +import org.apache.pdfbox.text.PDFTextStripper; +import org.junit.jupiter.api.Test; + +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class MeteredInvoiceSmokeTest { + + // -- what the sheet says ------------------------------------------- + + @Test + void theSheetCarriesTheIssuerTheCustomerAndEveryLine() throws Exception { + String text = textOf(render(MeteredInvoiceFixtures.invoice())); + assertThat(text) + .contains("Northwind Cloud Services Ltd.") + .contains("Bright Future Ltd.") + .contains("Compute Instances") + .contains("Managed Database") + .contains("Support Plan") + .contains("TOTAL DUE"); + } + + @Test + void aRateKeepsThePrecisionItIsQuotedAtAndAnAmountDoesNot() throws Exception { + String text = textOf(render(MeteredInvoiceFixtures.invoice())); + // 0.0710 rounded to an amount's two places is 0.07 — a different price + // that multiplies out to a different bill. + assertThat(text).contains("GBP 0.0710").contains("GBP 0.0215"); + // The support line is quoted at two places and stays there. + assertThat(text).contains("GBP 0.91"); + // Amounts and totals are written at two throughout. + assertThat(text).contains("GBP 122.76").contains("GBP 257.65"); + } + + @Test + void aCountIsWrittenWithTheThingItCounts() throws Exception { + String text = textOf(render(MeteredInvoiceFixtures.invoice())); + assertThat(text).contains("744 Hrs").contains("480 GB"); + // The support line counts a plan, which has no unit. + assertThat(text).contains("Support Plan"); + } + + @Test + void theBandUnderTheSheetCarriesTheDisclosureTheSupplierStates() throws Exception { + String text = textOf(render(MeteredInvoiceFixtures.invoice())); + assertThat(text).contains("is a subsidiary of Northwind Group plc."); + } + + @Test + void aSupplierThatStatesNoDisclosureIsIdentifiedByItsNameInstead() throws Exception { + StructuredInvoiceData data = MeteredInvoiceFixtures.invoice(); + InvoiceContactBlock plain = new InvoiceContactBlock( + data.supplier().legalName(), data.supplier().addressLines(), + data.supplier().phone(), data.supplier().email(), data.supplier().website(), + "", "", data.supplier().taxRegistrationLabel(), + data.supplier().taxRegistrationNumber()); + String text = textOf(render(StructuredInvoiceData.builder() + .brand(data.brand()).supplier(plain).masthead(data.masthead()) + .billTo(data.billTo()).shipTo(data.shipTo()) + .serviceLines(data.serviceLines()).totals(data.totals()) + .payment(data.payment()).notes(data.notes()) + .currencyCode(data.currencyCode()).build())); + assertThat(text).contains("Northwind Cloud Services Ltd.") + .doesNotContain("is a subsidiary of"); + } + + // -- what a reader can act on -------------------------------------- + + @Test + void everyAddressTheSheetPrintsIsReachable() throws Exception { + List targets = linkTargets(render(MeteredInvoiceFixtures.invoice())); + assertThat(targets) + .contains("mailto:billing@northwind.example") + .contains("https://northwind.example") + // The trunk prefix goes: it is the digit a caller omits from abroad. + .contains("tel:+441174962280"); + } + + @Test + void theNotesProseLinksTheAddressesInsideItAndNotTheSentenceAroundThem() + throws Exception { + byte[] pdf = render(MeteredInvoiceFixtures.invoice()); + assertThat(linkTargets(pdf)) + .filteredOn(target -> target.startsWith("mailto:")) + .isNotEmpty(); + // The sentence still reads as one sentence. + assertThat(textOf(pdf)) + .contains("For billing and account support, visit"); + } + + // -- what the document chooses, and what it may not ---------------- + + @Test + void aLineDrawsTheMarkItNames() throws Exception { + String text = textOf(render(MeteredInvoiceFixtures.invoice())); + // A mark leaves no glyph, so the render succeeding on every packaged + // token is the assertion; an unknown one is rejected below. + assertThat(text).contains("Egress Traffic"); + } + + @Test + void aLineThatNamesNoMarkStillRenders() throws Exception { + StructuredInvoiceData data = withLines(new InvoiceServiceLines( + MeteredInvoiceFixtures.invoice().serviceLines().columns(), + List.of(new InvoiceServiceLines.Line(1, "Compute Instances", "On-demand usage", + "London (eu-west)", new BigDecimal("744"), "Hrs", + new BigDecimal("0.0710"), new BigDecimal("52.82"), "", "")))); + assertThat(textOf(render(data))).contains("Compute Instances"); + } + + @Test + void anUnknownMarkIsReportedAsADataErrorNamingTheSet() { + StructuredInvoiceData data = withLines(new InvoiceServiceLines( + MeteredInvoiceFixtures.invoice().serviceLines().columns(), + List.of(new InvoiceServiceLines.Line(1, "Compute Instances", "On-demand usage", + "London (eu-west)", new BigDecimal("744"), "Hrs", + new BigDecimal("0.0710"), new BigDecimal("52.82"), "", "rocket")))); + assertThatThrownBy(() -> render(data)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("rocket") + .hasMessageContaining("compute"); + } + + // -- how it flows --------------------------------------------------- + + @Test + void theDesignsOwnLineCountIsOneSheet() throws Exception { + assertThat(pagesOf(MeteredInvoiceFixtures.invoice())).isEqualTo(1); + } + + @Test + void enoughLinesPaginateAndTheHeaderComesBackWithThem() throws Exception { + List many = new ArrayList<>(); + for (int i = 1; i <= 40; i++) { + many.add(new InvoiceServiceLines.Line(i, "Compute Instances " + i, + "On-demand usage", "London (eu-west)", new BigDecimal("744"), "Hrs", + new BigDecimal("0.0710"), new BigDecimal("52.82"), "", "compute")); + } + StructuredInvoiceData data = withLines(new InvoiceServiceLines( + MeteredInvoiceFixtures.invoice().serviceLines().columns(), many)); + assertThat(pagesOf(data)).isGreaterThan(1); + + String text = textOf(render(data)); + // The caps header is repeated, so it appears once per page the table reaches. + assertThat(text.split("UNIT PRICE", -1).length - 1).isGreaterThan(1); + } + + // -- helpers --------------------------------------------------------- + + private static byte[] render(StructuredInvoiceData data) throws Exception { + // The preset owns its page geometry, so the session starts unconfigured. + try (DocumentSession session = GraphCompose.document().create()) { + MeteredInvoice.create().compose(session, data); + assertThat(session.roots()).isNotEmpty(); + byte[] pdfBytes = session.toPdfBytes(); + assertThat(pdfBytes).isNotEmpty(); + return pdfBytes; + } + } + + private static String textOf(byte[] pdfBytes) throws Exception { + try (PDDocument document = Loader.loadPDF(pdfBytes)) { + return new PDFTextStripper().getText(document); + } + } + + private static int pagesOf(StructuredInvoiceData data) throws Exception { + try (DocumentSession session = GraphCompose.document().create()) { + MeteredInvoice.create().compose(session, data); + return session.layoutSnapshot().totalPages(); + } + } + + private static List linkTargets(byte[] pdfBytes) throws Exception { + List targets = new ArrayList<>(); + try (PDDocument document = Loader.loadPDF(pdfBytes)) { + for (PDPage page : document.getPages()) { + for (PDAnnotation annotation : page.getAnnotations()) { + if (annotation instanceof PDAnnotationLink link + && link.getAction() instanceof PDActionURI uri) { + targets.add(uri.getURI()); + } + } + } + } + return targets; + } + + private static StructuredInvoiceData withLines(InvoiceServiceLines lines) { + StructuredInvoiceData base = MeteredInvoiceFixtures.invoice(); + return StructuredInvoiceData.builder() + .brand(base.brand()) + .supplier(base.supplier()) + .masthead(base.masthead()) + .billTo(base.billTo()) + .shipTo(base.shipTo()) + .serviceLines(lines) + .totals(base.totals()) + .payment(base.payment()) + .notes(base.notes()) + .currencyCode(base.currencyCode()) + .build(); + } +} diff --git a/qa/src/test/java/com/demcha/compose/document/templates/invoice/presets/MeteredInvoiceVisualParityTest.java b/qa/src/test/java/com/demcha/compose/document/templates/invoice/presets/MeteredInvoiceVisualParityTest.java new file mode 100644 index 000000000..067c55ebc --- /dev/null +++ b/qa/src/test/java/com/demcha/compose/document/templates/invoice/presets/MeteredInvoiceVisualParityTest.java @@ -0,0 +1,32 @@ +package com.demcha.compose.document.templates.invoice.presets; + +import com.demcha.compose.GraphCompose; +import com.demcha.compose.document.api.DocumentSession; +import com.demcha.compose.testing.visual.PdfVisualRegression; +import org.junit.jupiter.api.Test; + +import java.nio.file.Path; + +class MeteredInvoiceVisualParityTest { + + private static final Path BASELINE_ROOT = Path.of( + "src", "test", "resources", "visual-baselines", "invoice-v2-layered"); + + private static final long PIXEL_DIFF_BUDGET = 50_000L; + private static final int PER_PIXEL_TOLERANCE = 8; + + @Test + void rendersWithinPixelDiffBudget() throws Exception { + byte[] pdfBytes; + try (DocumentSession document = GraphCompose.document().create()) { + MeteredInvoice.create().compose(document, MeteredInvoiceFixtures.invoice()); + pdfBytes = document.toPdfBytes(); + } + + PdfVisualRegression.standard() + .baselineRoot(BASELINE_ROOT) + .perPixelTolerance(PER_PIXEL_TOLERANCE) + .mismatchedPixelBudget(PIXEL_DIFF_BUDGET) + .assertMatchesBaseline("metered_invoice", pdfBytes); + } +} diff --git a/qa/src/test/java/com/demcha/compose/document/templates/invoice/presets/ObsidianInvoiceFixtures.java b/qa/src/test/java/com/demcha/compose/document/templates/invoice/presets/ObsidianInvoiceFixtures.java new file mode 100644 index 000000000..1be5916dc --- /dev/null +++ b/qa/src/test/java/com/demcha/compose/document/templates/invoice/presets/ObsidianInvoiceFixtures.java @@ -0,0 +1,97 @@ +package com.demcha.compose.document.templates.invoice.presets; + +import com.demcha.compose.document.templates.data.invoice.InvoiceBrand; +import com.demcha.compose.document.templates.data.invoice.InvoiceContactBlock; +import com.demcha.compose.document.templates.data.invoice.InvoiceMasthead; +import com.demcha.compose.document.templates.data.invoice.InvoiceNotesBlock; +import com.demcha.compose.document.templates.data.invoice.InvoicePaymentBlock; +import com.demcha.compose.document.templates.data.invoice.InvoiceRecipient; +import com.demcha.compose.document.templates.data.invoice.InvoiceServiceLines; +import com.demcha.compose.document.templates.data.invoice.InvoiceTotalsBlock; +import com.demcha.compose.document.templates.data.invoice.StructuredInvoiceData; + +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.List; + +/** + * The one invoice the Obsidian preset's tests render. + * + *

Both gates read it: the exact layout snapshot and the pixel baseline. One + * fixture behind both is what makes the two comparable — a shift the snapshot + * catches is a shift in the same document the baseline was recorded from.

+ * + *

Its lines are priced so the tax the preset works out is a round figure per + * line, because that column is derived rather than stated and a fixture that + * hid the arithmetic would not exercise it.

+ */ +final class ObsidianInvoiceFixtures { + + private ObsidianInvoiceFixtures() { + } + + /** A month of business-account services, billed to one customer. */ + static StructuredInvoiceData invoice() { + return StructuredInvoiceData.builder() + .brand(new InvoiceBrand(null, "Kestrel", "", "", "K", "")) + .supplier(new InvoiceContactBlock( + "Kestrel Business", + List.of("7 Harbourside Walk", "Bristol BS1 5TY", "United Kingdom"), + "", "ar@kestrel.example", "", "", "", "", "")) + .masthead(new InvoiceMasthead("INVOICE", List.of( + new InvoiceMasthead.Entry("Invoice number", "INV-2026-1048", false), + new InvoiceMasthead.Entry("Issue date", "27 August 2026", false), + new InvoiceMasthead.Entry("Due date", "10 September 2026", true), + new InvoiceMasthead.Entry("Payment terms", "14 days", false), + new InvoiceMasthead.Entry("Currency", "GBP", false)))) + .billTo(new InvoiceRecipient("Bill to", "Northline Consulting Ltd", "", + List.of("21 Jubilee Way", "London SE1 3SS", "United Kingdom"), + "", "", "", "")) + .serviceLines(new InvoiceServiceLines( + new InvoiceServiceLines.Columns("#", "Description", "", + "Qty", "Unit price", "Amount", "VAT (20%)"), + lines())) + .totals(new InvoiceTotalsBlock(List.of( + new InvoiceTotalsBlock.Row("Subtotal", new BigDecimal("730.00")), + new InvoiceTotalsBlock.Row("VAT (20%)", new BigDecimal("146.00"))), + "Total due", new BigDecimal("876.00"))) + .payment(new InvoicePaymentBlock("Payment details", List.of( + new InvoicePaymentBlock.Field("Payee", "Kestrel Business"), + new InvoicePaymentBlock.Field("Sort code", "04-00-75"), + new InvoicePaymentBlock.Field("Account number", "12345678"), + new InvoicePaymentBlock.Field("Reference", "INV-2026-1048")), + "Please pay by bank transfer\nto the account below.", + "Payment is due by 10 September 2026.", + "10 September 2026", "", "Thank you for your business.")) + .notes(new InvoiceNotesBlock("Notes", List.of( + "Thank you for choosing Kestrel Business.\n" + + "This invoice covers services and features provided\n" + + "to your business as of the date of issue.", + "If you have any questions about this invoice,\n" + + "please contact us at ar@kestrel.example."), + "", "")) + .currencyCode("GBP") + .build(); + } + + private static List lines() { + List lines = new ArrayList<>(); + lines.add(line(1, "Subscription setup", "One-off account and workspace setup", + "150.00", "180.00")); + lines.add(line(2, "International payment support", + "Monthly support for cross-border payments", "250.00", "300.00")); + lines.add(line(3, "Expense management integration", "API access and integration", + "200.00", "240.00")); + lines.add(line(4, "Virtual cards", "50 virtual cards", "100.00", "120.00")); + lines.add(line(5, "Business account maintenance", "Monthly account maintenance fee", + "30.00", "36.00")); + return lines; + } + + private static InvoiceServiceLines.Line line(int number, String title, String subtitle, + String unitPrice, String amount) { + return new InvoiceServiceLines.Line(number, title, subtitle, "", + BigDecimal.ONE, "", new BigDecimal(unitPrice), + new BigDecimal(amount), "", "", ""); + } +} diff --git a/qa/src/test/java/com/demcha/compose/document/templates/invoice/presets/ObsidianInvoiceLayoutSnapshotTest.java b/qa/src/test/java/com/demcha/compose/document/templates/invoice/presets/ObsidianInvoiceLayoutSnapshotTest.java new file mode 100644 index 000000000..eb890c9d7 --- /dev/null +++ b/qa/src/test/java/com/demcha/compose/document/templates/invoice/presets/ObsidianInvoiceLayoutSnapshotTest.java @@ -0,0 +1,21 @@ +package com.demcha.compose.document.templates.invoice.presets; + +import com.demcha.compose.GraphCompose; +import com.demcha.compose.document.api.DocumentSession; +import com.demcha.compose.document.templates.TemplateTestSupport; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +class ObsidianInvoiceLayoutSnapshotTest { + + @Test + void theInvoiceMatchesItsLayoutSnapshot() throws Exception { + try (DocumentSession session = GraphCompose.document().create()) { + ObsidianInvoice.create().compose(session, ObsidianInvoiceFixtures.invoice()); + assertThat(session.layoutSnapshot().totalPages()).as("total pages").isEqualTo(1); + TemplateTestSupport.assertCanonicalSnapshot( + session, "obsidian_invoice_layout", "invoice"); + } + } +} diff --git a/qa/src/test/java/com/demcha/compose/document/templates/invoice/presets/ObsidianInvoiceSmokeTest.java b/qa/src/test/java/com/demcha/compose/document/templates/invoice/presets/ObsidianInvoiceSmokeTest.java new file mode 100644 index 000000000..5e49dd476 --- /dev/null +++ b/qa/src/test/java/com/demcha/compose/document/templates/invoice/presets/ObsidianInvoiceSmokeTest.java @@ -0,0 +1,182 @@ +package com.demcha.compose.document.templates.invoice.presets; + +import com.demcha.compose.GraphCompose; +import com.demcha.compose.document.api.DocumentSession; +import com.demcha.compose.document.templates.data.invoice.InvoiceBrand; +import com.demcha.compose.document.templates.data.invoice.InvoiceServiceLines; +import com.demcha.compose.document.templates.data.invoice.StructuredInvoiceData; +import org.apache.pdfbox.Loader; +import org.apache.pdfbox.pdmodel.PDDocument; +import org.apache.pdfbox.pdmodel.PDPage; +import org.apache.pdfbox.pdmodel.interactive.action.PDActionURI; +import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotation; +import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationLink; +import org.apache.pdfbox.text.PDFTextStripper; +import org.junit.jupiter.api.Test; + +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +class ObsidianInvoiceSmokeTest { + + // -- what the sheet says ------------------------------------------- + + @Test + void theSheetCarriesTheIssuerTheCustomerAndEveryLine() throws Exception { + String text = textOf(render(ObsidianInvoiceFixtures.invoice())); + assertThat(text) + .contains("Kestrel Business") + .contains("Northline Consulting Ltd") + .contains("Subscription setup") + .contains("Business account maintenance") + .contains("Total due"); + } + + @Test + void theTaxColumnIsWorkedOutFromTheLineRatherThanStated() throws Exception { + String text = textOf(render(ObsidianInvoiceFixtures.invoice())); + // 1 x £150.00 charged at £180.00 leaves £30.00 of tax, and the model + // carries a rate rather than that figure. + assertThat(text).contains("£30.00").contains("£50.00").contains("£40.00"); + } + + @Test + void aLineWithNoTaxLeavesTheColumnAtZeroRatherThanNegative() throws Exception { + StructuredInvoiceData data = withLines(new InvoiceServiceLines( + ObsidianInvoiceFixtures.invoice().serviceLines().columns(), + List.of(new InvoiceServiceLines.Line(1, "Zero-rated service", "No tax due", "", + BigDecimal.ONE, "", new BigDecimal("150.00"), + new BigDecimal("150.00"), "", "", "")))); + assertThat(textOf(render(data))).contains("£0.00"); + } + + @Test + void moneyCarriesItsCurrencyMarkAgainstTheDigits() throws Exception { + String text = textOf(render(ObsidianInvoiceFixtures.invoice())); + assertThat(text).contains("£150.00").contains("£876.00"); + } + + // -- the discs ------------------------------------------------------ + + @Test + void theBilledPartysDiscTakesItsInitialsFromItsName() throws Exception { + // "Northline Consulting Ltd" gives NC, which is the design's own disc. + assertThat(textOf(render(ObsidianInvoiceFixtures.invoice()))).contains("NC"); + } + + @Test + void theIssuersDiscFallsBackFromMonogramToInitials() throws Exception { + StructuredInvoiceData base = ObsidianInvoiceFixtures.invoice(); + // The fixture states a monogram, so that is what the disc shows. + assertThat(textOf(render(base))).contains("K"); + + StructuredInvoiceData noMonogram = withBrand( + new InvoiceBrand(null, "Kestrel Business", "", "", "", "")); + // With none stated it falls back to initials taken from the name. + assertThat(textOf(render(noMonogram))).contains("KB"); + } + + // -- what a reader can act on -------------------------------------- + + @Test + void everyAddressTheSheetPrintsIsReachable() throws Exception { + List targets = linkTargets(render(ObsidianInvoiceFixtures.invoice())); + assertThat(targets).contains("mailto:ar@kestrel.example"); + } + + @Test + void theDueSentenceSetsItsDateApartWithoutBreakingTheSentence() throws Exception { + String text = textOf(render(ObsidianInvoiceFixtures.invoice())); + // The date is a run of the same sentence, not a paragraph of its own. + assertThat(text).contains("Payment is due by 10 September 2026."); + } + + // -- how it flows --------------------------------------------------- + + @Test + void theDesignsOwnLineCountIsOneSheet() throws Exception { + assertThat(pagesOf(ObsidianInvoiceFixtures.invoice())).isEqualTo(1); + } + + @Test + void enoughLinesPaginateAndTheHeaderComesBackWithThem() throws Exception { + List many = new ArrayList<>(); + for (int i = 1; i <= 30; i++) { + many.add(new InvoiceServiceLines.Line(i, "Subscription setup " + i, + "One-off account and workspace setup", "", + BigDecimal.ONE, "", new BigDecimal("150.00"), + new BigDecimal("180.00"), "", "", "")); + } + StructuredInvoiceData data = withLines(new InvoiceServiceLines( + ObsidianInvoiceFixtures.invoice().serviceLines().columns(), many)); + assertThat(pagesOf(data)).isGreaterThan(1); + + String text = textOf(render(data)); + // The caps header is repeated, so it appears once per page the table reaches. + assertThat(text.split("Unit price", -1).length - 1).isGreaterThan(1); + } + + // -- helpers --------------------------------------------------------- + + private static byte[] render(StructuredInvoiceData data) throws Exception { + // The preset owns its page geometry, so the session starts unconfigured. + try (DocumentSession session = GraphCompose.document().create()) { + ObsidianInvoice.create().compose(session, data); + assertThat(session.roots()).isNotEmpty(); + byte[] pdfBytes = session.toPdfBytes(); + assertThat(pdfBytes).isNotEmpty(); + return pdfBytes; + } + } + + private static String textOf(byte[] pdfBytes) throws Exception { + try (PDDocument document = Loader.loadPDF(pdfBytes)) { + return new PDFTextStripper().getText(document); + } + } + + private static int pagesOf(StructuredInvoiceData data) throws Exception { + try (DocumentSession session = GraphCompose.document().create()) { + ObsidianInvoice.create().compose(session, data); + return session.layoutSnapshot().totalPages(); + } + } + + private static List linkTargets(byte[] pdfBytes) throws Exception { + List targets = new ArrayList<>(); + try (PDDocument document = Loader.loadPDF(pdfBytes)) { + for (PDPage page : document.getPages()) { + for (PDAnnotation annotation : page.getAnnotations()) { + if (annotation instanceof PDAnnotationLink link + && link.getAction() instanceof PDActionURI uri) { + targets.add(uri.getURI()); + } + } + } + } + return targets; + } + + private static StructuredInvoiceData withLines(InvoiceServiceLines lines) { + StructuredInvoiceData base = ObsidianInvoiceFixtures.invoice(); + return StructuredInvoiceData.builder() + .brand(base.brand()).supplier(base.supplier()).masthead(base.masthead()) + .billTo(base.billTo()) + .serviceLines(lines).totals(base.totals()) + .payment(base.payment()).notes(base.notes()) + .currencyCode(base.currencyCode()).build(); + } + + private static StructuredInvoiceData withBrand(InvoiceBrand brand) { + StructuredInvoiceData base = ObsidianInvoiceFixtures.invoice(); + return StructuredInvoiceData.builder() + .brand(brand).supplier(base.supplier()).masthead(base.masthead()) + .billTo(base.billTo()) + .serviceLines(base.serviceLines()).totals(base.totals()) + .payment(base.payment()).notes(base.notes()) + .currencyCode(base.currencyCode()).build(); + } +} diff --git a/qa/src/test/java/com/demcha/compose/document/templates/invoice/presets/ObsidianInvoiceVisualParityTest.java b/qa/src/test/java/com/demcha/compose/document/templates/invoice/presets/ObsidianInvoiceVisualParityTest.java new file mode 100644 index 000000000..dee6121cf --- /dev/null +++ b/qa/src/test/java/com/demcha/compose/document/templates/invoice/presets/ObsidianInvoiceVisualParityTest.java @@ -0,0 +1,32 @@ +package com.demcha.compose.document.templates.invoice.presets; + +import com.demcha.compose.GraphCompose; +import com.demcha.compose.document.api.DocumentSession; +import com.demcha.compose.testing.visual.PdfVisualRegression; +import org.junit.jupiter.api.Test; + +import java.nio.file.Path; + +class ObsidianInvoiceVisualParityTest { + + private static final Path BASELINE_ROOT = Path.of( + "src", "test", "resources", "visual-baselines", "invoice-v2-layered"); + + private static final long PIXEL_DIFF_BUDGET = 50_000L; + private static final int PER_PIXEL_TOLERANCE = 8; + + @Test + void rendersWithinPixelDiffBudget() throws Exception { + byte[] pdfBytes; + try (DocumentSession document = GraphCompose.document().create()) { + ObsidianInvoice.create().compose(document, ObsidianInvoiceFixtures.invoice()); + pdfBytes = document.toPdfBytes(); + } + + PdfVisualRegression.standard() + .baselineRoot(BASELINE_ROOT) + .perPixelTolerance(PER_PIXEL_TOLERANCE) + .mismatchedPixelBudget(PIXEL_DIFF_BUDGET) + .assertMatchesBaseline("obsidian_invoice", pdfBytes); + } +} diff --git a/qa/src/test/java/com/demcha/compose/document/templates/invoice/presets/PaymentsInvoiceFixtures.java b/qa/src/test/java/com/demcha/compose/document/templates/invoice/presets/PaymentsInvoiceFixtures.java new file mode 100644 index 000000000..8ee074ca7 --- /dev/null +++ b/qa/src/test/java/com/demcha/compose/document/templates/invoice/presets/PaymentsInvoiceFixtures.java @@ -0,0 +1,154 @@ +package com.demcha.compose.document.templates.invoice.presets; + +import com.demcha.compose.document.templates.data.invoice.InvoiceBrand; +import com.demcha.compose.document.templates.data.invoice.InvoiceContactBlock; +import com.demcha.compose.document.templates.data.invoice.InvoiceMasthead; +import com.demcha.compose.document.templates.data.invoice.InvoiceNotesBlock; +import com.demcha.compose.document.templates.data.invoice.InvoicePaymentBlock; +import com.demcha.compose.document.templates.data.invoice.InvoiceRecipient; +import com.demcha.compose.document.templates.data.invoice.InvoiceServiceLines; +import com.demcha.compose.document.templates.data.invoice.InvoiceSummaryBlock; +import com.demcha.compose.document.templates.data.invoice.InvoiceTotalsBlock; +import com.demcha.compose.document.templates.data.invoice.StructuredInvoiceData; + +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.List; + +/** + * Shared fixture data for the {@link PaymentsInvoice} gates — the SAME document + * feeds the pixel parity test and the layout snapshot test, so a geometry shift + * the pixel budget absorbs still trips the exact snapshot, and vice versa. + */ +final class PaymentsInvoiceFixtures { + + private PaymentsInvoiceFixtures() { + } + + /** + * The canonical one-page invoice — seven metadata rows, two addressed + * parties, six marked service lines, seven payment fields, two summed rows + * and a two-line note. + * + * @return the document + */ + static StructuredInvoiceData canonicalInvoice() { + return new StructuredInvoiceData( + brand(), supplier(), masthead(), billTo(), shipTo(), + new InvoiceSummaryBlock("", "", ""), + serviceLines(), totals(), payment(), notes(), "GBP"); + } + + /** No logo: the wordmark is what a document without one falls back to. */ + static InvoiceBrand brand() { + return new InvoiceBrand(null, "Meridian", "", "", "", ""); + } + + static InvoiceContactBlock supplier() { + return new InvoiceContactBlock( + "Meridian Payments Ltd.", + List.of("1 Harbour Exchange", "Canary Wharf", "London E14 9GE", + "United Kingdom"), + "", "", "", + "", "Company No. 09048900", + "", "VAT No. GB 123 4567 89"); + } + + static InvoiceMasthead masthead() { + List entries = new ArrayList<>(); + entries.add(new InvoiceMasthead.Entry("Invoice Number", "INV-2025-05-10472", false)); + entries.add(new InvoiceMasthead.Entry("Invoice Date", "10 May 2025", false)); + entries.add(new InvoiceMasthead.Entry("Billing Period", "1 - 30 April 2025", false)); + entries.add(new InvoiceMasthead.Entry("Account ID", "acct_4Q8xTb2mKp", false)); + entries.add(new InvoiceMasthead.Entry("Payment Terms", "Net 30", false)); + entries.add(new InvoiceMasthead.Entry("Due Date", "14 June 2025", false)); + entries.add(new InvoiceMasthead.Entry("Status", "UNPAID", true)); + return new InvoiceMasthead("INVOICE", entries); + } + + /** The subline is where a billed party's registration goes on this sheet. */ + static InvoiceRecipient billTo() { + return new InvoiceRecipient("BILL TO", "Northwind Ltd.", + "VAT No. GB 987 6543 21", + List.of("Attention: Finance Team", "42 Bridgewater Street", + "Manchester M15 4QT", "United Kingdom"), + "", ""); + } + + static InvoiceRecipient shipTo() { + return new InvoiceRecipient("SHIP TO", "Northwind Ltd.", "", + List.of("Operations Centre", "7 Kingsway Park", + "Leeds LS12 6BD", "United Kingdom"), + "", ""); + } + + static InvoiceServiceLines serviceLines() { + InvoiceServiceLines.Columns columns = new InvoiceServiceLines.Columns( + "", "DESCRIPTION", "", "QUANTITY", "UNIT PRICE", "AMOUNT", "VAT"); + List lines = new ArrayList<>(); + lines.add(line(1, "Subscription Billing", + "Subscription management and recurring billing", + "1", "250.00", "250.00", "20%", "card")); + lines.add(line(2, "Payment Processing", + "Online card payments processing (volume tier)", + "1", "1,180.00", "1,180.00", "20%", "card-settings")); + lines.add(line(3, "Fraud Screening", + "Advanced fraud screening and rules", + "1", "320.00", "320.00", "20%", "shield")); + lines.add(line(4, "Company Formation", + "Incorporation and compliance filings", + "1", "180.00", "180.00", "20%", "globe")); + lines.add(line(5, "In-Person Terminals", + "Terminal rental and firmware updates", + "4", "39.00", "156.00", "20%", "mobile")); + lines.add(line(6, "Priority Support", + "Named engineer, one-hour response", + "1", "100.00", "100.00", "20%", "headset")); + return new InvoiceServiceLines(columns, lines); + } + + private static InvoiceServiceLines.Line line(int number, String title, String description, + String quantity, String unitPrice, + String amount, String vat, String icon) { + return new InvoiceServiceLines.Line(number, title, description, "", + decimal(quantity), "", decimal(unitPrice), decimal(amount), vat, icon); + } + + static InvoiceTotalsBlock totals() { + List rows = new ArrayList<>(); + rows.add(new InvoiceTotalsBlock.Row("Subtotal (excl. VAT)", decimal("2,186.00"))); + rows.add(new InvoiceTotalsBlock.Row("VAT at 20%", decimal("437.20"))); + return new InvoiceTotalsBlock(rows, "TOTAL DUE", decimal("2,623.20")); + } + + static InvoicePaymentBlock payment() { + List fields = new ArrayList<>(); + fields.add(new InvoicePaymentBlock.Field("Bank Name", "Harbour Bank plc")); + fields.add(new InvoicePaymentBlock.Field("Account Name", "Meridian Payments Ltd.")); + fields.add(new InvoicePaymentBlock.Field("Sort Code", "40-02-50")); + fields.add(new InvoicePaymentBlock.Field("Account Number", "71295408")); + fields.add(new InvoicePaymentBlock.Field("IBAN", "GB29 HRBK 4002 5071 2954 08")); + fields.add(new InvoicePaymentBlock.Field("SWIFT / BIC", "HRBKGB2L")); + fields.add(new InvoicePaymentBlock.Field("Reference", "INV-2025-05-10472")); + return new InvoicePaymentBlock("PAYMENT DETAILS", fields, + "Please include the invoice number as payment reference.", + "Due by 14 June 2025", + "Payment due by 14 June 2025", + "", + "Questions? We're here to help."); + } + + static InvoiceNotesBlock notes() { + return new InvoiceNotesBlock("NOTES", + List.of("Thank you for your business. This invoice covers services and " + + "fees for the period stated above.", + "Late payment may incur interest at the statutory rate."), + "support@meridianpayments.example", + "+44 (0) 20 3966 1900"); + } + + /** A printed figure back to a number: the grouping comes off. */ + private static BigDecimal decimal(String printed) { + return new BigDecimal(printed.replace(",", "")); + } +} diff --git a/qa/src/test/java/com/demcha/compose/document/templates/invoice/presets/PaymentsInvoiceLayoutSnapshotTest.java b/qa/src/test/java/com/demcha/compose/document/templates/invoice/presets/PaymentsInvoiceLayoutSnapshotTest.java new file mode 100644 index 000000000..b97d53c9b --- /dev/null +++ b/qa/src/test/java/com/demcha/compose/document/templates/invoice/presets/PaymentsInvoiceLayoutSnapshotTest.java @@ -0,0 +1,33 @@ +package com.demcha.compose.document.templates.invoice.presets; + +import com.demcha.compose.GraphCompose; +import com.demcha.compose.document.api.DocumentSession; +import com.demcha.compose.document.templates.TemplateTestSupport; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Exact layout snapshot gate for {@link PaymentsInvoice} — freezes the resolved + * geometry of the canonical one-page invoice, which the pixel budget cannot + * see: a small column or spacing shift stays under the visual-diff budget but + * changes the snapshot. + * + *

The preset owns its page geometry, so the session starts unconfigured.

+ * + *

Refresh with {@code -Dgraphcompose.updateSnapshots=true} after a + * deliberate layout change, and commit the JSON with the change.

+ */ +class PaymentsInvoiceLayoutSnapshotTest { + + @Test + void canonicalInvoiceMatchesLayoutSnapshot() throws Exception { + try (DocumentSession session = GraphCompose.document().create()) { + PaymentsInvoice.create().compose(session, + PaymentsInvoiceFixtures.canonicalInvoice()); + assertThat(session.layoutSnapshot().totalPages()).as("total pages").isEqualTo(1); + TemplateTestSupport.assertCanonicalSnapshot( + session, "payments_invoice_layout", "invoice"); + } + } +} diff --git a/qa/src/test/java/com/demcha/compose/document/templates/invoice/presets/PaymentsInvoiceSmokeTest.java b/qa/src/test/java/com/demcha/compose/document/templates/invoice/presets/PaymentsInvoiceSmokeTest.java new file mode 100644 index 000000000..aed6a269d --- /dev/null +++ b/qa/src/test/java/com/demcha/compose/document/templates/invoice/presets/PaymentsInvoiceSmokeTest.java @@ -0,0 +1,268 @@ +package com.demcha.compose.document.templates.invoice.presets; + +import com.demcha.compose.GraphCompose; +import com.demcha.compose.document.api.DocumentSession; +import com.demcha.compose.document.templates.api.DocumentTemplate; +import com.demcha.compose.document.templates.data.invoice.InvoiceBrand; +import com.demcha.compose.document.templates.data.invoice.InvoiceNotesBlock; +import com.demcha.compose.document.templates.data.invoice.InvoicePaymentBlock; +import com.demcha.compose.document.templates.data.invoice.InvoiceRecipient; +import com.demcha.compose.document.templates.data.invoice.InvoiceServiceLines; +import com.demcha.compose.document.templates.data.invoice.InvoiceSummaryBlock; +import com.demcha.compose.document.templates.data.invoice.StructuredInvoiceData; +import org.apache.pdfbox.Loader; +import org.apache.pdfbox.pdmodel.PDDocument; +import org.apache.pdfbox.pdmodel.PDPage; +import org.apache.pdfbox.pdmodel.interactive.action.PDActionURI; +import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotation; +import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationLink; +import org.apache.pdfbox.text.PDFTextStripper; +import org.junit.jupiter.api.Test; + +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +/** + * Smoke test for {@link PaymentsInvoice} — proves the preset renders a + * {@link StructuredInvoiceData} end-to-end with its packaged marks, falls back + * from a logo to a wordmark, writes its figures with the locale stated rather + * than inherited, reports an unknown mark as a data error, repeats the table + * header when the lines run past a page, and carries the support contacts as + * link annotations, which move no pixel and no layout node so neither gate + * would notice them going missing. + */ +class PaymentsInvoiceSmokeTest { + + private static byte[] render(StructuredInvoiceData data) throws Exception { + // The preset owns its page geometry, so the session starts unconfigured. + try (DocumentSession session = GraphCompose.document().create()) { + PaymentsInvoice.create().compose(session, data); + assertThat(session.roots()).isNotEmpty(); + byte[] pdfBytes = session.toPdfBytes(); + assertThat(pdfBytes).isNotEmpty(); + return pdfBytes; + } + } + + private static String textOf(byte[] pdfBytes) throws Exception { + try (PDDocument document = Loader.loadPDF(pdfBytes)) { + return new PDFTextStripper().getText(document); + } + } + + private static int pagesOf(StructuredInvoiceData data) throws Exception { + try (DocumentSession session = GraphCompose.document().create()) { + PaymentsInvoice.create().compose(session, data); + return session.layoutSnapshot().totalPages(); + } + } + + private static List linkTargets(byte[] pdfBytes) throws Exception { + List targets = new ArrayList<>(); + try (PDDocument document = Loader.loadPDF(pdfBytes)) { + for (PDPage page : document.getPages()) { + for (PDAnnotation annotation : page.getAnnotations()) { + if (annotation instanceof PDAnnotationLink link + && link.getAction() instanceof PDActionURI uri) { + targets.add(uri.getURI()); + } + } + } + } + return targets; + } + + /** The canonical invoice with its service lines replaced. */ + private static StructuredInvoiceData withLines(InvoiceServiceLines lines) { + StructuredInvoiceData base = PaymentsInvoiceFixtures.canonicalInvoice(); + return new StructuredInvoiceData(base.brand(), base.supplier(), base.masthead(), + base.billTo(), base.shipTo(), base.summary(), lines, base.totals(), + base.payment(), base.notes(), base.currencyCode()); + } + + @Test + void exposesStableIdentity() { + DocumentTemplate template = PaymentsInvoice.create(); + assertThat(template.id()).isEqualTo(PaymentsInvoice.ID); + assertThat(template.displayName()).isEqualTo(PaymentsInvoice.DISPLAY_NAME); + } + + @Test + void rendersCanonicalInvoiceWithPackagedMarks() throws Exception { + render(PaymentsInvoiceFixtures.canonicalInvoice()); + } + + @Test + void canonicalRenderCarriesEveryBlocksText() throws Exception { + String text = textOf(render(PaymentsInvoiceFixtures.canonicalInvoice())); + assertThat(text) + .contains("INVOICE") + .contains("Meridian Payments Ltd.") + .contains("Company No. 09048900") + .contains("VAT No. GB 123 4567 89") + .contains("Invoice Number") + .contains("BILL TO") + .contains("SHIP TO") + .contains("Northwind Ltd.") + .contains("Subscription Billing") + .contains("PAYMENT DETAILS") + .contains("GB29 HRBK 4002 5071 2954 08") + .contains("TOTAL DUE") + .contains("NOTES") + .contains("Questions? We're here to help."); + } + + @Test + void aDocumentWithNoLogoFallsBackToItsName() throws Exception { + // The preset carries no mark of its own, so a document that brings only + // a name still gets a lockup rather than an empty box. + assertThat(textOf(render(PaymentsInvoiceFixtures.canonicalInvoice()))) + .contains("Meridian"); + } + + @Test + void figuresAreWrittenWithTheCurrencyStatedOnceAndCarried() throws Exception { + // The column names the currency; each figure carries its symbol. Both + // come from the ISO code rather than from a formatted string. + String text = textOf(render(PaymentsInvoiceFixtures.canonicalInvoice())); + assertThat(text).contains("(GBP)").contains("£2,623.20").contains("£1,180.00"); + } + + @Test + void theDueLineIsSetInCapitalsWhateverCaseTheDocumentUses() throws Exception { + // The design sets the footer's due line in capitals, so the preset is + // what uppercases it rather than the document having to. + assertThat(textOf(render(PaymentsInvoiceFixtures.canonicalInvoice()))) + .contains("PAYMENT DUE BY 14 JUNE 2025"); + } + + @Test + void theSupportContactsAreClickable() throws Exception { + List targets = linkTargets(render(PaymentsInvoiceFixtures.canonicalInvoice())); + assertThat(targets) + .contains("mailto:support@meridianpayments.example") + .contains("tel:+442039661900"); + } + + @Test + void aTrunkPrefixIsPrintedButNotDialled() throws Exception { + // "(0)" is for a domestic dialler; left in the digits it would make a + // number that reaches nobody. The fixture's own number carries one, so + // the sheet prints it and the annotation drops it. + byte[] pdfBytes = render(PaymentsInvoiceFixtures.canonicalInvoice()); + // The stripper's own spacing is not the design's, so the printed form + // is compared without it. + assertThat(textOf(pdfBytes).replaceAll("(?U)\\s", "")) + .contains("+44(0)2039661900"); + assertThat(linkTargets(pdfBytes)) + .contains("tel:+442039661900") + .doesNotContain("tel:+4402039661900"); + } + + @Test + void anUnknownMarkIsReportedAsADataError() { + InvoiceServiceLines wrong = new InvoiceServiceLines( + PaymentsInvoiceFixtures.serviceLines().columns(), + List.of(new InvoiceServiceLines.Line(1, "Telepathy", "Reading minds", "", + BigDecimal.ONE, "", BigDecimal.TEN, BigDecimal.TEN, "20%", "telescope"))); + + assertThatThrownBy(() -> render(withLines(wrong))) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("telescope") + .hasMessageContaining("shield"); + } + + @Test + void aLineWithoutAMarkIsDrawnWithoutOne() throws Exception { + InvoiceServiceLines unmarked = new InvoiceServiceLines( + PaymentsInvoiceFixtures.serviceLines().columns(), + List.of(new InvoiceServiceLines.Line(1, "Consulting", "A day of it", "", + BigDecimal.ONE, "", new BigDecimal("1200"), new BigDecimal("1200"), + "20%"))); + + assertThat(textOf(render(withLines(unmarked)))).contains("Consulting"); + } + + @Test + void aLongerInvoiceRunsOnAndRepeatsItsTableHeader() throws Exception { + // The table is the region that grows, so the sheet flows rather than + // refusing — and a continuation page has to say what its columns are. + List many = new ArrayList<>(); + for (int index = 1; index <= 30; index++) { + many.add(new InvoiceServiceLines.Line(index, "Service " + index, + "A line of description for it", "", + BigDecimal.ONE, "", new BigDecimal("120"), new BigDecimal("120"), + "20%", "card")); + } + StructuredInvoiceData longer = withLines(new InvoiceServiceLines( + PaymentsInvoiceFixtures.serviceLines().columns(), many)); + + assertThat(pagesOf(longer)).isGreaterThan(1); + byte[] pdfBytes = render(longer); + assertThat(textOf(pdfBytes)).contains("Service 30"); + try (PDDocument document = Loader.loadPDF(pdfBytes)) { + PDFTextStripper stripper = new PDFTextStripper(); + stripper.setStartPage(2); + stripper.setEndPage(2); + assertThat(stripper.getText(document)) + .as("the second page names its columns") + .contains("DESCRIPTION") + .contains("UNIT PRICE"); + } + } + + @Test + void everyPageCarriesItsNumber() throws Exception { + List many = new ArrayList<>(); + for (int index = 1; index <= 30; index++) { + many.add(new InvoiceServiceLines.Line(index, "Service " + index, "", "", + BigDecimal.ONE, "", new BigDecimal("120"), new BigDecimal("120"), + "20%", "card")); + } + String text = textOf(render(withLines(new InvoiceServiceLines( + PaymentsInvoiceFixtures.serviceLines().columns(), many)))); + assertThat(text).contains("Page 1 of").contains("Page 2 of"); + } + + @Test + void anInvoiceWithNoShipToDrawsOneParty() throws Exception { + StructuredInvoiceData base = PaymentsInvoiceFixtures.canonicalInvoice(); + StructuredInvoiceData single = new StructuredInvoiceData(base.brand(), base.supplier(), + base.masthead(), base.billTo(), + new InvoiceRecipient("", "", "", List.of(), "", ""), + base.summary(), base.serviceLines(), base.totals(), base.payment(), + base.notes(), base.currencyCode()); + + String text = textOf(render(single)); + assertThat(text).contains("BILL TO").doesNotContain("SHIP TO"); + } + + @Test + void anInvoiceWithNoNotesDropsTheBlockRatherThanLeavingItsDisc() throws Exception { + StructuredInvoiceData base = PaymentsInvoiceFixtures.canonicalInvoice(); + StructuredInvoiceData bare = new StructuredInvoiceData(base.brand(), base.supplier(), + base.masthead(), base.billTo(), base.shipTo(), base.summary(), + base.serviceLines(), base.totals(), base.payment(), + new InvoiceNotesBlock("NOTES", List.of(), "ops@example.test", ""), + base.currencyCode()); + + String text = textOf(render(bare)); + assertThat(text).doesNotContain("Late payment may incur interest"); + assertThat(linkTargets(render(bare))).contains("mailto:ops@example.test"); + } + + @Test + void anEmptyDocumentStillRenders() throws Exception { + // Every block absent: the preset draws its furniture and nothing else, + // rather than failing on the first missing field. + render(new StructuredInvoiceData(new InvoiceBrand(null, "", "", ""), null, null, + null, null, new InvoiceSummaryBlock("", "", ""), + new InvoiceServiceLines(null, List.of()), null, + new InvoicePaymentBlock("", List.of(), "", "", "", "", ""), + new InvoiceNotesBlock("", List.of(), "", ""), "")); + } +} diff --git a/qa/src/test/java/com/demcha/compose/document/templates/invoice/presets/PaymentsInvoiceVisualParityTest.java b/qa/src/test/java/com/demcha/compose/document/templates/invoice/presets/PaymentsInvoiceVisualParityTest.java new file mode 100644 index 000000000..9b6046383 --- /dev/null +++ b/qa/src/test/java/com/demcha/compose/document/templates/invoice/presets/PaymentsInvoiceVisualParityTest.java @@ -0,0 +1,41 @@ +package com.demcha.compose.document.templates.invoice.presets; + +import com.demcha.compose.GraphCompose; +import com.demcha.compose.document.api.DocumentSession; +import com.demcha.compose.testing.visual.PdfVisualRegression; +import org.junit.jupiter.api.Test; + +import java.nio.file.Path; + +/** + * Pixel-diff visual parity gate for {@link PaymentsInvoice} — the canonical + * one-page invoice against a checked-in baseline. + * + *

Re-blessing baselines — after a deliberate visual change, + * re-run with {@code -Dgraphcompose.visual.approve=true} and commit the updated + * PNG with the change.

+ */ +class PaymentsInvoiceVisualParityTest { + + private static final Path BASELINE_ROOT = Path.of( + "src", "test", "resources", "visual-baselines", "invoice-v2-layered"); + + private static final long PIXEL_DIFF_BUDGET = 50_000L; + private static final int PER_PIXEL_TOLERANCE = 8; + + @Test + void rendersWithinPixelDiffBudget() throws Exception { + byte[] pdfBytes; + try (DocumentSession document = GraphCompose.document().create()) { + PaymentsInvoice.create().compose(document, + PaymentsInvoiceFixtures.canonicalInvoice()); + pdfBytes = document.toPdfBytes(); + } + + PdfVisualRegression.standard() + .baselineRoot(BASELINE_ROOT) + .perPixelTolerance(PER_PIXEL_TOLERANCE) + .mismatchedPixelBudget(PIXEL_DIFF_BUDGET) + .assertMatchesBaseline("payments_invoice", pdfBytes); + } +} diff --git a/qa/src/test/java/com/demcha/compose/document/templates/invoice/presets/PlatformInvoiceFixtures.java b/qa/src/test/java/com/demcha/compose/document/templates/invoice/presets/PlatformInvoiceFixtures.java new file mode 100644 index 000000000..74463ad9c --- /dev/null +++ b/qa/src/test/java/com/demcha/compose/document/templates/invoice/presets/PlatformInvoiceFixtures.java @@ -0,0 +1,111 @@ +package com.demcha.compose.document.templates.invoice.presets; + +import com.demcha.compose.document.templates.data.invoice.InvoiceBrand; +import com.demcha.compose.document.templates.data.invoice.InvoiceContactBlock; +import com.demcha.compose.document.templates.data.invoice.InvoiceMasthead; +import com.demcha.compose.document.templates.data.invoice.InvoiceNotesBlock; +import com.demcha.compose.document.templates.data.invoice.InvoicePaymentBlock; +import com.demcha.compose.document.templates.data.invoice.InvoiceRecipient; +import com.demcha.compose.document.templates.data.invoice.InvoiceServiceLines; +import com.demcha.compose.document.templates.data.invoice.InvoiceTotalsBlock; +import com.demcha.compose.document.templates.data.invoice.StructuredInvoiceData; + +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.List; + +/** + * The one invoice the Platform preset's tests render. + * + *

Both gates read it: the exact layout snapshot and the pixel baseline. One + * fixture behind both is what makes the two comparable — a shift the snapshot + * catches is a shift in the same document the baseline was recorded from.

+ * + *

It bills across two regions on purpose, because that is the column this + * design has and the others do not.

+ */ +final class PlatformInvoiceFixtures { + + private PlatformInvoiceFixtures() { + } + + /** A month of platform usage, billed across two regions to one customer. */ + static StructuredInvoiceData invoice() { + return StructuredInvoiceData.builder() + .brand(new InvoiceBrand(null, "Aurora Cloud", "", "", "", "")) + .supplier(new InvoiceContactBlock( + "Aurora Cloud Inc.", + List.of("500 Harbour Street", "Vancouver, BC V6C 2W2", "Canada"), + "+1 604-555-0180", + "billing@aurora.example", + "aurora.example", + "", "", + "VAT ID:", "CA BN 84213 7755")) + .masthead(new InvoiceMasthead("INVOICE", List.of( + new InvoiceMasthead.Entry("Invoice Number:", "INV-2026-07-31-9F4K2M", true), + new InvoiceMasthead.Entry("Invoice Date:", "31 July 2026", false), + new InvoiceMasthead.Entry("Due Date:", "30 August 2026", false), + new InvoiceMasthead.Entry("Billing Account ID:", "7C2D9E-B1F4A8-3K6M0P", true), + new InvoiceMasthead.Entry("Billing Period:", "01 Jul 2026 – 31 Jul 2026", false), + new InvoiceMasthead.Entry("Payment Terms:", "Net 30 Days", false), + new InvoiceMasthead.Entry("Currency:", "CAD", false)))) + .billTo(new InvoiceRecipient("BILL TO", "Bright Future Ltd.", "", + List.of("45 King Street", "Manchester, M2 4WU", "United Kingdom"), + "", "", "VAT ID:", "GB 987 6543 21")) + .shipTo(new InvoiceRecipient("SHIP TO", "Bright Future Ltd.", "", + List.of("Warehouse 7", "North Industrial Estate", + "Manchester, M2 4WU", "United Kingdom"), + "", "", "", "")) + .serviceLines(new InvoiceServiceLines( + new InvoiceServiceLines.Columns("", "DESCRIPTION", "SERVICE", + "USAGE", "UNIT PRICE", "AMOUNT", "", "REGION"), + lines())) + .totals(new InvoiceTotalsBlock(List.of( + new InvoiceTotalsBlock.Row("Subtotal", new BigDecimal("186.44")), + new InvoiceTotalsBlock.Row("Tax (5%)", new BigDecimal("9.32"))), + "TOTAL DUE", new BigDecimal("195.76"))) + .payment(new InvoicePaymentBlock("PAYMENT INFORMATION", List.of( + new InvoicePaymentBlock.Field("Bank Name:", "Harbourbank of Canada"), + new InvoicePaymentBlock.Field("Account Name:", "Aurora Cloud Inc."), + new InvoicePaymentBlock.Field("Account Number:", "417 802 336"), + new InvoicePaymentBlock.Field("SWIFT Code:", "HBCACAT1"), + new InvoicePaymentBlock.Field("Transit Number:", "00812"), + new InvoicePaymentBlock.Field("Institution Number:", "049"), + new InvoicePaymentBlock.Field("Account Type:", "Business Chequing"), + new InvoicePaymentBlock.Field("Remittance Email:", + "billing@aurora.example")), + "Include your Billing Account ID\non your payment remittance.", + "DUE DATE", "30 August 2026", "", "")) + .notes(new InvoiceNotesBlock( + "Thank you for running on Aurora Cloud.", + List.of("Manage your account at aurora.example" + + " or write to billing@aurora.example."), + "billing@aurora.example", "")) + .currencyCode("CAD") + .build(); + } + + private static List lines() { + List lines = new ArrayList<>(); + lines.add(line(1, "Compute Instances", "N2 Standard VM", "Compute", "ca-central1", + "744", "Hours", "0.0685", "50.96", "compute")); + lines.add(line(2, "Object Storage", "Standard class", "Storage", "ca-central1", + "480", "GB", "0.0210", "10.08", "storage")); + lines.add(line(3, "Managed Database", "PostgreSQL instance", "Database", "ca-central1", + "744", "Hours", "0.1385", "103.04", "database")); + lines.add(line(4, "Egress Traffic", "Data transfer out", "Network", "Global", + "260", "GB", "0.0860", "22.36", "network")); + lines.add(line(5, "Support Plan", "Standard response", "Support", "Global", + "1", "", "0.00", "0.00", "support")); + return lines; + } + + private static InvoiceServiceLines.Line line(int number, String title, String description, + String service, String region, String quantity, + String unit, String unitPrice, String amount, + String icon) { + return new InvoiceServiceLines.Line(number, title, description, service, + new BigDecimal(quantity), unit, new BigDecimal(unitPrice), + new BigDecimal(amount), "", icon, region); + } +} diff --git a/qa/src/test/java/com/demcha/compose/document/templates/invoice/presets/PlatformInvoiceLayoutSnapshotTest.java b/qa/src/test/java/com/demcha/compose/document/templates/invoice/presets/PlatformInvoiceLayoutSnapshotTest.java new file mode 100644 index 000000000..f039c73b2 --- /dev/null +++ b/qa/src/test/java/com/demcha/compose/document/templates/invoice/presets/PlatformInvoiceLayoutSnapshotTest.java @@ -0,0 +1,21 @@ +package com.demcha.compose.document.templates.invoice.presets; + +import com.demcha.compose.GraphCompose; +import com.demcha.compose.document.api.DocumentSession; +import com.demcha.compose.document.templates.TemplateTestSupport; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +class PlatformInvoiceLayoutSnapshotTest { + + @Test + void theInvoiceMatchesItsLayoutSnapshot() throws Exception { + try (DocumentSession session = GraphCompose.document().create()) { + PlatformInvoice.create().compose(session, PlatformInvoiceFixtures.invoice()); + assertThat(session.layoutSnapshot().totalPages()).as("total pages").isEqualTo(1); + TemplateTestSupport.assertCanonicalSnapshot( + session, "platform_invoice_layout", "invoice"); + } + } +} diff --git a/qa/src/test/java/com/demcha/compose/document/templates/invoice/presets/PlatformInvoiceSmokeTest.java b/qa/src/test/java/com/demcha/compose/document/templates/invoice/presets/PlatformInvoiceSmokeTest.java new file mode 100644 index 000000000..cbf409af2 --- /dev/null +++ b/qa/src/test/java/com/demcha/compose/document/templates/invoice/presets/PlatformInvoiceSmokeTest.java @@ -0,0 +1,206 @@ +package com.demcha.compose.document.templates.invoice.presets; + +import com.demcha.compose.GraphCompose; +import com.demcha.compose.document.api.DocumentSession; +import com.demcha.compose.document.templates.data.invoice.InvoiceServiceLines; +import com.demcha.compose.document.templates.data.invoice.StructuredInvoiceData; +import org.apache.pdfbox.Loader; +import org.apache.pdfbox.pdmodel.PDDocument; +import org.apache.pdfbox.pdmodel.PDPage; +import org.apache.pdfbox.pdmodel.interactive.action.PDActionURI; +import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotation; +import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationLink; +import org.apache.pdfbox.text.PDFTextStripper; +import org.junit.jupiter.api.Test; + +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class PlatformInvoiceSmokeTest { + + // -- what the sheet says ------------------------------------------- + + @Test + void theSheetCarriesTheIssuerTheCustomerAndEveryLine() throws Exception { + String text = textOf(render(PlatformInvoiceFixtures.invoice())); + assertThat(text) + .contains("Aurora Cloud Inc.") + .contains("Bright Future Ltd.") + .contains("Compute Instances") + .contains("Managed Database") + .contains("Support Plan") + .contains("TOTAL DUE"); + } + + @Test + void aLineStatesWhereItWasDelivered() throws Exception { + String text = textOf(render(PlatformInvoiceFixtures.invoice())); + assertThat(text).contains("REGION").contains("ca-central1").contains("Global"); + } + + @Test + void aRateKeepsThePrecisionItIsQuotedAtAndAnAmountDoesNot() throws Exception { + String text = textOf(render(PlatformInvoiceFixtures.invoice())); + // 0.0685 rounded to an amount's two places is 0.07 — a different price + // that multiplies out to a different bill. + assertThat(text).contains("0.0685").contains("0.0210").contains("0.1385"); + // Amounts stay at two. + assertThat(text).contains("103.04").contains("22.36"); + } + + @Test + void usageWithAUnitIsAMeasurementAndUsageWithoutOneIsACount() throws Exception { + String text = textOf(render(PlatformInvoiceFixtures.invoice())); + // The meter reads to two places, so a whole number keeps them. + assertThat(text).contains("744.00").contains("480.00").contains("260.00"); + // The support line counts one plan and names no unit, so it stays bare. + assertThat(text).doesNotContain("1.00"); + } + + @Test + void theCurrencyIsStatedOnceInEachMoneyColumnAndOnceOnTheTotal() throws Exception { + String text = textOf(render(PlatformInvoiceFixtures.invoice())); + assertThat(text).contains("UNIT PRICE (CAD)").contains("AMOUNT (CAD)"); + // The grand total stands under no caption, so it names its own currency. + assertThat(text).contains("CAD 195.76"); + // The summed rows sit under a column that already stated it. + assertThat(text).contains("186.44").doesNotContain("CAD 186.44"); + } + + // -- what a reader can act on -------------------------------------- + + @Test + void everyAddressTheSheetPrintsIsReachable() throws Exception { + List targets = linkTargets(render(PlatformInvoiceFixtures.invoice())); + assertThat(targets) + .contains("mailto:billing@aurora.example") + // A site printed without a scheme still needs one to open. + .contains("https://aurora.example") + .contains("tel:+16045550180"); + } + + // -- what the document chooses, and what it may not ---------------- + + @Test + void aLineThatNamesNoMarkStillRenders() throws Exception { + StructuredInvoiceData data = withLines(new InvoiceServiceLines( + PlatformInvoiceFixtures.invoice().serviceLines().columns(), + List.of(new InvoiceServiceLines.Line(1, "Compute Instances", "N2 Standard VM", + "Compute", new BigDecimal("744"), "Hours", + new BigDecimal("0.0685"), new BigDecimal("50.96"), "", "", + "ca-central1")))); + assertThat(textOf(render(data))).contains("Compute Instances"); + } + + @Test + void anUnknownMarkIsReportedAsADataErrorNamingTheSet() { + StructuredInvoiceData data = withLines(new InvoiceServiceLines( + PlatformInvoiceFixtures.invoice().serviceLines().columns(), + List.of(new InvoiceServiceLines.Line(1, "Compute Instances", "N2 Standard VM", + "Compute", new BigDecimal("744"), "Hours", + new BigDecimal("0.0685"), new BigDecimal("50.96"), "", "rocket", + "ca-central1")))); + assertThatThrownBy(() -> render(data)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("rocket") + .hasMessageContaining("compute"); + } + + @Test + void anInvoiceThatShipsNowhereLeavesTheColumnEmptyRatherThanFailing() throws Exception { + StructuredInvoiceData base = PlatformInvoiceFixtures.invoice(); + StructuredInvoiceData data = StructuredInvoiceData.builder() + .brand(base.brand()).supplier(base.supplier()).masthead(base.masthead()) + .billTo(base.billTo()) + .serviceLines(base.serviceLines()).totals(base.totals()) + .payment(base.payment()).notes(base.notes()) + .currencyCode(base.currencyCode()).build(); + String text = textOf(render(data)); + assertThat(text).contains("BILL TO").doesNotContain("SHIP TO"); + } + + // -- how it flows --------------------------------------------------- + + @Test + void theDesignsOwnLineCountIsOneSheet() throws Exception { + assertThat(pagesOf(PlatformInvoiceFixtures.invoice())).isEqualTo(1); + } + + @Test + void enoughLinesPaginateAndTheHeaderComesBackWithThem() throws Exception { + List many = new ArrayList<>(); + for (int i = 1; i <= 40; i++) { + many.add(new InvoiceServiceLines.Line(i, "Compute Instances " + i, "N2 Standard VM", + "Compute", new BigDecimal("744"), "Hours", new BigDecimal("0.0685"), + new BigDecimal("50.96"), "", "compute", "ca-central1")); + } + StructuredInvoiceData data = withLines(new InvoiceServiceLines( + PlatformInvoiceFixtures.invoice().serviceLines().columns(), many)); + assertThat(pagesOf(data)).isGreaterThan(1); + + String text = textOf(render(data)); + // The caps header is repeated, so it appears once per page the table reaches. + assertThat(text.split("REGION", -1).length - 1).isGreaterThan(1); + } + + // -- helpers --------------------------------------------------------- + + private static byte[] render(StructuredInvoiceData data) throws Exception { + // The preset owns its page geometry, so the session starts unconfigured. + try (DocumentSession session = GraphCompose.document().create()) { + PlatformInvoice.create().compose(session, data); + assertThat(session.roots()).isNotEmpty(); + byte[] pdfBytes = session.toPdfBytes(); + assertThat(pdfBytes).isNotEmpty(); + return pdfBytes; + } + } + + private static String textOf(byte[] pdfBytes) throws Exception { + try (PDDocument document = Loader.loadPDF(pdfBytes)) { + return new PDFTextStripper().getText(document); + } + } + + private static int pagesOf(StructuredInvoiceData data) throws Exception { + try (DocumentSession session = GraphCompose.document().create()) { + PlatformInvoice.create().compose(session, data); + return session.layoutSnapshot().totalPages(); + } + } + + private static List linkTargets(byte[] pdfBytes) throws Exception { + List targets = new ArrayList<>(); + try (PDDocument document = Loader.loadPDF(pdfBytes)) { + for (PDPage page : document.getPages()) { + for (PDAnnotation annotation : page.getAnnotations()) { + if (annotation instanceof PDAnnotationLink link + && link.getAction() instanceof PDActionURI uri) { + targets.add(uri.getURI()); + } + } + } + } + return targets; + } + + private static StructuredInvoiceData withLines(InvoiceServiceLines lines) { + StructuredInvoiceData base = PlatformInvoiceFixtures.invoice(); + return StructuredInvoiceData.builder() + .brand(base.brand()) + .supplier(base.supplier()) + .masthead(base.masthead()) + .billTo(base.billTo()) + .shipTo(base.shipTo()) + .serviceLines(lines) + .totals(base.totals()) + .payment(base.payment()) + .notes(base.notes()) + .currencyCode(base.currencyCode()) + .build(); + } +} diff --git a/qa/src/test/java/com/demcha/compose/document/templates/invoice/presets/PlatformInvoiceVisualParityTest.java b/qa/src/test/java/com/demcha/compose/document/templates/invoice/presets/PlatformInvoiceVisualParityTest.java new file mode 100644 index 000000000..c3beec57c --- /dev/null +++ b/qa/src/test/java/com/demcha/compose/document/templates/invoice/presets/PlatformInvoiceVisualParityTest.java @@ -0,0 +1,32 @@ +package com.demcha.compose.document.templates.invoice.presets; + +import com.demcha.compose.GraphCompose; +import com.demcha.compose.document.api.DocumentSession; +import com.demcha.compose.testing.visual.PdfVisualRegression; +import org.junit.jupiter.api.Test; + +import java.nio.file.Path; + +class PlatformInvoiceVisualParityTest { + + private static final Path BASELINE_ROOT = Path.of( + "src", "test", "resources", "visual-baselines", "invoice-v2-layered"); + + private static final long PIXEL_DIFF_BUDGET = 50_000L; + private static final int PER_PIXEL_TOLERANCE = 8; + + @Test + void rendersWithinPixelDiffBudget() throws Exception { + byte[] pdfBytes; + try (DocumentSession document = GraphCompose.document().create()) { + PlatformInvoice.create().compose(document, PlatformInvoiceFixtures.invoice()); + pdfBytes = document.toPdfBytes(); + } + + PdfVisualRegression.standard() + .baselineRoot(BASELINE_ROOT) + .perPixelTolerance(PER_PIXEL_TOLERANCE) + .mismatchedPixelBudget(PIXEL_DIFF_BUDGET) + .assertMatchesBaseline("platform_invoice", pdfBytes); + } +} diff --git a/qa/src/test/java/com/demcha/compose/document/templates/invoice/presets/SubscriptionInvoiceFixtures.java b/qa/src/test/java/com/demcha/compose/document/templates/invoice/presets/SubscriptionInvoiceFixtures.java new file mode 100644 index 000000000..aa5c0f43d --- /dev/null +++ b/qa/src/test/java/com/demcha/compose/document/templates/invoice/presets/SubscriptionInvoiceFixtures.java @@ -0,0 +1,103 @@ +package com.demcha.compose.document.templates.invoice.presets; + +import com.demcha.compose.document.templates.data.invoice.InvoiceBrand; +import com.demcha.compose.document.templates.data.invoice.InvoiceContactBlock; +import com.demcha.compose.document.templates.data.invoice.InvoiceMasthead; +import com.demcha.compose.document.templates.data.invoice.InvoiceNotesBlock; +import com.demcha.compose.document.templates.data.invoice.InvoicePaymentBlock; +import com.demcha.compose.document.templates.data.invoice.InvoiceRecipient; +import com.demcha.compose.document.templates.data.invoice.InvoiceServiceLines; +import com.demcha.compose.document.templates.data.invoice.InvoiceTotalsBlock; +import com.demcha.compose.document.templates.data.invoice.StructuredInvoiceData; + +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.List; + +/** + * The one invoice the Subscription preset's tests render. + * + *

Both gates read it: the exact layout snapshot and the pixel baseline. One + * fixture behind both is what makes the two comparable — a shift the snapshot + * catches is a shift in the same document the baseline was recorded from.

+ * + *

It bills seats with a tax rate on every line, because that is what this + * design's table carries and the others' do not.

+ */ +final class SubscriptionInvoiceFixtures { + + private SubscriptionInvoiceFixtures() { + } + + /** A billing period of software subscriptions, billed to one customer. */ + static StructuredInvoiceData invoice() { + return StructuredInvoiceData.builder() + .brand(new InvoiceBrand(null, "Halstead", "", "", "", "")) + .supplier(new InvoiceContactBlock( + "Halstead Software Ltd.", + List.of("18 Chandler Row", "Leeds LS1 4QT", "United Kingdom"), + "", + "billing@halstead.example", + "www.halstead.example", + "", "", + "VAT No", "GB 418 2276 03")) + .masthead(new InvoiceMasthead("INVOICE", List.of( + new InvoiceMasthead.Entry("Invoice No", "HS-INV-2026-0417", false), + new InvoiceMasthead.Entry("Issue Date", "27 August 2026", false), + new InvoiceMasthead.Entry("Due Date", "10 September 2026", false), + new InvoiceMasthead.Entry("Payment Terms", "14 days", false), + new InvoiceMasthead.Entry("Currency", "GBP", false)))) + .billTo(new InvoiceRecipient("BILL TO", "Northline Consulting Ltd", + "Attn: Sarah Mitchell", + List.of("21 Jubilee Way", "London SE1 3SS", "United Kingdom"), + "", "", "", "")) + .shipTo(new InvoiceRecipient("SHIP TO", "Northline Consulting Ltd", + "The Foundry, 2nd Floor", + List.of("17-19 Great Suffolk Street", "London SE1 0NS", + "United Kingdom"), + "", "", "", "")) + .serviceLines(new InvoiceServiceLines( + new InvoiceServiceLines.Columns("#", "DESCRIPTION", "", + "QTY", "UNIT PRICE", "AMOUNT", "VAT"), + lines())) + .totals(new InvoiceTotalsBlock(List.of( + new InvoiceTotalsBlock.Row("Subtotal", new BigDecimal("947.00")), + new InvoiceTotalsBlock.Row("VAT (20%)", new BigDecimal("189.40"))), + "TOTAL DUE", new BigDecimal("1136.40"))) + .payment(new InvoicePaymentBlock("PAYMENT DETAILS", List.of( + new InvoicePaymentBlock.Field("Beneficiary", "Halstead Software Ltd"), + new InvoicePaymentBlock.Field("Bank", "Northgate Bank"), + new InvoicePaymentBlock.Field("Sort Code", "40-05-30"), + new InvoicePaymentBlock.Field("Account Number", "12345678"), + new InvoicePaymentBlock.Field("Reference", "HS-INV-2026-0417")), + "Please include the invoice number as the payment reference" + + " to ensure timely allocation.", + "Payment is due by 10 September 2026. If payment is not received" + + " by the due date, late fees may apply.\n" + + "For any queries, please contact billing@halstead.example.", + "", "", "Thank you for your business.")) + .notes(new InvoiceNotesBlock("NOTES", + List.of("This invoice covers software subscriptions and hosted" + + " services for the current billing period."), + "", "")) + .currencyCode("GBP") + .build(); + } + + private static List lines() { + List lines = new ArrayList<>(); + lines.add(line(1, "Halstead Workspace Premium (10 seats)", "10", "18.00", "180.00")); + lines.add(line(2, "Hosted Platform Services", "1", "420.00", "420.00")); + lines.add(line(3, "Analytics Pro Licences (5 seats)", "5", "8.40", "42.00")); + lines.add(line(4, "Priority Support Plan", "1", "275.00", "275.00")); + lines.add(line(5, "Voice Add-on (5 seats)", "5", "6.00", "30.00")); + return lines; + } + + private static InvoiceServiceLines.Line line(int number, String title, String quantity, + String unitPrice, String amount) { + return new InvoiceServiceLines.Line(number, title, "", "", + new BigDecimal(quantity), "", new BigDecimal(unitPrice), + new BigDecimal(amount), "20%", "", ""); + } +} diff --git a/qa/src/test/java/com/demcha/compose/document/templates/invoice/presets/SubscriptionInvoiceLayoutSnapshotTest.java b/qa/src/test/java/com/demcha/compose/document/templates/invoice/presets/SubscriptionInvoiceLayoutSnapshotTest.java new file mode 100644 index 000000000..fc994f672 --- /dev/null +++ b/qa/src/test/java/com/demcha/compose/document/templates/invoice/presets/SubscriptionInvoiceLayoutSnapshotTest.java @@ -0,0 +1,22 @@ +package com.demcha.compose.document.templates.invoice.presets; + +import com.demcha.compose.GraphCompose; +import com.demcha.compose.document.api.DocumentSession; +import com.demcha.compose.document.templates.TemplateTestSupport; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +class SubscriptionInvoiceLayoutSnapshotTest { + + @Test + void theInvoiceMatchesItsLayoutSnapshot() throws Exception { + try (DocumentSession session = GraphCompose.document().create()) { + SubscriptionInvoice.create().compose(session, + SubscriptionInvoiceFixtures.invoice()); + assertThat(session.layoutSnapshot().totalPages()).as("total pages").isEqualTo(1); + TemplateTestSupport.assertCanonicalSnapshot( + session, "subscription_invoice_layout", "invoice"); + } + } +} diff --git a/qa/src/test/java/com/demcha/compose/document/templates/invoice/presets/SubscriptionInvoiceSmokeTest.java b/qa/src/test/java/com/demcha/compose/document/templates/invoice/presets/SubscriptionInvoiceSmokeTest.java new file mode 100644 index 000000000..fe7dbf8a9 --- /dev/null +++ b/qa/src/test/java/com/demcha/compose/document/templates/invoice/presets/SubscriptionInvoiceSmokeTest.java @@ -0,0 +1,225 @@ +package com.demcha.compose.document.templates.invoice.presets; + +import com.demcha.compose.GraphCompose; +import com.demcha.compose.document.api.DocumentSession; +import com.demcha.compose.document.templates.data.invoice.InvoicePaymentBlock; +import com.demcha.compose.document.templates.data.invoice.InvoiceServiceLines; +import com.demcha.compose.document.templates.data.invoice.StructuredInvoiceData; +import org.apache.pdfbox.Loader; +import org.apache.pdfbox.pdmodel.PDDocument; +import org.apache.pdfbox.pdmodel.PDPage; +import org.apache.pdfbox.pdmodel.interactive.action.PDActionURI; +import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotation; +import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationLink; +import org.apache.pdfbox.text.PDFTextStripper; +import org.junit.jupiter.api.Test; + +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class SubscriptionInvoiceSmokeTest { + + // -- what the sheet says ------------------------------------------- + + @Test + void theSheetCarriesTheIssuerTheCustomerAndEveryLine() throws Exception { + String text = textOf(render(SubscriptionInvoiceFixtures.invoice())); + assertThat(text) + .contains("Halstead Software Ltd.") + .contains("Northline Consulting Ltd") + .contains("Halstead Workspace Premium (10 seats)") + .contains("Priority Support Plan") + .contains("TOTAL DUE"); + } + + @Test + void everyLineCarriesItsOwnTaxRate() throws Exception { + String text = textOf(render(SubscriptionInvoiceFixtures.invoice())); + // The rate is printed per line, not only in the sum, which is what the + // VAT column is for. + assertThat(text.split("20%", -1).length - 1).isGreaterThanOrEqualTo(5); + } + + @Test + void aLineIsNumberedByWhatItStatesAndOtherwiseByItsPosition() throws Exception { + StructuredInvoiceData stated = withLines(new InvoiceServiceLines( + SubscriptionInvoiceFixtures.invoice().serviceLines().columns(), + List.of(line(41, "Carried forward"), line(42, "Second")))); + assertThat(textOf(render(stated))).contains("41").contains("42"); + + StructuredInvoiceData unstated = withLines(new InvoiceServiceLines( + SubscriptionInvoiceFixtures.invoice().serviceLines().columns(), + List.of(line(0, "First"), line(0, "Second")))); + String text = textOf(render(unstated)); + assertThat(text).contains("1").contains("2"); + } + + @Test + void moneyCarriesItsCurrencySymbolAgainstTheDigits() throws Exception { + String text = textOf(render(SubscriptionInvoiceFixtures.invoice())); + // Every figure names its own currency, because no column states one. + assertThat(text).contains("£18.00").contains("£1,136.40"); + } + + @Test + void aCurrencyWhoseMarkTheColumnCannotHoldIsRejectedRatherThanOverflowed() { + StructuredInvoiceData base = SubscriptionInvoiceFixtures.invoice(); + StructuredInvoiceData data = StructuredInvoiceData.builder() + .brand(base.brand()).supplier(base.supplier()).masthead(base.masthead()) + .billTo(base.billTo()).shipTo(base.shipTo()) + .serviceLines(base.serviceLines()).totals(base.totals()) + .payment(base.payment()).notes(base.notes()) + .currencyCode("ZZZ").build(); + // The design writes money with a one-character mark and its columns are + // measured for one, so a currency the runtime knows only by its + // three-letter code does not fit the unit-price column. Widening it + // would be a different sheet, so the render is refused where it happens + // rather than letting the figure run under its neighbour — a defect a + // reader would have to catch by eye. + assertThatThrownBy(() -> render(data)) + .isInstanceOf(IllegalStateException.class) + .hasMessageContaining("column"); + } + + // -- what a reader can act on -------------------------------------- + + @Test + void everyAddressTheSheetPrintsIsReachable() throws Exception { + List targets = linkTargets(render(SubscriptionInvoiceFixtures.invoice())); + assertThat(targets) + .contains("mailto:billing@halstead.example") + // A site printed without a scheme still needs one to open. + .contains("https://www.halstead.example"); + } + + @Test + void theClosingBandLinksTheAddressInsideItsSentence() throws Exception { + byte[] pdf = render(SubscriptionInvoiceFixtures.invoice()); + assertThat(textOf(pdf)).contains("For any queries, please contact"); + assertThat(linkTargets(pdf)) + .filteredOn(target -> target.equals("mailto:billing@halstead.example")) + // Once in the supplier block and once in the closing band. + .hasSizeGreaterThanOrEqualTo(2); + } + + // -- what the design assumes, and what it tolerates ---------------- + + @Test + void aPaymentBandWithADifferentCellCountStillDividesEvenly() throws Exception { + StructuredInvoiceData base = SubscriptionInvoiceFixtures.invoice(); + InvoicePaymentBlock payment = base.payment(); + StructuredInvoiceData data = StructuredInvoiceData.builder() + .brand(base.brand()).supplier(base.supplier()).masthead(base.masthead()) + .billTo(base.billTo()).shipTo(base.shipTo()) + .serviceLines(base.serviceLines()).totals(base.totals()) + .payment(new InvoicePaymentBlock(payment.heading(), + payment.fields().subList(0, 3), payment.instruction(), + payment.dueNotice(), "", "", payment.signOff())) + .notes(base.notes()).currencyCode(base.currencyCode()).build(); + String text = textOf(render(data)); + assertThat(text).contains("Beneficiary").contains("Sort Code") + .doesNotContain("Account Number"); + } + + @Test + void anInvoiceThatShipsNowhereLeavesTheSecondPartyOut() throws Exception { + StructuredInvoiceData base = SubscriptionInvoiceFixtures.invoice(); + StructuredInvoiceData data = StructuredInvoiceData.builder() + .brand(base.brand()).supplier(base.supplier()).masthead(base.masthead()) + .billTo(base.billTo()) + .serviceLines(base.serviceLines()).totals(base.totals()) + .payment(base.payment()).notes(base.notes()) + .currencyCode(base.currencyCode()).build(); + String text = textOf(render(data)); + assertThat(text).contains("BILL TO").doesNotContain("SHIP TO"); + } + + // -- how it flows --------------------------------------------------- + + @Test + void theDesignsOwnLineCountIsOneSheet() throws Exception { + assertThat(pagesOf(SubscriptionInvoiceFixtures.invoice())).isEqualTo(1); + } + + @Test + void enoughLinesPaginateAndTheHeaderComesBackWithThem() throws Exception { + List many = new ArrayList<>(); + for (int i = 1; i <= 40; i++) { + many.add(line(i, "Halstead Workspace Premium " + i)); + } + StructuredInvoiceData data = withLines(new InvoiceServiceLines( + SubscriptionInvoiceFixtures.invoice().serviceLines().columns(), many)); + assertThat(pagesOf(data)).isGreaterThan(1); + + String text = textOf(render(data)); + // The caps header is repeated, so it appears once per page the table reaches. + assertThat(text.split("UNIT PRICE", -1).length - 1).isGreaterThan(1); + } + + // -- helpers --------------------------------------------------------- + + private static InvoiceServiceLines.Line line(int number, String title) { + return new InvoiceServiceLines.Line(number, title, "", "", + new BigDecimal("10"), "", new BigDecimal("18.00"), + new BigDecimal("180.00"), "20%", "", ""); + } + + private static byte[] render(StructuredInvoiceData data) throws Exception { + // The preset owns its page geometry, so the session starts unconfigured. + try (DocumentSession session = GraphCompose.document().create()) { + SubscriptionInvoice.create().compose(session, data); + assertThat(session.roots()).isNotEmpty(); + byte[] pdfBytes = session.toPdfBytes(); + assertThat(pdfBytes).isNotEmpty(); + return pdfBytes; + } + } + + private static String textOf(byte[] pdfBytes) throws Exception { + try (PDDocument document = Loader.loadPDF(pdfBytes)) { + return new PDFTextStripper().getText(document); + } + } + + private static int pagesOf(StructuredInvoiceData data) throws Exception { + try (DocumentSession session = GraphCompose.document().create()) { + SubscriptionInvoice.create().compose(session, data); + return session.layoutSnapshot().totalPages(); + } + } + + private static List linkTargets(byte[] pdfBytes) throws Exception { + List targets = new ArrayList<>(); + try (PDDocument document = Loader.loadPDF(pdfBytes)) { + for (PDPage page : document.getPages()) { + for (PDAnnotation annotation : page.getAnnotations()) { + if (annotation instanceof PDAnnotationLink link + && link.getAction() instanceof PDActionURI uri) { + targets.add(uri.getURI()); + } + } + } + } + return targets; + } + + private static StructuredInvoiceData withLines(InvoiceServiceLines lines) { + StructuredInvoiceData base = SubscriptionInvoiceFixtures.invoice(); + return StructuredInvoiceData.builder() + .brand(base.brand()) + .supplier(base.supplier()) + .masthead(base.masthead()) + .billTo(base.billTo()) + .shipTo(base.shipTo()) + .serviceLines(lines) + .totals(base.totals()) + .payment(base.payment()) + .notes(base.notes()) + .currencyCode(base.currencyCode()) + .build(); + } +} diff --git a/qa/src/test/java/com/demcha/compose/document/templates/invoice/presets/SubscriptionInvoiceVisualParityTest.java b/qa/src/test/java/com/demcha/compose/document/templates/invoice/presets/SubscriptionInvoiceVisualParityTest.java new file mode 100644 index 000000000..9a3030d31 --- /dev/null +++ b/qa/src/test/java/com/demcha/compose/document/templates/invoice/presets/SubscriptionInvoiceVisualParityTest.java @@ -0,0 +1,33 @@ +package com.demcha.compose.document.templates.invoice.presets; + +import com.demcha.compose.GraphCompose; +import com.demcha.compose.document.api.DocumentSession; +import com.demcha.compose.testing.visual.PdfVisualRegression; +import org.junit.jupiter.api.Test; + +import java.nio.file.Path; + +class SubscriptionInvoiceVisualParityTest { + + private static final Path BASELINE_ROOT = Path.of( + "src", "test", "resources", "visual-baselines", "invoice-v2-layered"); + + private static final long PIXEL_DIFF_BUDGET = 50_000L; + private static final int PER_PIXEL_TOLERANCE = 8; + + @Test + void rendersWithinPixelDiffBudget() throws Exception { + byte[] pdfBytes; + try (DocumentSession document = GraphCompose.document().create()) { + SubscriptionInvoice.create().compose(document, + SubscriptionInvoiceFixtures.invoice()); + pdfBytes = document.toPdfBytes(); + } + + PdfVisualRegression.standard() + .baselineRoot(BASELINE_ROOT) + .perPixelTolerance(PER_PIXEL_TOLERANCE) + .mismatchedPixelBudget(PIXEL_DIFF_BUDGET) + .assertMatchesBaseline("subscription_invoice", pdfBytes); + } +} diff --git a/qa/src/test/java/com/demcha/compose/document/templates/invoice/presets/WorkspaceInvoiceFixtures.java b/qa/src/test/java/com/demcha/compose/document/templates/invoice/presets/WorkspaceInvoiceFixtures.java new file mode 100644 index 000000000..28fca8964 --- /dev/null +++ b/qa/src/test/java/com/demcha/compose/document/templates/invoice/presets/WorkspaceInvoiceFixtures.java @@ -0,0 +1,140 @@ +package com.demcha.compose.document.templates.invoice.presets; + +import com.demcha.compose.document.templates.data.invoice.InvoiceBrand; +import com.demcha.compose.document.templates.data.invoice.InvoiceContactBlock; +import com.demcha.compose.document.templates.data.invoice.InvoiceMasthead; +import com.demcha.compose.document.templates.data.invoice.InvoiceNotesBlock; +import com.demcha.compose.document.templates.data.invoice.InvoicePaymentBlock; +import com.demcha.compose.document.templates.data.invoice.InvoiceRecipient; +import com.demcha.compose.document.templates.data.invoice.InvoiceServiceLines; +import com.demcha.compose.document.templates.data.invoice.InvoiceSummaryBlock; +import com.demcha.compose.document.templates.data.invoice.InvoiceTotalsBlock; +import com.demcha.compose.document.templates.data.invoice.StructuredInvoiceData; + +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.List; + +/** + * Shared fixture data for the {@link WorkspaceInvoice} gates — the SAME + * document feeds the pixel parity test and the layout snapshot test, so a + * geometry shift the pixel budget absorbs still trips the exact snapshot, and + * vice versa. + */ +final class WorkspaceInvoiceFixtures { + + private WorkspaceInvoiceFixtures() { + } + + /** + * The canonical one-page invoice — six metadata rows, two addressed parties + * each printing a registration, four marked service lines, eight payment + * fields, two summed rows and a closing line with an address inside it. + * + * @return the document + */ + static StructuredInvoiceData canonicalInvoice() { + return new StructuredInvoiceData( + brand(), supplier(), masthead(), billTo(), shipTo(), + new InvoiceSummaryBlock("", "", ""), + serviceLines(), totals(), payment(), notes(), "USD"); + } + + /** No logo: the wordmark is what a document without one falls back to. */ + static InvoiceBrand brand() { + return new InvoiceBrand(null, "kestrel", "", "", "", ""); + } + + static InvoiceContactBlock supplier() { + return new InvoiceContactBlock( + "Kestrel Collaboration, Inc.", + List.of("500 Howard Street, 6th Floor", "San Francisco, CA 94105", + "United States"), + "", "", "kestrel.example", + "", "", + "VAT ID:", "US 77-0560185"); + } + + static InvoiceMasthead masthead() { + List entries = new ArrayList<>(); + entries.add(new InvoiceMasthead.Entry("Invoice Number:", "INV-2024-0003521", true)); + entries.add(new InvoiceMasthead.Entry("Invoice Date:", "27 May 2024", false)); + entries.add(new InvoiceMasthead.Entry("Billing Period:", "1 - 31 May 2024", false)); + entries.add(new InvoiceMasthead.Entry("Workspace ID:", "T04FQ9L2M", false)); + entries.add(new InvoiceMasthead.Entry("Payment Terms:", "Net 30", false)); + entries.add(new InvoiceMasthead.Entry("Due Date:", "26 June 2024", false)); + return new InvoiceMasthead("INVOICE", entries); + } + + /** The registration is the labelled pair printed under the address. */ + static InvoiceRecipient billTo() { + return new InvoiceRecipient("BILL TO", "Bright Future Ltd.", "", + List.of("45 King Street", "Manchester M2 7AZ", "United Kingdom"), + "", "", "VAT ID:", "GB 987 6543 21"); + } + + static InvoiceRecipient shipTo() { + return new InvoiceRecipient("SHIP TO", "Bright Future Ltd.", "", + List.of("Unit 4, Riverside Park", "Leeds LS1 4AP", "United Kingdom"), + "", "", "", ""); + } + + static InvoiceServiceLines serviceLines() { + // The currency is not in the labels: the preset names it once per money + // column, so a document states the code and nothing else. + InvoiceServiceLines.Columns columns = new InvoiceServiceLines.Columns( + "", "DESCRIPTION", "PLAN / SERVICE", "QTY", "UNIT PRICE", "AMOUNT", ""); + List lines = new ArrayList<>(); + lines.add(line(1, "Kestrel Business+", "Workspace subscription", + "Business+ Annual", "50", "Users", "8.75", "437.50", "grid")); + lines.add(line(2, "Enterprise Key Management", "Customer-managed encryption keys", + "Add-on", "1", "", "100.00", "100.00", "search")); + lines.add(line(3, "Priority Support", "Named engineer and response target", + "Standard", "1", "", "50.00", "50.00", "shield")); + lines.add(line(4, "Workflow Automation", "Templates and scheduled runs", + "Included", "1", "", "0.00", "0.00", "grid")); + return new InvoiceServiceLines(columns, lines); + } + + private static InvoiceServiceLines.Line line(int number, String title, String description, + String plan, String quantity, String unit, + String unitPrice, String amount, String icon) { + return new InvoiceServiceLines.Line(number, title, description, plan, + new BigDecimal(quantity), unit, decimal(unitPrice), decimal(amount), "", icon); + } + + static InvoiceTotalsBlock totals() { + List rows = new ArrayList<>(); + rows.add(new InvoiceTotalsBlock.Row("Subtotal", decimal("587.50"))); + rows.add(new InvoiceTotalsBlock.Row("Tax (0%)", decimal("0.00"))); + return new InvoiceTotalsBlock(rows, "TOTAL DUE", decimal("587.50")); + } + + static InvoicePaymentBlock payment() { + List fields = new ArrayList<>(); + fields.add(new InvoicePaymentBlock.Field("Bank Name:", "Bay Union Bank, N.A.")); + fields.add(new InvoicePaymentBlock.Field("Account Name:", "Kestrel Collaboration, Inc.")); + fields.add(new InvoicePaymentBlock.Field("Account Number:", "000000123456789")); + fields.add(new InvoicePaymentBlock.Field("Routing (ABA):", "021000021")); + fields.add(new InvoicePaymentBlock.Field("SWIFT / BIC:", "BAYUUS33")); + fields.add(new InvoicePaymentBlock.Field("Currency:", "USD")); + fields.add(new InvoicePaymentBlock.Field("Reference:", "INV-2024-0003521")); + fields.add(new InvoicePaymentBlock.Field("Bank Address:", "1 Market Plaza, San Francisco")); + return new InvoicePaymentBlock("PAYMENT DETAILS", fields, + "Please include the invoice number in your payment reference.", + "PAYMENT DUE BY", + "26 June 2024", + "", ""); + } + + static InvoiceNotesBlock notes() { + return new InvoiceNotesBlock("Thank you for using Kestrel.", + List.of("Questions about this invoice? Reach us at billing@kestrel.example " + + "and we will get back to you within one business day."), + "billing@kestrel.example", ""); + } + + private static BigDecimal decimal(String printed) { + return new BigDecimal(printed.replace(",", "")); + } +} diff --git a/qa/src/test/java/com/demcha/compose/document/templates/invoice/presets/WorkspaceInvoiceLayoutSnapshotTest.java b/qa/src/test/java/com/demcha/compose/document/templates/invoice/presets/WorkspaceInvoiceLayoutSnapshotTest.java new file mode 100644 index 000000000..74c3e25fe --- /dev/null +++ b/qa/src/test/java/com/demcha/compose/document/templates/invoice/presets/WorkspaceInvoiceLayoutSnapshotTest.java @@ -0,0 +1,33 @@ +package com.demcha.compose.document.templates.invoice.presets; + +import com.demcha.compose.GraphCompose; +import com.demcha.compose.document.api.DocumentSession; +import com.demcha.compose.document.templates.TemplateTestSupport; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Exact layout snapshot gate for {@link WorkspaceInvoice} — freezes the resolved + * geometry of the canonical one-page invoice, which the pixel budget cannot + * see: a small column or spacing shift stays under the visual-diff budget but + * changes the snapshot. + * + *

The preset owns its page geometry, so the session starts unconfigured.

+ * + *

Refresh with {@code -Dgraphcompose.updateSnapshots=true} after a + * deliberate layout change, and commit the JSON with the change.

+ */ +class WorkspaceInvoiceLayoutSnapshotTest { + + @Test + void canonicalInvoiceMatchesLayoutSnapshot() throws Exception { + try (DocumentSession session = GraphCompose.document().create()) { + WorkspaceInvoice.create().compose(session, + WorkspaceInvoiceFixtures.canonicalInvoice()); + assertThat(session.layoutSnapshot().totalPages()).as("total pages").isEqualTo(1); + TemplateTestSupport.assertCanonicalSnapshot( + session, "workspace_invoice_layout", "invoice"); + } + } +} diff --git a/qa/src/test/java/com/demcha/compose/document/templates/invoice/presets/WorkspaceInvoiceSmokeTest.java b/qa/src/test/java/com/demcha/compose/document/templates/invoice/presets/WorkspaceInvoiceSmokeTest.java new file mode 100644 index 000000000..8dad4bc60 --- /dev/null +++ b/qa/src/test/java/com/demcha/compose/document/templates/invoice/presets/WorkspaceInvoiceSmokeTest.java @@ -0,0 +1,269 @@ +package com.demcha.compose.document.templates.invoice.presets; + +import com.demcha.compose.GraphCompose; +import com.demcha.compose.document.api.DocumentSession; +import com.demcha.compose.document.templates.api.DocumentTemplate; +import com.demcha.compose.document.templates.data.invoice.InvoiceBrand; +import com.demcha.compose.document.templates.data.invoice.InvoiceNotesBlock; +import com.demcha.compose.document.templates.data.invoice.InvoicePaymentBlock; +import com.demcha.compose.document.templates.data.invoice.InvoiceRecipient; +import com.demcha.compose.document.templates.data.invoice.InvoiceServiceLines; +import com.demcha.compose.document.templates.data.invoice.InvoiceSummaryBlock; +import com.demcha.compose.document.templates.data.invoice.StructuredInvoiceData; +import org.apache.pdfbox.Loader; +import org.apache.pdfbox.pdmodel.PDDocument; +import org.apache.pdfbox.pdmodel.PDPage; +import org.apache.pdfbox.pdmodel.interactive.action.PDActionURI; +import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotation; +import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationLink; +import org.apache.pdfbox.text.PDFTextStripper; +import org.junit.jupiter.api.Test; + +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +/** + * Smoke test for {@link WorkspaceInvoice} — proves the preset renders a + * {@link StructuredInvoiceData} end-to-end with its packaged marks, names the + * currency once per money column rather than on every figure, writes a quantity + * with what it counts, prints a billed party's registration under its address, + * reports an unknown mark as a data error, repeats the table header when the + * lines run past a page, and carries the closing address as a link annotation, + * which moves no pixel and no layout node so neither gate would notice it going + * missing. + */ +class WorkspaceInvoiceSmokeTest { + + private static byte[] render(StructuredInvoiceData data) throws Exception { + // The preset owns its page geometry, so the session starts unconfigured. + try (DocumentSession session = GraphCompose.document().create()) { + WorkspaceInvoice.create().compose(session, data); + assertThat(session.roots()).isNotEmpty(); + byte[] pdfBytes = session.toPdfBytes(); + assertThat(pdfBytes).isNotEmpty(); + return pdfBytes; + } + } + + private static String textOf(byte[] pdfBytes) throws Exception { + try (PDDocument document = Loader.loadPDF(pdfBytes)) { + return new PDFTextStripper().getText(document); + } + } + + private static int pagesOf(StructuredInvoiceData data) throws Exception { + try (DocumentSession session = GraphCompose.document().create()) { + WorkspaceInvoice.create().compose(session, data); + return session.layoutSnapshot().totalPages(); + } + } + + private static List linkTargets(byte[] pdfBytes) throws Exception { + List targets = new ArrayList<>(); + try (PDDocument document = Loader.loadPDF(pdfBytes)) { + for (PDPage page : document.getPages()) { + for (PDAnnotation annotation : page.getAnnotations()) { + if (annotation instanceof PDAnnotationLink link + && link.getAction() instanceof PDActionURI uri) { + targets.add(uri.getURI()); + } + } + } + } + return targets; + } + + /** The canonical invoice with its service lines replaced. */ + private static StructuredInvoiceData withLines(InvoiceServiceLines lines) { + StructuredInvoiceData base = WorkspaceInvoiceFixtures.canonicalInvoice(); + return new StructuredInvoiceData(base.brand(), base.supplier(), base.masthead(), + base.billTo(), base.shipTo(), base.summary(), lines, base.totals(), + base.payment(), base.notes(), base.currencyCode()); + } + + @Test + void exposesStableIdentity() { + DocumentTemplate template = WorkspaceInvoice.create(); + assertThat(template.id()).isEqualTo(WorkspaceInvoice.ID); + assertThat(template.displayName()).isEqualTo(WorkspaceInvoice.DISPLAY_NAME); + } + + @Test + void rendersCanonicalInvoiceWithPackagedMarks() throws Exception { + render(WorkspaceInvoiceFixtures.canonicalInvoice()); + } + + @Test + void canonicalRenderCarriesEveryBlocksText() throws Exception { + String text = textOf(render(WorkspaceInvoiceFixtures.canonicalInvoice())); + assertThat(text) + .contains("INVOICE") + .contains("Kestrel Collaboration, Inc.") + .contains("Invoice Number:") + .contains("BILL TO") + .contains("SHIP TO") + .contains("Bright Future Ltd.") + .contains("Kestrel Business+") + .contains("PAYMENT DETAILS") + .contains("BAYUUS33") + .contains("TOTAL DUE") + .contains("PAYMENT DUE BY") + .contains("Thank you for using Kestrel."); + } + + @Test + void bothPartiesPrintTheirOwnRegistration() throws Exception { + // The supplier's sits under its address in the header, the billed + // party's under its address in the split — both labelled pairs. + String text = textOf(render(WorkspaceInvoiceFixtures.canonicalInvoice())); + assertThat(text).contains("US 77-0560185").contains("GB 987 6543 21"); + } + + @Test + void aPartyWithNoRegistrationPrintsNoLabel() throws Exception { + // The ship-to has none, and a label over an absence would be worse than + // no row at all — so there is exactly one of each in the split. + String text = textOf(render(WorkspaceInvoiceFixtures.canonicalInvoice())); + assertThat(text.split("VAT ID:", -1)).hasSize(3); + } + + @Test + void theCurrencyIsNamedOncePerColumnAndNotOnEveryFigure() throws Exception { + // This design states the currency in the column head; the figures under + // it are bare, and only the total carries the code. + String text = textOf(render(WorkspaceInvoiceFixtures.canonicalInvoice())); + assertThat(text) + .contains("UNIT PRICE (USD)") + .contains("AMOUNT (USD)") + .contains("USD 587.50") + .doesNotContain("$437.50"); + assertThat(text).contains("437.50"); + } + + @Test + void aQuantityIsWrittenWithWhatItCounts() throws Exception { + String text = textOf(render(WorkspaceInvoiceFixtures.canonicalInvoice())); + assertThat(text).contains("50 Users"); + } + + @Test + void aDocumentWithNoLogoFallsBackToItsName() throws Exception { + // The preset carries no mark of its own, so a document that brings only + // a name still gets a lockup — and the closing band always sets it. + assertThat(textOf(render(WorkspaceInvoiceFixtures.canonicalInvoice()))) + .contains("kestrel"); + } + + @Test + void theClosingAddressIsClickable() throws Exception { + assertThat(linkTargets(render(WorkspaceInvoiceFixtures.canonicalInvoice()))) + .contains("mailto:billing@kestrel.example") + .contains("https://kestrel.example"); + } + + @Test + void anUnknownMarkIsReportedAsADataError() { + InvoiceServiceLines wrong = new InvoiceServiceLines( + WorkspaceInvoiceFixtures.serviceLines().columns(), + List.of(new InvoiceServiceLines.Line(1, "Telepathy", "Reading minds", "Add-on", + BigDecimal.ONE, "", BigDecimal.TEN, BigDecimal.TEN, "", "telescope"))); + + assertThatThrownBy(() -> render(withLines(wrong))) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("telescope") + .hasMessageContaining("shield"); + } + + @Test + void aLineWithoutAMarkIsDrawnWithoutOne() throws Exception { + InvoiceServiceLines unmarked = new InvoiceServiceLines( + WorkspaceInvoiceFixtures.serviceLines().columns(), + List.of(new InvoiceServiceLines.Line(1, "Consulting", "A day of it", "Standard", + BigDecimal.ONE, "", new BigDecimal("1200"), new BigDecimal("1200"), ""))); + + assertThat(textOf(render(withLines(unmarked)))).contains("Consulting"); + } + + @Test + void aLongerInvoiceRunsOnAndRepeatsItsTableHeader() throws Exception { + List many = new ArrayList<>(); + for (int index = 1; index <= 30; index++) { + many.add(new InvoiceServiceLines.Line(index, "Service " + index, + "A line of description for it", "Standard", + BigDecimal.ONE, "", new BigDecimal("120"), new BigDecimal("120"), + "", "grid")); + } + StructuredInvoiceData longer = withLines(new InvoiceServiceLines( + WorkspaceInvoiceFixtures.serviceLines().columns(), many)); + + assertThat(pagesOf(longer)).isGreaterThan(1); + byte[] pdfBytes = render(longer); + assertThat(textOf(pdfBytes)).contains("Service 30"); + try (PDDocument document = Loader.loadPDF(pdfBytes)) { + PDFTextStripper stripper = new PDFTextStripper(); + stripper.setStartPage(2); + stripper.setEndPage(2); + assertThat(stripper.getText(document)) + .as("the second page names its columns") + .contains("DESCRIPTION") + .contains("PLAN / SERVICE"); + } + } + + @Test + void everyPageCarriesItsNumber() throws Exception { + List many = new ArrayList<>(); + for (int index = 1; index <= 30; index++) { + many.add(new InvoiceServiceLines.Line(index, "Service " + index, "", "Standard", + BigDecimal.ONE, "", new BigDecimal("120"), new BigDecimal("120"), + "", "grid")); + } + String text = textOf(render(withLines(new InvoiceServiceLines( + WorkspaceInvoiceFixtures.serviceLines().columns(), many)))); + assertThat(text).contains("Page 1 of").contains("Page 2 of"); + } + + @Test + void anInvoiceWithNoShipToDrawsOneParty() throws Exception { + StructuredInvoiceData base = WorkspaceInvoiceFixtures.canonicalInvoice(); + StructuredInvoiceData single = new StructuredInvoiceData(base.brand(), base.supplier(), + base.masthead(), base.billTo(), + new InvoiceRecipient("", "", "", List.of(), "", ""), + base.summary(), base.serviceLines(), base.totals(), base.payment(), + base.notes(), base.currencyCode()); + + String text = textOf(render(single)); + assertThat(text).contains("BILL TO").doesNotContain("SHIP TO"); + } + + @Test + void anInvoiceWithNoNoteDropsTheCardsRuleWithIt() throws Exception { + // The rule exists to separate the fields from the note; with no note it + // would close the card on nothing. + StructuredInvoiceData base = WorkspaceInvoiceFixtures.canonicalInvoice(); + InvoicePaymentBlock quiet = new InvoicePaymentBlock( + base.payment().heading(), base.payment().fields(), "", + base.payment().dueNotice(), base.payment().dueNoticeEmphasis(), "", ""); + StructuredInvoiceData bare = new StructuredInvoiceData(base.brand(), base.supplier(), + base.masthead(), base.billTo(), base.shipTo(), base.summary(), + base.serviceLines(), base.totals(), quiet, base.notes(), base.currencyCode()); + + assertThat(textOf(render(bare))) + .doesNotContain("Please include the invoice number"); + } + + @Test + void anEmptyDocumentStillRenders() throws Exception { + // Every block absent: the preset draws its furniture and nothing else, + // rather than failing on the first missing field. + render(new StructuredInvoiceData(new InvoiceBrand(null, "", "", ""), null, null, + null, null, new InvoiceSummaryBlock("", "", ""), + new InvoiceServiceLines(null, List.of()), null, + new InvoicePaymentBlock("", List.of(), "", "", "", "", ""), + new InvoiceNotesBlock("", List.of(), "", ""), "")); + } +} diff --git a/qa/src/test/java/com/demcha/compose/document/templates/invoice/presets/WorkspaceInvoiceVisualParityTest.java b/qa/src/test/java/com/demcha/compose/document/templates/invoice/presets/WorkspaceInvoiceVisualParityTest.java new file mode 100644 index 000000000..0a72411b1 --- /dev/null +++ b/qa/src/test/java/com/demcha/compose/document/templates/invoice/presets/WorkspaceInvoiceVisualParityTest.java @@ -0,0 +1,41 @@ +package com.demcha.compose.document.templates.invoice.presets; + +import com.demcha.compose.GraphCompose; +import com.demcha.compose.document.api.DocumentSession; +import com.demcha.compose.testing.visual.PdfVisualRegression; +import org.junit.jupiter.api.Test; + +import java.nio.file.Path; + +/** + * Pixel-diff visual parity gate for {@link WorkspaceInvoice} — the canonical + * one-page invoice against a checked-in baseline. + * + *

Re-blessing baselines — after a deliberate visual change, + * re-run with {@code -Dgraphcompose.visual.approve=true} and commit the updated + * PNG with the change.

+ */ +class WorkspaceInvoiceVisualParityTest { + + private static final Path BASELINE_ROOT = Path.of( + "src", "test", "resources", "visual-baselines", "invoice-v2-layered"); + + private static final long PIXEL_DIFF_BUDGET = 50_000L; + private static final int PER_PIXEL_TOLERANCE = 8; + + @Test + void rendersWithinPixelDiffBudget() throws Exception { + byte[] pdfBytes; + try (DocumentSession document = GraphCompose.document().create()) { + WorkspaceInvoice.create().compose(document, + WorkspaceInvoiceFixtures.canonicalInvoice()); + pdfBytes = document.toPdfBytes(); + } + + PdfVisualRegression.standard() + .baselineRoot(BASELINE_ROOT) + .perPixelTolerance(PER_PIXEL_TOLERANCE) + .mismatchedPixelBudget(PIXEL_DIFF_BUDGET) + .assertMatchesBaseline("workspace_invoice", pdfBytes); + } +} diff --git a/qa/src/test/java/com/demcha/compose/document/templates/proposal/presets/EditorialProposalFixtures.java b/qa/src/test/java/com/demcha/compose/document/templates/proposal/presets/EditorialProposalFixtures.java new file mode 100644 index 000000000..ecc8e686a --- /dev/null +++ b/qa/src/test/java/com/demcha/compose/document/templates/proposal/presets/EditorialProposalFixtures.java @@ -0,0 +1,148 @@ +package com.demcha.compose.document.templates.proposal.presets; + +import com.demcha.compose.document.templates.data.proposal.ProposalAcceptance; +import com.demcha.compose.document.templates.data.proposal.ProposalBrand; +import com.demcha.compose.document.templates.data.proposal.ProposalDeliverables; +import com.demcha.compose.document.templates.data.proposal.ProposalGlance; +import com.demcha.compose.document.templates.data.proposal.ProposalGoals; +import com.demcha.compose.document.templates.data.proposal.ProposalInvestment; +import com.demcha.compose.document.templates.data.proposal.ProposalMetaLine; +import com.demcha.compose.document.templates.data.proposal.ProposalPhaseGrid; +import com.demcha.compose.document.templates.data.proposal.ProposalScope; +import com.demcha.compose.document.templates.data.proposal.ProposalSummaryBlock; +import com.demcha.compose.document.templates.data.proposal.ProposalTermsBlock; +import com.demcha.compose.document.templates.data.proposal.ProposalTitleLines; +import com.demcha.compose.document.templates.data.proposal.StructuredProposalData; +import com.demcha.compose.document.templates.data.proposal.StructuredProposalDocumentSpec; + +import java.util.List; + +/** + * Shared fixture data for the Editorial proposal gates — the SAME spec + * feeds the smoke, layout-snapshot, and pixel gates, so a geometry shift + * the pixel budget absorbs still trips the exact snapshot, and vice versa. + * + *

The canonical fixture is the preset's reference content: a two-page + * brand-refresh proposal exercising every band — all four glance facts + * (one with the optional note), four goal cells, five scope rows, both + * deliverable columns, the four-phase grid, an investment table with + * ordinary, subtotal and optional rows plus the total band, four terms, + * and the three-field signing card.

+ * + *

Kept in lockstep with the examples module's + * {@code EditorialProposalSampleData} — the two modules cannot share a + * source file, so a content change here belongs there too.

+ */ +final class EditorialProposalFixtures { + + private EditorialProposalFixtures() { + } + + static StructuredProposalDocumentSpec canonicalProposal() { + return StructuredProposalDocumentSpec.from(StructuredProposalData.builder() + .brand(new ProposalBrand("N", "NORTHLINE", "STUDIO", "PROPOSAL", + "northlinestudio.com", "NORTHLINE STUDIO")) + .title(new ProposalTitleLines("Proposal —", "Brand Refresh &", + "Website Redesign")) + .meta(new ProposalMetaLine("PREPARED FOR ASTERA HEALTH CO.", + "PREPARED BY NORTHLINE STUDIO", "25 AUGUST 2026")) + .executiveSummary(new ProposalSummaryBlock("Executive Summary", "", List.of( + "Thank you for the opportunity to partner with Astera Health Co. " + + "on your next chapter. This proposal outlines our approach to " + + "refreshing your brand identity and redesigning your website to " + + "better reflect your mission, elevate credibility, and deliver a " + + "seamless experience for the people you serve.", + "Our process blends strategic thinking with thoughtful design to " + + "create a cohesive brand system and a modern, conversion-focused " + + "website. The outcome will be a clear, confident presence that " + + "strengthens trust, improves engagement, and supports your " + + "growth goals."))) + .glance(new ProposalGlance("", List.of( + new ProposalGlance.Fact("fact-duration", "PROJECT DURATION", + "6 weeks", null), + new ProposalGlance.Fact("fact-start", "START WINDOW", + "September 2026", null), + new ProposalGlance.Fact("fact-contact", "PRIMARY CONTACT", + "Project Lead", null), + new ProposalGlance.Fact("fact-validity", "PROPOSAL VALIDITY", + "14 days", "(until 8 September 2026)")))) + .goals(new ProposalGoals("Project Goals", "goal-check", List.of( + new ProposalGoals.Goal("", + "Strengthen brand credibility and visual consistency."), + new ProposalGoals.Goal("", + "Improve website usability and drive meaningful action."), + new ProposalGoals.Goal("", + "Communicate your value clearly across all touchpoints."), + new ProposalGoals.Goal("", + "Build a scalable digital foundation for future growth.")))) + .scope(new ProposalScope("Scope of Work", "", List.of( + new ProposalScope.Item("01", "Discovery & Research", + "Stakeholder interviews, brand audit, competitor review, and " + + "audience insights to inform strategy."), + new ProposalScope.Item("02", "Visual Identity Refinement", + "Refine logo system, color palette, typography, and visual " + + "guidelines for a cohesive, modern identity."), + new ProposalScope.Item("03", "Website UX/UI Design", + "Design a responsive, accessibility-minded website with " + + "intuitive navigation and clear conversion pathways."), + new ProposalScope.Item("04", "Content Structure & Messaging", + "Define site architecture, key messaging, and content " + + "hierarchy to communicate value and build trust."), + new ProposalScope.Item("05", "Launch Support", + "Developer handoff, QA support, training, and go-live " + + "assistance to ensure a smooth launch.")))) + .deliverables(new ProposalDeliverables("Deliverables", "", + List.of("Brand strategy summary", + "Refined logo suite & brand guidelines", + "Color, typography & design system", + "Website UX wireframes", + "High-fidelity website designs"), + List.of("Responsive design for desktop, tablet & mobile", + "Content strategy & page templates", + "Developer-ready assets & specifications", + "Launch checklist & training session"))) + .timeline(new ProposalPhaseGrid("Timeline", "", + List.of("PHASE", "FOCUS", "DURATION", "OUTPUT"), List.of( + new ProposalPhaseGrid.Phase("01", "Discover", + "Research, audit, insights, and strategy", "1 week", + "Discovery report & creative direction"), + new ProposalPhaseGrid.Phase("02", "Design", + "Brand refinement and website UX/UI design", "2 weeks", + "Design system & high-fidelity designs"), + new ProposalPhaseGrid.Phase("03", "Develop Prep", + "Content structure, developer handoff & asset preparation", + "1 week", "Specs, assets & content framework"), + new ProposalPhaseGrid.Phase("04", "Launch Support", + "QA, training & go-live assistance", "2 weeks", + "QA sign-off & successful launch")))) + .investment(new ProposalInvestment("Investment", "", + "ITEM", "AMOUNT (GBP)", List.of( + new ProposalInvestment.Row("Discovery & Strategy", "£2,500", + ProposalInvestment.Role.NONE), + new ProposalInvestment.Row("Brand Identity Refinement", "£4,500", + ProposalInvestment.Role.NONE), + new ProposalInvestment.Row("Website UX/UI Design", "£7,000", + ProposalInvestment.Role.NONE), + new ProposalInvestment.Row("Content Structure & Messaging", "£2,000", + ProposalInvestment.Role.NONE), + new ProposalInvestment.Row("Launch Support", "£1,500", + ProposalInvestment.Role.NONE), + new ProposalInvestment.Row("Subtotal", "£17,500", + ProposalInvestment.Role.SUBTOTAL), + new ProposalInvestment.Row("Optional: 3 Months Post-Launch Support", + "£1,750", ProposalInvestment.Role.OPTIONAL)), + "TOTAL INVESTMENT", "£19,250")) + .terms(new ProposalTermsBlock("Terms", "", List.of( + "50% deposit is required to secure the project and schedule the " + + "kick-off.", + "Includes two rounds of revisions per major deliverable.", + "Final files and handoff delivered upon final payment.", + "Ownership of all final deliverables transfers to Astera Health Co. " + + "after full payment is received."))) + .acceptance(new ProposalAcceptance("Acceptance", "", + "By signing below, you agree to the scope, timeline, investment, " + + "and terms outlined in this proposal.", + List.of("Accepted by:", "Signature:", "Date:"))) + .build()); + } +} diff --git a/qa/src/test/java/com/demcha/compose/document/templates/proposal/presets/EditorialProposalLayoutSnapshotTest.java b/qa/src/test/java/com/demcha/compose/document/templates/proposal/presets/EditorialProposalLayoutSnapshotTest.java new file mode 100644 index 000000000..b9956ab81 --- /dev/null +++ b/qa/src/test/java/com/demcha/compose/document/templates/proposal/presets/EditorialProposalLayoutSnapshotTest.java @@ -0,0 +1,30 @@ +package com.demcha.compose.document.templates.proposal.presets; + +import com.demcha.compose.GraphCompose; +import com.demcha.compose.document.api.DocumentSession; +import com.demcha.compose.document.templates.TemplateTestSupport; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Exact layout snapshot gate for {@link EditorialProposal} — freezes the + * resolved geometry of both pages, including the natural page-one → page-two + * break the masthead's {@code keepWithNext} guarantees. + * + *

Refresh with {@code -Dgraphcompose.updateSnapshots=true} after a + * deliberate layout change, and commit the JSON with the change.

+ */ +class EditorialProposalLayoutSnapshotTest { + + @Test + void canonicalProposalMatchesLayoutSnapshot() throws Exception { + try (DocumentSession session = GraphCompose.document().create()) { + EditorialProposal.create().compose( + session, EditorialProposalFixtures.canonicalProposal()); + assertThat(session.layoutSnapshot().totalPages()).isEqualTo(2); + TemplateTestSupport.assertCanonicalSnapshot( + session, "editorial_proposal_layout", "proposal"); + } + } +} diff --git a/qa/src/test/java/com/demcha/compose/document/templates/proposal/presets/EditorialProposalSmokeTest.java b/qa/src/test/java/com/demcha/compose/document/templates/proposal/presets/EditorialProposalSmokeTest.java new file mode 100644 index 000000000..9cd973287 --- /dev/null +++ b/qa/src/test/java/com/demcha/compose/document/templates/proposal/presets/EditorialProposalSmokeTest.java @@ -0,0 +1,186 @@ +package com.demcha.compose.document.templates.proposal.presets; + +import com.demcha.compose.GraphCompose; +import com.demcha.compose.document.api.DocumentSession; +import com.demcha.compose.document.templates.api.DocumentTemplate; +import com.demcha.compose.document.templates.data.proposal.ProposalAcceptance; +import com.demcha.compose.document.templates.data.proposal.ProposalDeliverables; +import com.demcha.compose.document.templates.data.proposal.ProposalInvestment; +import com.demcha.compose.document.templates.data.proposal.ProposalGoals; +import com.demcha.compose.document.templates.data.proposal.ProposalPhaseGrid; +import com.demcha.compose.document.templates.data.proposal.ProposalScope; +import com.demcha.compose.document.templates.data.proposal.ProposalSummaryBlock; +import com.demcha.compose.document.templates.data.proposal.ProposalTermsBlock; +import com.demcha.compose.document.templates.data.proposal.StructuredProposalData; +import com.demcha.compose.document.templates.data.proposal.StructuredProposalDocumentSpec; +import org.apache.pdfbox.Loader; +import org.apache.pdfbox.pdmodel.PDDocument; +import org.apache.pdfbox.text.PDFTextStripper; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +/** + * Smoke test for {@link EditorialProposal} — proves the preset renders a + * {@link StructuredProposalDocumentSpec} end-to-end with its packaged SVG + * icon set, renders an empty document through its guards, puts the table + * content on the text layer where the layout snapshot cannot see it, and + * reports its two data contracts (unknown icon token, phase-grid header + * count) by name. + * + *

It also pins what the two proposal presets do and do not share: the + * document model and the four {@code fact-*} tokens, yes; the badge and + * goal tokens, no — each preset packages its own set.

+ */ +class EditorialProposalSmokeTest { + + private static byte[] render(StructuredProposalDocumentSpec spec) throws Exception { + // The preset owns its page geometry, so the session starts unconfigured. + try (DocumentSession session = GraphCompose.document().create()) { + EditorialProposal.create().compose(session, spec); + assertThat(session.roots()).isNotEmpty(); + byte[] pdfBytes = session.toPdfBytes(); + assertThat(pdfBytes).isNotEmpty(); + return pdfBytes; + } + } + + private static String textOf(byte[] pdfBytes) throws Exception { + try (PDDocument document = Loader.loadPDF(pdfBytes)) { + return new PDFTextStripper().getText(document); + } + } + + @Test + void exposesStableIdentity() { + DocumentTemplate template = EditorialProposal.create(); + assertThat(template.id()).isEqualTo(EditorialProposal.ID); + assertThat(template.displayName()).isEqualTo(EditorialProposal.DISPLAY_NAME); + } + + @Test + void rendersCanonicalProposalWithPackagedIcons() throws Exception { + render(EditorialProposalFixtures.canonicalProposal()); + } + + @Test + void rendersEmptyProposal() throws Exception { + // Exercises the guards: no goal cells, no phase grid, no signature + // row — and the blank icon tokens that carry no packaged mark. + render(StructuredProposalDocumentSpec.from(StructuredProposalData.builder().build())); + } + + @Test + void theDocumentLabelReachesTheTextLayerAsAWord() throws Exception { + // The tracked label used to be built by padding the string out with a + // space between every letter, so the file stored "P R O P O S A L". The + // tracking is on the style now and the word is a word. Neither the pixel + // nor the snapshot gate can see this: the label's paragraph takes its + // section's width, so the geometry does not move when the glyphs do. + String words = textOf(render(EditorialProposalFixtures.canonicalProposal())) + .replaceAll("\\s+", " "); + assertThat(words).as("the label, as written").contains("PROPOSAL"); + assertThat(words).as("and not padded out letter by letter") + .doesNotContain("P R O P O S A L"); + } + + @Test + void canonicalRenderCarriesTheTableText() throws Exception { + // The phase grid and the investment table are single leaf nodes in + // the layout snapshot, so their content is asserted on the text layer. + String text = textOf(render(EditorialProposalFixtures.canonicalProposal())); + assertThat(text) + .contains("Discovery report") + .contains("Brand Identity Refinement") + .contains("2 weeks") + .contains("TOTAL INVESTMENT") + .contains("£19,250"); + } + + @Test + void theSameDocumentRendersThroughBothProposalPresets() throws Exception { + // The point of the structured model: one document, two looks. The + // fact tokens are named alike in both packaged sets, so they stay in + // the document; the badge and goal tokens are not, so those are the + // ones a move has to clear. + StructuredProposalDocumentSpec spec = + StructuredProposalDocumentSpec.from(withoutSectionIconTokens( + EditorialProposalFixtures.canonicalProposal().proposal())); + String editorial = textOf(render(spec)); + byte[] northline; + try (DocumentSession session = GraphCompose.document().create()) { + NorthlineProposal.create().compose(session, spec); + northline = session.toPdfBytes(); + } + assertThat(textOf(northline)) + .contains("Executive Summary") + .contains("TOTAL INVESTMENT"); + assertThat(editorial).contains("Executive Summary"); + } + + /** + * The same document with the preset-scoped tokens cleared — every + * section badge and the goal mark — and the shared {@code fact-*} tokens + * left in place, since both packaged sets name those alike. + */ + private static StructuredProposalData withoutSectionIconTokens( + StructuredProposalData data) { + return StructuredProposalData.builder() + .brand(data.brand()) + .title(data.title()) + .meta(data.meta()) + .executiveSummary(new ProposalSummaryBlock(data.executiveSummary().heading(), + "", data.executiveSummary().paragraphs())) + .glance(data.glance()) + .goals(new ProposalGoals(data.goals().heading(), "", + data.goals().items().stream() + .map(g -> new ProposalGoals.Goal("", g.text())) + .toList())) + .scope(new ProposalScope(data.scope().heading(), "", data.scope().items())) + .deliverables(new ProposalDeliverables(data.deliverables().heading(), "", + data.deliverables().leftColumn(), data.deliverables().rightColumn())) + .timeline(new ProposalPhaseGrid(data.timeline().heading(), "", + data.timeline().columnHeaders(), data.timeline().phases())) + .investment(new ProposalInvestment(data.investment().heading(), "", + data.investment().itemHeader(), data.investment().amountHeader(), + data.investment().rows(), data.investment().totalLabel(), + data.investment().totalAmount())) + .terms(new ProposalTermsBlock(data.terms().heading(), "", data.terms().items())) + .acceptance(new ProposalAcceptance(data.acceptance().heading(), "", + data.acceptance().statement(), data.acceptance().fields())) + .build(); + } + + @Test + void rejectsUnknownIconTokenByName() { + StructuredProposalDocumentSpec spec = StructuredProposalDocumentSpec.from( + StructuredProposalData.builder() + .goals(new ProposalGoals("Project Goals", "no-such-icon", + List.of(new ProposalGoals.Goal("", "A goal.")))) + .build()); + try (DocumentSession session = GraphCompose.document().create()) { + assertThatThrownBy(() -> EditorialProposal.create().compose(session, spec)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("no-such-icon"); + } + } + + @Test + void phaseGridRejectsHeaderCountMismatchByName() { + StructuredProposalDocumentSpec spec = StructuredProposalDocumentSpec.from( + StructuredProposalData.builder() + .timeline(new ProposalPhaseGrid("Timeline", "", + List.of("PHASE", "FOCUS", "DURATION"), + List.of(new ProposalPhaseGrid.Phase("01", "Discover", + "Research", "1 week", "Report")))) + .build()); + try (DocumentSession session = GraphCompose.document().create()) { + assertThatThrownBy(() -> EditorialProposal.create().compose(session, spec)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("one header per column"); + } + } +} diff --git a/qa/src/test/java/com/demcha/compose/document/templates/proposal/presets/EditorialProposalVisualParityTest.java b/qa/src/test/java/com/demcha/compose/document/templates/proposal/presets/EditorialProposalVisualParityTest.java new file mode 100644 index 000000000..f3033ff56 --- /dev/null +++ b/qa/src/test/java/com/demcha/compose/document/templates/proposal/presets/EditorialProposalVisualParityTest.java @@ -0,0 +1,42 @@ +package com.demcha.compose.document.templates.proposal.presets; + +import com.demcha.compose.GraphCompose; +import com.demcha.compose.document.api.DocumentSession; +import com.demcha.compose.testing.visual.PdfVisualRegression; +import org.junit.jupiter.api.Test; + +import java.nio.file.Path; + +/** + * Pixel-diff visual parity gate for {@link EditorialProposal} — both pages + * of the canonical fixture against checked-in baselines. + * + *

Re-blessing baselines — after a deliberate visual + * change, re-run with {@code -Dgraphcompose.visual.approve=true} and commit + * the updated PNGs with the change. Baselines live under + * {@code src/test/resources/visual-baselines/proposal-v2-layered/}.

+ */ +class EditorialProposalVisualParityTest { + + private static final Path BASELINE_ROOT = Path.of( + "src", "test", "resources", "visual-baselines", "proposal-v2-layered"); + + private static final long PIXEL_DIFF_BUDGET = 50_000L; + private static final int PER_PIXEL_TOLERANCE = 8; + + @Test + void rendersWithinPixelDiffBudget() throws Exception { + byte[] pdfBytes; + try (DocumentSession document = GraphCompose.document().create()) { + EditorialProposal.create().compose( + document, EditorialProposalFixtures.canonicalProposal()); + pdfBytes = document.toPdfBytes(); + } + + PdfVisualRegression.standard() + .baselineRoot(BASELINE_ROOT) + .perPixelTolerance(PER_PIXEL_TOLERANCE) + .mismatchedPixelBudget(PIXEL_DIFF_BUDGET) + .assertMatchesBaseline("editorial_proposal", pdfBytes); + } +} diff --git a/qa/src/test/java/com/demcha/compose/document/templates/proposal/presets/IndigoProposalFixtures.java b/qa/src/test/java/com/demcha/compose/document/templates/proposal/presets/IndigoProposalFixtures.java new file mode 100644 index 000000000..1334e29b6 --- /dev/null +++ b/qa/src/test/java/com/demcha/compose/document/templates/proposal/presets/IndigoProposalFixtures.java @@ -0,0 +1,112 @@ +package com.demcha.compose.document.templates.proposal.presets; + +import com.demcha.compose.document.templates.data.proposal.ProposalAttention; +import com.demcha.compose.document.templates.data.proposal.ProposalBrand; +import com.demcha.compose.document.templates.data.proposal.ProposalFooter; +import com.demcha.compose.document.templates.data.proposal.ProposalGlance; +import com.demcha.compose.document.templates.data.proposal.ProposalInvestment; +import com.demcha.compose.document.templates.data.proposal.ProposalMetaLine; +import com.demcha.compose.document.templates.data.proposal.ProposalRecipient; +import com.demcha.compose.document.templates.data.proposal.ProposalScope; +import com.demcha.compose.document.templates.data.proposal.ProposalTermsBlock; +import com.demcha.compose.document.templates.data.proposal.ProposalTitleLines; +import com.demcha.compose.document.templates.data.proposal.StructuredProposalData; +import com.demcha.compose.document.templates.data.proposal.StructuredProposalDocumentSpec; + +import java.util.List; + +/** + * Shared fixture data for the Indigo proposal gates — the SAME spec feeds the + * smoke, layout-snapshot and pixel gates, so a geometry shift the pixel budget + * absorbs still trips the exact snapshot, and vice versa. + * + *

The canonical fixture is the preset's reference content: an addressed head + * with a person to reply to, a four-line headline over a three-line standfirst, + * four header tiles, the tinted band with a four-line paragraph and four feature + * tiles, five numbered steps beside four priced rows and a total, three notes, + * and a foot with an address and three channels.

+ * + *

The line counts are part of the fixture. The preset places + * every block at a stated position, and the blocks that wrap — the standfirst, + * the band's paragraph, the plan's opening and the notes list — declare how many + * lines they set in, because the engine cannot be asked at compose time. Editing + * one of them to a different line count moves everything below it, which both + * gates will report. Change the text and the map together, or keep the + * length.

+ */ +final class IndigoProposalFixtures { + + private IndigoProposalFixtures() { + } + + /** One page: a financial-infrastructure partnership offered to one customer. */ + static StructuredProposalDocumentSpec canonicalProposal() { + return StructuredProposalDocumentSpec.from(StructuredProposalData.builder() + .brand(new ProposalBrand("K", "Kestrel", "", "PROPOSAL", + "kestrel.example", "Kestrel Payments Ltd")) + .recipient(new ProposalRecipient("PREPARED FOR", "Bright Future Ltd.", + List.of("45 King Street", "Manchester, M2 4WU", "United Kingdom"))) + .attention(new ProposalAttention("ATTN", "Priya Raman", "Founder", + "priya@brightfuture.example", "+44 7700 900123")) + .title(ProposalTitleLines.of("", List.of( + "Proposal for", + "Financial Infrastructure", + "& Embedded Finance", + "Partnership"), + List.of("Empowering Bright Future Ltd. with modern financial" + + " infrastructure, global capabilities, and a seamless" + + " experience for your customers."))) + .meta(new ProposalMetaLine(List.of( + new ProposalMetaLine.Entry("date", "DATE", "27 May 2026"), + new ProposalMetaLine.Entry("identifier", "PROPOSAL ID", + "KES-2026-0527-01"), + new ProposalMetaLine.Entry("valid", "VALID UNTIL", "26 June 2026"), + new ProposalMetaLine.Entry("prepared-by", "PREPARED BY", + "Kestrel Business Team")))) + .glance(new ProposalGlance("ABOUT KESTREL BUSINESS", List.of( + new ProposalGlance.Fact("global", "Global", "Scale", ""), + new ProposalGlance.Fact("infrastructure", "Modern", + "Infrastructure", ""), + new ProposalGlance.Fact("secure", "Secure &", "Compliant", ""), + new ProposalGlance.Fact("apis", "Developer", "First APIs", "")), + "Kestrel Business provides modern financial infrastructure for" + + " forward-thinking companies. From multi-currency accounts" + + " and payments to cards, FX, and embedded finance APIs —" + + " all in one platform.")) + .scope(new ProposalScope("PROJECT OVERVIEW", "", List.of( + new ProposalScope.Item("01", "Multi-currency business accounts", + "Hold, send, receive in 30+ currencies"), + new ProposalScope.Item("02", "Corporate cards & expenses", + "Issue physical & virtual cards with smart controls"), + new ProposalScope.Item("03", "Payments & collections", + "Local & global payments, direct debits, card acquiring"), + new ProposalScope.Item("04", "Embedded finance APIs", + "Build financial products directly into your platform"), + new ProposalScope.Item("05", "Compliance & risk", + "KYC, AML, fraud monitoring and regulatory coverage")), + "This proposal outlines a tailored financial infrastructure solution" + + " to support Bright Future Ltd. in enabling fast, secure and" + + " scalable financial services for your business and your" + + " customers.")) + .investment(new ProposalInvestment("INVESTMENT SUMMARY", "", "", "", List.of( + new ProposalInvestment.Row("Implementation & Onboarding", + "£2,500", null), + new ProposalInvestment.Row("Platform Access & APIs", + "£3,000", null), + new ProposalInvestment.Row("Transaction Fees (est.)", "Variable", null), + new ProposalInvestment.Row("Support & Account Management", + "Included", null)), + "Estimated First Year Investment", "£5,500")) + .terms(new ProposalTermsBlock("NOTES", "", List.of( + "Pricing excludes applicable taxes.", + "Transaction fees are based on usage and volume.", + "This proposal is valid for 30 days from the date above."))) + .footer(new ProposalFooter("Kestrel Payments Ltd", + List.of("7 Harbour Circus, Canary Wharf, London E14 4HD," + + " United Kingdom"), + List.of("kestrel.example/business", "business@kestrel.example", + "+44 20 3322 8352"), + "")) + .build()); + } +} diff --git a/qa/src/test/java/com/demcha/compose/document/templates/proposal/presets/IndigoProposalLayoutSnapshotTest.java b/qa/src/test/java/com/demcha/compose/document/templates/proposal/presets/IndigoProposalLayoutSnapshotTest.java new file mode 100644 index 000000000..78f1cb9bb --- /dev/null +++ b/qa/src/test/java/com/demcha/compose/document/templates/proposal/presets/IndigoProposalLayoutSnapshotTest.java @@ -0,0 +1,31 @@ +package com.demcha.compose.document.templates.proposal.presets; + +import com.demcha.compose.GraphCompose; +import com.demcha.compose.document.api.DocumentSession; +import com.demcha.compose.document.templates.TemplateTestSupport; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Exact layout snapshot gate for {@link IndigoProposal} — freezes the resolved + * geometry of the one-page sheet, whose blocks are placed to a stated position + * rather than stacked, so a shift the pixel budget would absorb still shows up + * here as a moved node. + * + *

Refresh with {@code -Dgraphcompose.updateSnapshots=true} after a deliberate + * layout change, and commit the JSON with the change.

+ */ +class IndigoProposalLayoutSnapshotTest { + + @Test + void canonicalProposalMatchesLayoutSnapshot() throws Exception { + try (DocumentSession session = GraphCompose.document().create()) { + IndigoProposal.create().compose( + session, IndigoProposalFixtures.canonicalProposal()); + assertThat(session.layoutSnapshot().totalPages()).isEqualTo(1); + TemplateTestSupport.assertCanonicalSnapshot( + session, "indigo_proposal_layout", "proposal"); + } + } +} diff --git a/qa/src/test/java/com/demcha/compose/document/templates/proposal/presets/IndigoProposalSmokeTest.java b/qa/src/test/java/com/demcha/compose/document/templates/proposal/presets/IndigoProposalSmokeTest.java new file mode 100644 index 000000000..a4998105b --- /dev/null +++ b/qa/src/test/java/com/demcha/compose/document/templates/proposal/presets/IndigoProposalSmokeTest.java @@ -0,0 +1,279 @@ +package com.demcha.compose.document.templates.proposal.presets; + +import com.demcha.compose.GraphCompose; +import com.demcha.compose.document.api.DocumentSession; +import com.demcha.compose.document.snapshot.LayoutNodeSnapshot; +import com.demcha.compose.document.templates.api.DocumentTemplate; +import com.demcha.compose.document.templates.data.proposal.ProposalFooter; +import com.demcha.compose.document.templates.data.proposal.ProposalGlance; +import com.demcha.compose.document.templates.data.proposal.ProposalMetaLine; +import com.demcha.compose.document.templates.data.proposal.ProposalScope; +import com.demcha.compose.document.templates.data.proposal.StructuredProposalData; +import com.demcha.compose.document.templates.data.proposal.StructuredProposalDocumentSpec; +import org.apache.pdfbox.Loader; +import org.apache.pdfbox.pdmodel.PDDocument; +import org.apache.pdfbox.pdmodel.PDPage; +import org.apache.pdfbox.pdmodel.interactive.action.PDActionURI; +import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotation; +import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationLink; +import org.apache.pdfbox.text.PDFTextStripper; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import java.util.ArrayList; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +/** + * Smoke test for {@link IndigoProposal} — proves the preset renders a + * {@link StructuredProposalDocumentSpec} end-to-end with its packaged SVG icon + * set on one page, renders an empty document through its guards, makes the + * addressed person and the foot's channels followable, keeps a confidentiality + * line on the foot's own last line, and reports an unknown icon token by name. + */ +class IndigoProposalSmokeTest { + + /** A notice as one is actually written: a sentence, and not a URI. */ + private static final String NOTICE = + "Confidential — prepared for the addressee, not for onward circulation."; + + private static byte[] render(StructuredProposalDocumentSpec spec) throws Exception { + // The preset owns its page geometry, so the session starts unconfigured. + try (DocumentSession session = GraphCompose.document().create()) { + IndigoProposal.create().compose(session, spec); + assertThat(session.roots()).isNotEmpty(); + byte[] pdfBytes = session.toPdfBytes(); + assertThat(pdfBytes).isNotEmpty(); + return pdfBytes; + } + } + + private static String textOf(byte[] pdfBytes) throws Exception { + try (PDDocument document = Loader.loadPDF(pdfBytes)) { + return new PDFTextStripper().getText(document); + } + } + + private static List linkTargets(byte[] pdfBytes) throws Exception { + List targets = new ArrayList<>(); + try (PDDocument document = Loader.loadPDF(pdfBytes)) { + for (PDPage page : document.getPages()) { + for (PDAnnotation annotation : page.getAnnotations()) { + if (annotation instanceof PDAnnotationLink link + && link.getAction() instanceof PDActionURI uri) { + targets.add(uri.getURI()); + } + } + } + } + return targets; + } + + @Test + void exposesStableIdentity() { + DocumentTemplate template = IndigoProposal.create(); + assertThat(template.id()).isEqualTo(IndigoProposal.ID); + assertThat(template.displayName()).isEqualTo(IndigoProposal.DISPLAY_NAME); + } + + @Test + void rendersTheCanonicalProposalOnOnePage() throws Exception { + try (DocumentSession session = GraphCompose.document().create()) { + IndigoProposal.create().compose( + session, IndigoProposalFixtures.canonicalProposal()); + // The design is a one-page sheet, and the fixture is sized for it. + assertThat(session.layoutSnapshot().totalPages()).isEqualTo(1); + } + } + + @Test + void rendersEmptyProposal() throws Exception { + // Exercises the guards: no monogram in the foot's mark, no channels, no + // feature tiles, no steps, no priced rows, no total card, no notes. + render(StructuredProposalDocumentSpec.from(StructuredProposalData.builder().build())); + } + + @Test + void canonicalRenderCarriesTheMoneyOnTheTextLayer() throws Exception { + // The priced rows are composed table cells, which emit no placed node + // of their own — the text layer is where their content is assertable. + String text = textOf(render(IndigoProposalFixtures.canonicalProposal())); + assertThat(text) + .contains("Implementation & Onboarding") + .contains("£2,500") + .contains("Estimated First Year Investment") + .contains("£5,500"); + } + + @Test + void theAddressedPersonAndTheFootsChannelsAreFollowable() throws Exception { + // A proposal names a person to reply to and the channels its issuer + // answers on; a reader's device needs the form it can act on, and the + // foot's channels say only how they are printed. + assertThat(linkTargets(render(IndigoProposalFixtures.canonicalProposal()))) + .contains("mailto:priya@brightfuture.example") + .contains("tel:+447700900123") + .contains("https://kestrel.example/business") + .contains("mailto:business@kestrel.example") + .contains("tel:+442033228352"); + } + + @Test + void aConfidentialityLineClosesTheFootWithoutOpeningAPage() throws Exception { + // The design's foot is three lines and its last one sits against the + // bottom margin, so the line closes the channels rather than opening a + // fourth line that would carry the whole foot onto a second page. + StructuredProposalData base = IndigoProposalFixtures.canonicalProposal().proposal(); + StructuredProposalDocumentSpec spec = StructuredProposalDocumentSpec.from( + StructuredProposalData.builder() + .brand(base.brand()) + .recipient(base.recipient()) + .attention(base.attention()) + .title(base.title()) + .meta(base.meta()) + .glance(base.glance()) + .scope(base.scope()) + .investment(base.investment()) + .terms(base.terms()) + .footer(new ProposalFooter(base.footer().name(), + base.footer().addressLines(), base.footer().contacts(), + NOTICE)) + .build()); + try (DocumentSession session = GraphCompose.document().create()) { + IndigoProposal.create().compose(session, spec); + assertThat(session.layoutSnapshot().totalPages()).isEqualTo(1); + assertThat(textOf(session.toPdfBytes())).contains(NOTICE); + } + } + + @ParameterizedTest + @ValueSource(strings = {NOTICE, "Commercial-in-confidence"}) + void aNoticeIsSetAsProseAndNotAsSomewhereToBeSent(String notice) throws Exception { + // A confidentiality notice shares the foot's last line with the + // channels, and a channel is linked from the shape of what it prints. + // A notice is neither: the sentence would be a link target with spaces + // in it, and the single word — which is a perfectly good host name — + // would quietly become a site nobody meant to publish. + StructuredProposalData base = IndigoProposalFixtures.canonicalProposal().proposal(); + StructuredProposalDocumentSpec spec = StructuredProposalDocumentSpec.from( + StructuredProposalData.builder() + .brand(base.brand()) + .footer(new ProposalFooter(base.footer().name(), + base.footer().addressLines(), List.of(), notice)) + .build()); + byte[] pdfBytes = render(spec); + assertThat(textOf(pdfBytes)).contains(notice); + assertThat(linkTargets(pdfBytes)).isEmpty(); + } + + @ParameterizedTest + @ValueSource(ints = {5, 6, 7, 8, 14}) + void aPlanLongerThanTheDesignsNeverPrintsTheFootOverIt(int stepCount) throws Exception { + // Every block is placed to a stated position, so a plan that runs past + // where the map expects it would otherwise ask for a negative margin and + // draw the closing rule over the last steps. Six and seven are the + // counts that still fit the page — where an unclamped margin overlaps + // silently — and fourteen is where it has to take a second page. + StructuredProposalData base = IndigoProposalFixtures.canonicalProposal().proposal(); + List steps = new ArrayList<>(base.scope().items()); + for (int i = steps.size(); i < stepCount; i++) { + steps.add(new ProposalScope.Item(String.format("%02d", i + 1), + "Step " + (i + 1), "What the step covers")); + } + StructuredProposalDocumentSpec spec = StructuredProposalDocumentSpec.from( + withScope(base, new ProposalScope(base.scope().heading(), "", + steps.subList(0, stepCount), base.scope().intro()))); + try (DocumentSession session = GraphCompose.document().create()) { + IndigoProposal.create().compose(session, spec); + List nodes = session.layoutSnapshot().nodes(); + LayoutNodeSnapshot plan = node(nodes, "LowerRow"); + LayoutNodeSnapshot closing = node(nodes, "FooterRule"); + if (closing.startPage() == plan.startPage()) { + // y grows upward, so the rule's top edge must not reach above + // the plan's bottom edge. + assertThat(closing.computedY() + closing.placementHeight()) + .isLessThanOrEqualTo(plan.computedY() + 0.01); + } + assertThat(textOf(session.toPdfBytes())).contains("Kestrel Payments Ltd"); + } + } + + private static LayoutNodeSnapshot node(List nodes, String name) { + return nodes.stream() + .filter(n -> name.equals(n.entityName())) + .findFirst() + .orElseThrow(() -> new AssertionError("No node named " + name)); + } + + private static StructuredProposalData withScope(StructuredProposalData base, + ProposalScope scope) { + return StructuredProposalData.builder() + .brand(base.brand()) + .recipient(base.recipient()) + .attention(base.attention()) + .title(base.title()) + .meta(base.meta()) + .glance(base.glance()) + .scope(scope) + .investment(base.investment()) + .terms(base.terms()) + .footer(base.footer()) + .build(); + } + + @Test + void aTileThatNamesNoMarkIsStillTheTileTheDesignDraws() throws Exception { + // The shape is the design's and the mark inside it is the document's, + // so a fact naming none keeps its tile — and the row of tiles keeps the + // pitch the copy above it was measured against. Asserting the text would + // pass with the tile deleted, so the tile itself is what is asked for. + StructuredProposalDocumentSpec spec = StructuredProposalDocumentSpec.from( + StructuredProposalData.builder() + .glance(new ProposalGlance("ABOUT", List.of( + new ProposalGlance.Fact("", "Global", "Scale", "")), "")) + .build()); + try (DocumentSession session = GraphCompose.document().create()) { + IndigoProposal.create().compose(session, spec); + LayoutNodeSnapshot tile = node(session.layoutSnapshot().nodes(), "FeatureTile_0"); + assertThat(tile.placementWidth()).isGreaterThan(0); + assertThat(tile.placementHeight()).isGreaterThan(0); + assertThat(textOf(session.toPdfBytes())).contains("Global").contains("Scale"); + } + } + + @Test + void rejectsUnknownIconTokenByName() { + StructuredProposalDocumentSpec spec = StructuredProposalDocumentSpec.from( + StructuredProposalData.builder() + .glance(new ProposalGlance("ABOUT", List.of( + new ProposalGlance.Fact("no-such-icon", "Global", + "Scale", "")), "")) + .build()); + try (DocumentSession session = GraphCompose.document().create()) { + assertThatThrownBy(() -> IndigoProposal.create().compose(session, spec)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("no-such-icon"); + } + } + + @Test + void rejectsAMarkNamedOnTheBlockItWasNotCutFor() { + // The header's marks are drawn in the accent for a pale disc and the + // band's in white for a near-black tile, so a band token on a header + // disc would render as nothing at all. It is refused by name instead. + StructuredProposalDocumentSpec spec = StructuredProposalDocumentSpec.from( + StructuredProposalData.builder() + .meta(new ProposalMetaLine(List.of( + new ProposalMetaLine.Entry("global", "DATE", "27 May 2026")))) + .build()); + try (DocumentSession session = GraphCompose.document().create()) { + assertThatThrownBy(() -> IndigoProposal.create().compose(session, spec)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("global") + .hasMessageContaining("prepared-by"); + } + } +} diff --git a/qa/src/test/java/com/demcha/compose/document/templates/proposal/presets/IndigoProposalVisualParityTest.java b/qa/src/test/java/com/demcha/compose/document/templates/proposal/presets/IndigoProposalVisualParityTest.java new file mode 100644 index 000000000..fbd8ea50b --- /dev/null +++ b/qa/src/test/java/com/demcha/compose/document/templates/proposal/presets/IndigoProposalVisualParityTest.java @@ -0,0 +1,42 @@ +package com.demcha.compose.document.templates.proposal.presets; + +import com.demcha.compose.GraphCompose; +import com.demcha.compose.document.api.DocumentSession; +import com.demcha.compose.testing.visual.PdfVisualRegression; +import org.junit.jupiter.api.Test; + +import java.nio.file.Path; + +/** + * Pixel-diff visual parity gate for {@link IndigoProposal} — the canonical + * fixture's page against a checked-in baseline. + * + *

Re-blessing baselines — after a deliberate visual change, + * re-run with {@code -Dgraphcompose.visual.approve=true} and commit the updated + * PNGs with the change. Baselines live under + * {@code src/test/resources/visual-baselines/proposal-v2-layered/}.

+ */ +class IndigoProposalVisualParityTest { + + private static final Path BASELINE_ROOT = Path.of( + "src", "test", "resources", "visual-baselines", "proposal-v2-layered"); + + private static final long PIXEL_DIFF_BUDGET = 50_000L; + private static final int PER_PIXEL_TOLERANCE = 8; + + @Test + void rendersWithinPixelDiffBudget() throws Exception { + byte[] pdfBytes; + try (DocumentSession document = GraphCompose.document().create()) { + IndigoProposal.create().compose( + document, IndigoProposalFixtures.canonicalProposal()); + pdfBytes = document.toPdfBytes(); + } + + PdfVisualRegression.standard() + .baselineRoot(BASELINE_ROOT) + .perPixelTolerance(PER_PIXEL_TOLERANCE) + .mismatchedPixelBudget(PIXEL_DIFF_BUDGET) + .assertMatchesBaseline("indigo_proposal", pdfBytes); + } +} diff --git a/qa/src/test/java/com/demcha/compose/document/templates/proposal/presets/NorthlineProposalFixtures.java b/qa/src/test/java/com/demcha/compose/document/templates/proposal/presets/NorthlineProposalFixtures.java new file mode 100644 index 000000000..63844a745 --- /dev/null +++ b/qa/src/test/java/com/demcha/compose/document/templates/proposal/presets/NorthlineProposalFixtures.java @@ -0,0 +1,149 @@ +package com.demcha.compose.document.templates.proposal.presets; + +import com.demcha.compose.document.templates.data.proposal.ProposalAcceptance; +import com.demcha.compose.document.templates.data.proposal.ProposalBrand; +import com.demcha.compose.document.templates.data.proposal.ProposalDeliverables; +import com.demcha.compose.document.templates.data.proposal.ProposalGlance; +import com.demcha.compose.document.templates.data.proposal.ProposalGoals; +import com.demcha.compose.document.templates.data.proposal.ProposalInvestment; +import com.demcha.compose.document.templates.data.proposal.ProposalMetaLine; +import com.demcha.compose.document.templates.data.proposal.ProposalPhaseGrid; +import com.demcha.compose.document.templates.data.proposal.ProposalScope; +import com.demcha.compose.document.templates.data.proposal.ProposalSummaryBlock; +import com.demcha.compose.document.templates.data.proposal.ProposalTermsBlock; +import com.demcha.compose.document.templates.data.proposal.ProposalTitleLines; +import com.demcha.compose.document.templates.data.proposal.StructuredProposalData; +import com.demcha.compose.document.templates.data.proposal.StructuredProposalDocumentSpec; + +import java.util.List; + +/** + * Shared fixture data for the Northline proposal gates — the SAME spec + * feeds the smoke, layout-snapshot, and pixel gates, so a geometry shift + * the pixel budget absorbs still trips the exact snapshot, and vice versa. + * + *

The canonical fixture is the preset's reference content: a two-page + * brand-refresh proposal exercising every band — all four glance facts + * (one with the optional note), four goal cells, five scope rows, both + * deliverable columns, the four-phase grid, an investment table with + * ordinary, subtotal and optional rows plus the total band, four terms, + * and the three-field signing card.

+ * + *

Kept in lockstep with the examples module's + * {@code StructuredProposalSampleData} — the two modules cannot share a + * source file, so a content change here belongs there too.

+ */ +final class NorthlineProposalFixtures { + + private NorthlineProposalFixtures() { + } + + static StructuredProposalDocumentSpec canonicalProposal() { + return StructuredProposalDocumentSpec.from(StructuredProposalData.builder() + .brand(new ProposalBrand("N", "NORTHLINE", "STUDIO", "PROPOSAL", + "northlinestudio.com", "NORTHLINE STUDIO")) + .title(new ProposalTitleLines("Proposal –", "Brand Refresh &", + "Website Redesign")) + .meta(new ProposalMetaLine("PREPARED FOR ASTERA HEALTH CO.", + "PREPARED BY NORTHLINE STUDIO", "25 AUGUST 2026")) + .executiveSummary(new ProposalSummaryBlock("EXECUTIVE SUMMARY", + "badge-summary", List.of( + "Thank you for the opportunity to partner with Astera Health Co. " + + "on your next chapter. This proposal outlines our approach to " + + "refreshing your brand identity and redesigning your website to " + + "better reflect your mission, elevate credibility, and deliver a " + + "seamless experience for the people you serve.", + "Our process blends strategic thinking with thoughtful design to " + + "create a cohesive brand system and a modern, conversion-focused " + + "website. The outcome will be a clear, confident presence that " + + "strengthens trust, improves engagement, and supports your " + + "growth goals."))) + .glance(new ProposalGlance("PROJECT AT A GLANCE", List.of( + new ProposalGlance.Fact("fact-duration", "PROJECT DURATION", + "6 weeks", null), + new ProposalGlance.Fact("fact-start", "START WINDOW", + "September 2026", null), + new ProposalGlance.Fact("fact-contact", "PRIMARY CONTACT", + "Project Lead", null), + new ProposalGlance.Fact("fact-validity", "PROPOSAL VALIDITY", + "14 days", "(until 8 September 2026)")))) + .goals(new ProposalGoals("PROJECT GOALS", "badge-goals", List.of( + new ProposalGoals.Goal("goal-brand", + "Strengthen brand credibility and visual consistency."), + new ProposalGoals.Goal("goal-usability", + "Improve website usability and drive meaningful action."), + new ProposalGoals.Goal("goal-message", + "Communicate your value clearly across all touchpoints."), + new ProposalGoals.Goal("goal-growth", + "Build a scalable digital foundation for future growth.")))) + .scope(new ProposalScope("SCOPE OF WORK", "badge-scope", List.of( + new ProposalScope.Item("01", "Discovery & Research", + "Stakeholder interviews, brand audit, competitor review, and " + + "audience insights to inform strategy."), + new ProposalScope.Item("02", "Visual Identity Refinement", + "Refine logo system, color palette, typography, and visual " + + "guidelines for a cohesive, modern identity."), + new ProposalScope.Item("03", "Website UX/UI Design", + "Design a responsive, accessibility-minded website with " + + "intuitive navigation and clear conversion pathways."), + new ProposalScope.Item("04", "Content Structure & Messaging", + "Define site architecture, key messaging, and content " + + "hierarchy to communicate value and build trust."), + new ProposalScope.Item("05", "Launch Support", + "Developer handoff, QA support, training, and go-live " + + "assistance to ensure a smooth launch.")))) + .deliverables(new ProposalDeliverables("DELIVERABLES", "badge-deliverables", + List.of("Brand strategy summary", + "Refined logo suite & brand guidelines", + "Color, typography & design system", + "Website UX wireframes", + "High-fidelity website designs"), + List.of("Responsive design for desktop, tablet & mobile", + "Content strategy & page templates", + "Developer-ready assets & specifications", + "Launch checklist & training session"))) + .timeline(new ProposalPhaseGrid("TIMELINE", "badge-timeline", + List.of("PHASE", "FOCUS", "DURATION", "OUTPUT"), List.of( + new ProposalPhaseGrid.Phase("01", "Discover", + "Research, audit, insights, and strategy", "1 week", + "Discovery report & creative direction"), + new ProposalPhaseGrid.Phase("02", "Design", + "Brand refinement and website UX/UI design", "2 weeks", + "Design system & high-fidelity designs"), + new ProposalPhaseGrid.Phase("03", "Develop Prep", + "Content structure, developer handoff & asset preparation", + "1 week", "Specs, assets & content framework"), + new ProposalPhaseGrid.Phase("04", "Launch Support", + "QA, training & go-live assistance", "2 weeks", + "QA sign-off & successful launch")))) + .investment(new ProposalInvestment("INVESTMENT", "badge-investment", + "ITEM", "AMOUNT (GBP)", List.of( + new ProposalInvestment.Row("Discovery & Strategy", "£2,500", + ProposalInvestment.Role.NONE), + new ProposalInvestment.Row("Brand Identity Refinement", "£4,500", + ProposalInvestment.Role.NONE), + new ProposalInvestment.Row("Website UX/UI Design", "£7,000", + ProposalInvestment.Role.NONE), + new ProposalInvestment.Row("Content Structure & Messaging", "£2,000", + ProposalInvestment.Role.NONE), + new ProposalInvestment.Row("Launch Support", "£1,500", + ProposalInvestment.Role.NONE), + new ProposalInvestment.Row("Subtotal", "£17,500", + ProposalInvestment.Role.SUBTOTAL), + new ProposalInvestment.Row("Optional: 3 Months Post-Launch Support", + "£1,750", ProposalInvestment.Role.OPTIONAL)), + "TOTAL INVESTMENT", "£19,250")) + .terms(new ProposalTermsBlock("TERMS", "badge-terms", List.of( + "50% deposit is required to secure the project and schedule the " + + "kick-off.", + "Includes two rounds of revisions per major deliverable.", + "Final files and handoff delivered upon final payment.", + "Ownership of all final deliverables transfers to Astera Health Co. " + + "after full payment is received."))) + .acceptance(new ProposalAcceptance("ACCEPTANCE", "badge-acceptance", + "By signing below, you agree to the scope, timeline, investment, " + + "and terms outlined in this proposal.", + List.of("Accepted by:", "Signature:", "Date:"))) + .build()); + } +} diff --git a/qa/src/test/java/com/demcha/compose/document/templates/proposal/presets/NorthlineProposalLayoutSnapshotTest.java b/qa/src/test/java/com/demcha/compose/document/templates/proposal/presets/NorthlineProposalLayoutSnapshotTest.java new file mode 100644 index 000000000..78e7d893b --- /dev/null +++ b/qa/src/test/java/com/demcha/compose/document/templates/proposal/presets/NorthlineProposalLayoutSnapshotTest.java @@ -0,0 +1,34 @@ +package com.demcha.compose.document.templates.proposal.presets; + +import com.demcha.compose.GraphCompose; +import com.demcha.compose.document.api.DocumentSession; +import com.demcha.compose.document.templates.TemplateTestSupport; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Exact layout snapshot gate for {@link NorthlineProposal} — freezes the + * resolved geometry of both pages, including the natural page-one → page-two + * break, which the pixel budget alone could quietly absorb shifting. + * + *

The preset is a fixed two-page composition (each page's bands are + * composed explicitly), so the canonical fixture IS the pagination + * contract; there is no overflow fixture to grow.

+ * + *

Refresh with {@code -Dgraphcompose.updateSnapshots=true} after a + * deliberate layout change, and commit the JSON with the change.

+ */ +class NorthlineProposalLayoutSnapshotTest { + + @Test + void canonicalProposalMatchesLayoutSnapshot() throws Exception { + try (DocumentSession session = GraphCompose.document().create()) { + NorthlineProposal.create().compose( + session, NorthlineProposalFixtures.canonicalProposal()); + assertThat(session.layoutSnapshot().totalPages()).isEqualTo(2); + TemplateTestSupport.assertCanonicalSnapshot( + session, "northline_proposal_layout", "proposal"); + } + } +} diff --git a/qa/src/test/java/com/demcha/compose/document/templates/proposal/presets/NorthlineProposalSmokeTest.java b/qa/src/test/java/com/demcha/compose/document/templates/proposal/presets/NorthlineProposalSmokeTest.java new file mode 100644 index 000000000..9c10fdacf --- /dev/null +++ b/qa/src/test/java/com/demcha/compose/document/templates/proposal/presets/NorthlineProposalSmokeTest.java @@ -0,0 +1,137 @@ +package com.demcha.compose.document.templates.proposal.presets; + +import com.demcha.compose.GraphCompose; +import com.demcha.compose.document.api.DocumentSession; +import com.demcha.compose.document.templates.api.DocumentTemplate; +import com.demcha.compose.document.templates.data.proposal.ProposalPhaseGrid; +import com.demcha.compose.document.templates.data.proposal.ProposalSummaryBlock; +import com.demcha.compose.document.templates.data.proposal.StructuredProposalData; +import com.demcha.compose.document.templates.data.proposal.StructuredProposalDocumentSpec; +import org.apache.pdfbox.Loader; +import org.apache.pdfbox.pdmodel.PDDocument; +import org.apache.pdfbox.text.PDFTextStripper; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +/** + * Smoke test for the structured proposal pipeline through + * {@link NorthlineProposal} — proves the preset renders a + * {@link StructuredProposalDocumentSpec} end-to-end, that the packaged + * icon set resolves from the templates artifact (a full render loads + * every icon token the canonical fixture names), that an empty document + * renders through the section guards, and that the phase-grid header + * contract fails with a named count rather than an index error. + */ +class NorthlineProposalSmokeTest { + + private static void render(StructuredProposalDocumentSpec spec) throws Exception { + // The preset owns its page geometry (size, margins, background), + // so the session starts unconfigured. + try (DocumentSession session = GraphCompose.document().create()) { + NorthlineProposal.create().compose(session, spec); + assertThat(session.roots()).isNotEmpty(); + // Drive layout + render — the icon loading, the zero-row table + // guards, and the chrome only exist at render time. + assertThat(session.toPdfBytes()).isNotEmpty(); + } + } + + @Test + void exposesStableIdentity() { + DocumentTemplate template = NorthlineProposal.create(); + assertThat(template.id()).isEqualTo(NorthlineProposal.ID); + assertThat(template.displayName()).isEqualTo(NorthlineProposal.DISPLAY_NAME); + } + + @Test + void rendersCanonicalProposalWithPackagedIcons() throws Exception { + render(NorthlineProposalFixtures.canonicalProposal()); + } + + @Test + void rendersEmptyProposal() throws Exception { + // Exercises the guards: no goal cells (the flattened column spec + // cannot describe zero cells), no phase grid, no signature row — + // and the investment table still carries its header + total band. + render(StructuredProposalDocumentSpec.from( + StructuredProposalData.builder().build())); + } + + @Test + void canonicalRenderCarriesTheTableText() throws Exception { + // The phase grid and the investment table are single leaf nodes in + // the layout snapshot (the recorded composed-cell blind spot), so + // their content is asserted on the rendered text layer instead. + byte[] pdfBytes; + try (DocumentSession session = GraphCompose.document().create()) { + NorthlineProposal.create().compose( + session, NorthlineProposalFixtures.canonicalProposal()); + pdfBytes = session.toPdfBytes(); + } + String text; + try (PDDocument document = Loader.loadPDF(pdfBytes)) { + text = new PDFTextStripper().getText(document); + } + // Composed cells wrap, so the assertions stay within one line. + assertThat(text) + .contains("Discovery report") + .contains("Brand Identity Refinement") + .contains("2 weeks") + .contains("TOTAL INVESTMENT") + .contains("£19,250"); + + // The masthead label used to be padded out with a space between every + // letter, so the file stored "P R O P O S A L". Its tracking is on the + // style now. Whitespace collapses first because an extractor + // synthesises spaces from the gaps it sees and real tracking widens the + // word space too — which makes the negative stricter, not weaker. + String words = text.replaceAll("\\s+", " "); + assertThat(words).as("the label, as written").contains("PROPOSAL"); + assertThat(words).as("and not padded out letter by letter") + .doesNotContain("P R O P O S A L"); + } + + @Test + void rejectsUnknownIconTokenByName() { + StructuredProposalDocumentSpec spec = StructuredProposalDocumentSpec.from( + StructuredProposalData.builder() + .executiveSummary(new ProposalSummaryBlock("SUMMARY", + "no-such-icon", List.of("Body."))) + .build()); + try (DocumentSession session = GraphCompose.document().create()) { + assertThatThrownBy(() -> NorthlineProposal.create().compose(session, spec)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("no-such-icon"); + } + } + + @Test + void headerCountIsNotValidatedWhenTheGridNeverRenders() throws Exception { + // A wrong header count with zero phases constrains nothing — the + // grid is skipped before the validation. + render(StructuredProposalDocumentSpec.from(StructuredProposalData.builder() + .timeline(new ProposalPhaseGrid("TIMELINE", "badge-timeline", + List.of("PHASE", "FOCUS", "DURATION"), List.of())) + .build())); + } + + @Test + void phaseGridRejectsHeaderCountMismatchByName() { + StructuredProposalDocumentSpec spec = StructuredProposalDocumentSpec.from( + StructuredProposalData.builder() + .timeline(new ProposalPhaseGrid("TIMELINE", "badge-timeline", + List.of("PHASE", "FOCUS", "DURATION"), + List.of(new ProposalPhaseGrid.Phase("01", "Discover", + "Research", "1 week", "Report")))) + .build()); + try (DocumentSession session = GraphCompose.document().create()) { + assertThatThrownBy(() -> NorthlineProposal.create().compose(session, spec)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("one header per column"); + } + } +} diff --git a/qa/src/test/java/com/demcha/compose/document/templates/proposal/presets/NorthlineProposalVisualParityTest.java b/qa/src/test/java/com/demcha/compose/document/templates/proposal/presets/NorthlineProposalVisualParityTest.java new file mode 100644 index 000000000..c276e7186 --- /dev/null +++ b/qa/src/test/java/com/demcha/compose/document/templates/proposal/presets/NorthlineProposalVisualParityTest.java @@ -0,0 +1,48 @@ +package com.demcha.compose.document.templates.proposal.presets; + +import com.demcha.compose.GraphCompose; +import com.demcha.compose.document.api.DocumentSession; +import com.demcha.compose.testing.visual.PdfVisualRegression; +import org.junit.jupiter.api.Test; + +import java.nio.file.Path; + +/** + * Pixel-diff visual parity gate for {@link NorthlineProposal} — both pages + * of the canonical fixture against checked-in baselines. + * + *

Separate from {@code ProposalV2VisualParityTest} because that gate is + * parameterised on the narrative {@code ProposalDocumentSpec} while this + * preset consumes the structured spec. Budget and tolerance mirror the + * other template parity gates (Lato/Spectral are bundled TTFs, the same + * cross-platform drift class as the CV presets' faces).

+ * + *

Re-blessing baselines — after a deliberate visual + * change, re-run with {@code -Dgraphcompose.visual.approve=true} and + * commit the updated PNGs with the change. Baselines live under + * {@code src/test/resources/visual-baselines/proposal-v2-layered/}.

+ */ +class NorthlineProposalVisualParityTest { + + private static final Path BASELINE_ROOT = Path.of( + "src", "test", "resources", "visual-baselines", "proposal-v2-layered"); + + private static final long PIXEL_DIFF_BUDGET = 50_000L; + private static final int PER_PIXEL_TOLERANCE = 8; + + @Test + void rendersWithinPixelDiffBudget() throws Exception { + byte[] pdfBytes; + try (DocumentSession document = GraphCompose.document().create()) { + NorthlineProposal.create().compose( + document, NorthlineProposalFixtures.canonicalProposal()); + pdfBytes = document.toPdfBytes(); + } + + PdfVisualRegression.standard() + .baselineRoot(BASELINE_ROOT) + .perPixelTolerance(PER_PIXEL_TOLERANCE) + .mismatchedPixelBudget(PIXEL_DIFF_BUDGET) + .assertMatchesBaseline("northline_proposal", pdfBytes); + } +} diff --git a/qa/src/test/java/com/demcha/compose/document/templates/receipt/presets/ModernReceiptFooterPinTest.java b/qa/src/test/java/com/demcha/compose/document/templates/receipt/presets/ModernReceiptFooterPinTest.java new file mode 100644 index 000000000..ee691c76f --- /dev/null +++ b/qa/src/test/java/com/demcha/compose/document/templates/receipt/presets/ModernReceiptFooterPinTest.java @@ -0,0 +1,149 @@ +package com.demcha.compose.document.templates.receipt.presets; + +import com.demcha.compose.GraphCompose; +import com.demcha.compose.document.api.DocumentPageSize; +import com.demcha.compose.document.api.DocumentSession; +import com.demcha.compose.document.snapshot.LayoutNodeSnapshot; +import com.demcha.compose.document.snapshot.LayoutSnapshot; +import com.demcha.compose.document.style.DocumentInsets; +import com.demcha.compose.document.templates.data.receipt.ReceiptDocumentSpec; +import com.demcha.compose.document.templates.data.receipt.ReceiptStatus; +import org.junit.jupiter.api.Test; + +import java.util.Optional; +import java.util.function.Consumer; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.within; + +/** + * The footer pin — {@link ModernReceipt} measures what the body left on the + * last page and spends it, so the verification code and the small print land on + * the bottom margin instead of floating under the notes. + * + *

The mechanism re-composes, which clears the session, so the cases that + * matter are the ones where it must not run: a session that already + * carries content, and a receipt with no footer to hold down.

+ */ +class ModernReceiptFooterPinTest { + + /** How close to the bottom margin the footer has to land to count as seated. */ + private static final double SEATED_TOLERANCE = 2.0; + + private static final String PIN_NODE = "ReceiptFooterPin"; + private static final String FOOTER_NODE = "ReceiptFooter"; + + private static DocumentSession session() { + return GraphCompose.document() + .pageSize(DocumentPageSize.A4) + .margin(DocumentInsets.of(ModernReceipt.RECOMMENDED_MARGIN)) + .create(); + } + + private static ReceiptDocumentSpec receipt(Consumer extra) { + return ReceiptDocumentSpec.of(builder -> { + builder.documentTitle("Transfer confirmation") + .issuerName("Northwind Pay") + .generatedOn("09 August 2026") + .amount("Amount collected", "£66.62") + .status(ReceiptStatus.settled("Completed")) + .payer("Paid from", party -> party.name("Alex Sample")) + .beneficiary("Paid to", party -> party.name("Harbour Finance Ltd")); + extra.accept(builder); + }); + } + + private static Optional node(LayoutSnapshot snapshot, String entityName) { + return snapshot.nodes().stream() + .filter(n -> entityName.equals(n.entityName())) + .findFirst(); + } + + @Test + void seatsTheFooterOnTheBottomMargin() throws Exception { + ReceiptDocumentSpec spec = receipt(builder -> builder + .verification("https://example.com/v/1", "Scan to check it.") + .legalNote("Every name on this page is invented.")); + + try (DocumentSession document = session()) { + ModernReceipt.create().compose(document, spec); + LayoutSnapshot snapshot = document.layoutSnapshot(); + + assertThat(node(snapshot, PIN_NODE)) + .describedAs("a short receipt leaves most of the page free, so it pins") + .isPresent(); + LayoutNodeSnapshot footer = node(snapshot, FOOTER_NODE).orElseThrow(); + assertThat(footer.placementY()) + .describedAs("footer bottom against the page's bottom margin") + .isCloseTo(snapshot.canvas().margin().bottom(), within(SEATED_TOLERANCE)); + } + } + + @Test + void leavesContentThatWasAlreadyComposed() throws Exception { + // The pin re-composes by clearing the session. A caller who put a cover + // page in first must get it back, footer pinned or not. + ReceiptDocumentSpec spec = receipt(builder -> builder + .verification("https://example.com/v/1", "Scan to check it.")); + + try (DocumentSession document = session()) { + document.dsl().pageFlow().name("CallerCover").addParagraph("Statement pack").build(); + ModernReceipt.create().compose(document, spec); + + assertThat(document.roots()).hasSizeGreaterThan(1); + LayoutSnapshot snapshot = document.layoutSnapshot(); + assertThat(node(snapshot, "CallerCover")) + .describedAs("the caller's own content survives compose()") + .isPresent(); + assertThat(node(snapshot, PIN_NODE)) + .describedAs("a shared session is not the receipt's to re-compose") + .isEmpty(); + } + } + + @Test + void pinsNothingWhenThereIsNoFooter() throws Exception { + // No QR, no support lines, no small print: a spacer here would be a + // page-tall hole holding nothing down. + try (DocumentSession document = session()) { + ModernReceipt.create().compose(document, receipt(builder -> { })); + + assertThat(node(document.layoutSnapshot(), PIN_NODE)).isEmpty(); + } + } + + @Test + void seatsTheFooterOnTheLastPageOfALongReceipt() throws Exception { + // Placement coordinates resolve on the page a node starts on, so a body + // spanning a page break must be measured from nodes that live entirely on + // the last page — otherwise the gap measured is page one's. + ReceiptDocumentSpec spec = receipt(builder -> { + for (int group = 0; group < 12; group++) { + int index = group; + builder.detailGroup("Block " + index, rows -> { + for (int row = 0; row < 8; row++) { + rows.field("Field " + row, "Value " + row); + } + }); + } + builder.verification("https://example.com/v/1", "Scan to check it.") + .legalNote("Every name on this page is invented."); + }); + + try (DocumentSession document = session()) { + ModernReceipt.create().compose(document, spec); + LayoutSnapshot snapshot = document.layoutSnapshot(); + + assertThat(snapshot.totalPages()) + .describedAs("the fixture is sized to overflow one page") + .isGreaterThan(1); + LayoutNodeSnapshot footer = node(snapshot, FOOTER_NODE).orElseThrow(); + assertThat(footer.startPage()).isEqualTo(snapshot.totalPages() - 1); + assertThat(footer.placementY()) + .describedAs("seated on the last page's bottom margin, not page one's") + .isCloseTo(snapshot.canvas().margin().bottom(), within(SEATED_TOLERANCE)); + assertThat(document.toPdfBytes()).isNotEmpty(); + } + } +} diff --git a/qa/src/test/java/com/demcha/compose/document/templates/receipt/presets/ModernReceiptLayoutSnapshotTest.java b/qa/src/test/java/com/demcha/compose/document/templates/receipt/presets/ModernReceiptLayoutSnapshotTest.java new file mode 100644 index 000000000..3b96ea3e1 --- /dev/null +++ b/qa/src/test/java/com/demcha/compose/document/templates/receipt/presets/ModernReceiptLayoutSnapshotTest.java @@ -0,0 +1,50 @@ +package com.demcha.compose.document.templates.receipt.presets; + +import com.demcha.compose.GraphCompose; +import com.demcha.compose.document.api.DocumentPageSize; +import com.demcha.compose.document.api.DocumentSession; +import com.demcha.compose.document.style.DocumentColor; +import com.demcha.compose.document.templates.TemplateTestSupport; +import com.demcha.compose.document.templates.api.DocumentTemplate; +import com.demcha.compose.document.templates.core.theme.BrandTheme; +import com.demcha.compose.document.templates.data.receipt.ReceiptDocumentSpec; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Exact layout snapshot gate for {@link ModernReceipt} — freezes the resolved geometry of + * the canonical receipt. + * + *

The receipt family shipped with a pixel gate only, and a pixel budget cannot say where + * a box went: it absorbs a small shift and reports the same number for a moved hairline as + * for a recoloured one. The receipt is built from measured text — a status chip sized to its + * label, spaced-caps group titles, a hero amount, and the rail of the status trail — so the + * geometry is the part that has to hold.

+ * + *

Composed onto the same page size, margin and branded factory as + * {@link ReceiptVisualParityTest}, from the same {@link ReceiptFixtures} spec, so the two + * gates describe one document rather than two that may drift apart.

+ * + *

Refresh with {@code -Dgraphcompose.updateSnapshots=true} after a deliberate layout + * change, and commit the JSON with the change.

+ */ +class ModernReceiptLayoutSnapshotTest { + + @Test + void canonicalReceiptMatchesLayoutSnapshot() throws Exception { + float margin = (float) ModernReceipt.RECOMMENDED_MARGIN; + DocumentTemplate template = ModernReceipt.create( + BrandTheme.receiptModern(), + ModernReceipt.Options.branded(null, DocumentColor.rgb(23, 92, 211))); + try (DocumentSession session = GraphCompose.document() + .pageSize(DocumentPageSize.A4) + .margin(margin, margin, margin, margin) + .create()) { + template.compose(session, ReceiptFixtures.canonicalReceipt()); + assertThat(session.layoutSnapshot().totalPages()).isEqualTo(1); + TemplateTestSupport.assertCanonicalSnapshot( + session, "modern_receipt_layout", "receipt"); + } + } +} diff --git a/qa/src/test/java/com/demcha/compose/document/templates/receipt/presets/ModernReceiptSmokeTest.java b/qa/src/test/java/com/demcha/compose/document/templates/receipt/presets/ModernReceiptSmokeTest.java new file mode 100644 index 000000000..4ffe4f9f1 --- /dev/null +++ b/qa/src/test/java/com/demcha/compose/document/templates/receipt/presets/ModernReceiptSmokeTest.java @@ -0,0 +1,206 @@ +package com.demcha.compose.document.templates.receipt.presets; + +import com.demcha.compose.GraphCompose; +import com.demcha.compose.document.api.DocumentPageSize; +import com.demcha.compose.document.api.DocumentSession; +import com.demcha.compose.document.style.DocumentColor; +import com.demcha.compose.document.style.DocumentInsets; +import com.demcha.compose.document.templates.api.DocumentTemplate; +import com.demcha.compose.document.templates.core.theme.BrandTheme; +import com.demcha.compose.document.templates.data.receipt.ReceiptDocumentSpec; +import com.demcha.compose.document.templates.data.receipt.ReceiptStatus; +import com.demcha.compose.document.templates.data.receipt.ReceiptStatusTone; +import org.apache.pdfbox.Loader; +import org.apache.pdfbox.pdmodel.PDDocument; +import org.apache.pdfbox.text.PDFTextStripper; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Smoke test for the layered receipt pipeline through {@link ModernReceipt} + * — proves the preset renders a {@link ReceiptDocumentSpec} end-to-end on a + * {@link BrandTheme}, through every factory, under a foreign theme, for each + * status tone, and on a receipt carrying nothing but an amount. + */ +class ModernReceiptSmokeTest { + + private static ReceiptDocumentSpec sampleSpec() { + return ReceiptDocumentSpec.of(receipt -> receipt + .documentTitle("Transfer confirmation") + .issuerName("Northwind Pay") + .generatedOn("09 August 2026") + .reference("NWP-4821-0067") + .amount("Amount collected", "£66.62") + .amountCaption("Direct Debit collected by Harbour Finance Ltd") + .status(ReceiptStatus.settled("Completed")) + .summaryField("Value date", "07 Jul 2026") + .summaryField("Scheme", "Bacs Direct Debit") + .payer("Paid from", party -> party + .name("Alex Sample") + .addressLines("12 Example Way", "Brentford TW0 0AA") + .field("Account", "•••• 4396")) + .beneficiary("Paid to", party -> party + .name("Harbour Finance Ltd") + .field("Account", "•••• 5604")) + .detailGroup("Transfer details", group -> group + .field("Mandate reference", "MND-0110-8054-5652") + .emphasized("Total debited", "£66.62")) + .event("Instructed", "07 Jul 2026, 08:12 BST", "Submitted to the scheme.") + .event("Settled", "09 Jul 2026, 06:30 BST", "Confirmed by the receiving bank.") + .note("Keep this confirmation for your records.") + .verification("https://example.com/verify/NWP-4821-0067", "Scan to check it.") + .supportLine("help@northwind-pay.example") + .legalNote("Every name and account on this page is invented.")); + } + + /** Nothing but an amount: exercises every skipped-block path at once. */ + private static ReceiptDocumentSpec minimalSpec() { + return ReceiptDocumentSpec.of(receipt -> receipt.amount("Amount", "£1.00")); + } + + /** One side only: the pair widget's single-column path. */ + private static ReceiptDocumentSpec payerOnlySpec() { + return ReceiptDocumentSpec.of(receipt -> receipt + .amount("Amount", "£1.00") + .payer("Paid from", party -> party.name("Alex Sample"))); + } + + private static void render(DocumentTemplate template, + ReceiptDocumentSpec spec) throws Exception { + try (DocumentSession session = GraphCompose.document() + .pageSize(DocumentPageSize.A4) + .margin(DocumentInsets.of(ModernReceipt.RECOMMENDED_MARGIN)) + .create()) { + template.compose(session, spec); + assertThat(session.roots()).isNotEmpty(); + // Renders all the way to bytes: the QR code and the inline glyphs only + // reach a backend here, so a compose-only assertion would miss them. + assertThat(session.toPdfBytes()).isNotEmpty(); + } + } + + @Test + void exposesStableIdentity() { + DocumentTemplate template = ModernReceipt.create(); + assertThat(template.id()).isEqualTo(ModernReceipt.ID); + assertThat(template.displayName()).isEqualTo(ModernReceipt.DISPLAY_NAME); + } + + @Test + void theSpacedCapsLabelsReachTheTextLayerAsWords() throws Exception { + // The spaced-caps look used to be built by rewriting the string with a + // space between every pair of letters, so a reader saw the right page + // and the file stored "A M O U N T C O L L E C T E D" — unsearchable, + // unparseable, and read out letter by letter. The tracking is on the + // style now and the words are words. That is the whole point of the + // change, and neither the pixel nor the geometry gate can see it. + String text; + try (DocumentSession session = GraphCompose.document() + .pageSize(DocumentPageSize.A4) + .margin(DocumentInsets.of(ModernReceipt.RECOMMENDED_MARGIN)) + .create()) { + ModernReceipt.create().compose(session, ReceiptFixtures.canonicalReceipt()); + try (PDDocument document = Loader.loadPDF(session.toPdfBytes())) { + text = new PDFTextStripper().getText(document); + } + } + + // Runs of whitespace collapse first. A PDF text extractor synthesises + // spaces from the gaps it sees, and real tracking widens the word space + // too, so the issuer's name comes back out with a run of them between + // its words. That is the extractor measuring, not the file storing — + // and collapsing makes the negatives below stricter rather than weaker, + // since letters padded apart still read as single-spaced letters. + String words = text.replaceAll("\\s+", " "); + + assertThat(words) + .as("every label the spaced-caps styles set, as written") + .contains("NORTHWIND PAY") + .contains("AMOUNT COLLECTED") + .contains("PAID FROM") + .contains("PAID TO") + .contains("TRANSFER DETAILS") + .contains("AMOUNT BREAKDOWN") + .contains("PAYMENT PROGRESS") + .contains("NOTES"); + assertThat(words) + .as("and not one of them padded out letter by letter") + .doesNotContain("A M O U N T") + .doesNotContain("P A I D") + .doesNotContain("N O R T H W I N D") + .doesNotContain("N O T E S"); + } + + @Test + void defaultFactoryRendersWithReceiptTheme() throws Exception { + render(ModernReceipt.create(), sampleSpec()); + } + + @Test + void rendersWithExplicitTheme() throws Exception { + render(ModernReceipt.create(BrandTheme.receiptModern()), sampleSpec()); + } + + @Test + void rendersWithIssuerBranding() throws Exception { + // No mark: the branded path that matters here is the accent, which reaches + // the hero strip, the direction arrow, and the reached timeline step. + ModernReceipt.Options options = ModernReceipt.Options.defaults() + .withTimelineTitle("Where it got to"); + render(ModernReceipt.create(BrandTheme.receiptModern(), options), sampleSpec()); + } + + @Test + void readsAnyTheme() throws Exception { + // A theme built for a CV drives every surface the receipt reads, so the + // preset must not depend on a token only its own theme sets. + render(ModernReceipt.create(BrandTheme.boxedClassic()), sampleSpec()); + } + + @Test + void nullOptionsFallBackToDefaults() throws Exception { + render(ModernReceipt.create(BrandTheme.receiptModern(), null), sampleSpec()); + } + + @ParameterizedTest + @EnumSource(ReceiptStatusTone.class) + void rendersEveryStatusTone(ReceiptStatusTone tone) throws Exception { + ReceiptDocumentSpec spec = ReceiptDocumentSpec.of(receipt -> receipt + .amount("Amount", "£1.00") + .status("Status", tone)); + render(ModernReceipt.create(BrandTheme.receiptModern(), + ModernReceipt.Options.defaults()), spec); + } + + @Test + void rendersReceiptCarryingOnlyAnAmount() throws Exception { + render(ModernReceipt.create(), minimalSpec()); + } + + @Test + void rendersOneSidedTransfer() throws Exception { + render(ModernReceipt.create(), payerOnlySpec()); + } + + @Test + void accentIsOptional() throws Exception { + // A null accent must render an unbranded but complete receipt rather than + // throwing on the first colour the hero asks for. + ModernReceipt.Options options = new ModernReceipt.Options(null, 0, null, null, " "); + assertThat(options.logoWidth()).isEqualTo(ModernReceipt.Options.DEFAULT_LOGO_WIDTH); + assertThat(options.timelineTitle()).isEqualTo(ModernReceipt.Options.DEFAULT_TIMELINE_TITLE); + render(ModernReceipt.create(BrandTheme.receiptModern(), options), sampleSpec()); + } + + @Test + void brandedOptionsCarryTheAccent() { + DocumentColor accent = DocumentColor.rgb(23, 92, 211); + ModernReceipt.Options options = ModernReceipt.Options.branded(null, accent); + assertThat(options.accent()).isEqualTo(accent); + assertThat(options.withLogoWidth(40).logoWidth()).isEqualTo(40); + assertThat(options.withLogoColor(accent).logoColor()).isEqualTo(accent); + } +} diff --git a/qa/src/test/java/com/demcha/compose/document/templates/receipt/presets/ReceiptFixtures.java b/qa/src/test/java/com/demcha/compose/document/templates/receipt/presets/ReceiptFixtures.java new file mode 100644 index 000000000..b43ff1516 --- /dev/null +++ b/qa/src/test/java/com/demcha/compose/document/templates/receipt/presets/ReceiptFixtures.java @@ -0,0 +1,73 @@ +package com.demcha.compose.document.templates.receipt.presets; + +import com.demcha.compose.document.templates.data.receipt.ReceiptDocumentSpec; +import com.demcha.compose.document.templates.data.receipt.ReceiptStatus; + +/** + * Shared fixture data for the receipt gates — the SAME spec feeds the pixel gate and the + * exact layout snapshot, so a geometry shift the 50k pixel budget absorbs still trips the + * snapshot, and a colour change the snapshot cannot see still trips the pixels. + * + *

Kept in the test tree and built only from main code, so the gates depend on the + * templates module and not on the examples module.

+ */ +final class ReceiptFixtures { + + private ReceiptFixtures() { + } + + /** + * Canonical sample receipt — exercises the masthead, the hero with its status chip and + * summary fields, both party sides, two detail groups, the status trail, the notes + * block, and the footer with its QR code. + * + * @return the canonical receipt spec shared by every receipt gate + */ + static ReceiptDocumentSpec canonicalReceipt() { + return ReceiptDocumentSpec.of(receipt -> receipt + .documentTitle("Transfer confirmation") + .issuerName("Northwind Pay") + .generatedOn("09 August 2026") + .reference("NWP-4821-0067") + .amount("Amount collected", "£66.62") + .amountCaption("Direct Debit collected by Harbour Finance Ltd") + .status(ReceiptStatus.settled("Completed")) + .summaryField("Value date", "07 Jul 2026") + .summaryField("Operation date", "07 Jul 2026") + .summaryField("Scheme", "Bacs Direct Debit") + .payer("Paid from", party -> party + .name("Alex Sample") + .addressLines("12 Example Way", "Brentford TW0 0AA", "United Kingdom") + .field("Account", "•••• 4396") + .field("Sort code", "00-00-00")) + .beneficiary("Paid to", party -> party + .name("Harbour Finance Ltd") + .addressLines("1 Sample Quay", "Manchester M0 0AA") + .field("Account", "•••• 5604") + .field("Sort code", "00-00-11")) + .detailGroup("Transfer details", group -> group + .field("Mandate reference", "MND-0110-8054-5652") + .field("Transaction ID", "b71f0c2e-9a44-4f18-bd30-51c7a2e9d840") + .field("Payment scheme", "Bacs Direct Debit") + .field("Statement description", "HARBOUR FIN 4821")) + .detailGroup("Amount breakdown", group -> group + .field("Amount", "£66.62") + .field("Transfer fee", "£0.00") + .field("Exchange rate", "1.00 GBP / GBP") + .emphasized("Total debited", "£66.62")) + .event("Instructed", "07 Jul 2026, 08:12 BST", + "Collection request submitted to the scheme.") + .event("In clearing", "08 Jul 2026, 09:00 BST", + "Three-working-day Bacs cycle.") + .event("Settled", "09 Jul 2026, 06:30 BST", + "Funds debited and confirmed by the receiving bank.") + .note("Keep this confirmation for your records. It is not a tax invoice.") + .verification("https://example.com/verify/NWP-4821-0067", + "Scan to check this confirmation against the issuing records.") + .supportLine("Support +44 20 0000 0000") + .supportLine("help@northwind-pay.example") + .legalNote("Northwind Pay is a fictional institution used in GraphCompose " + + "examples; every name, account, and reference on this page is " + + "invented.")); + } +} diff --git a/qa/src/test/java/com/demcha/compose/document/templates/receipt/presets/ReceiptVisualParityTest.java b/qa/src/test/java/com/demcha/compose/document/templates/receipt/presets/ReceiptVisualParityTest.java new file mode 100644 index 000000000..ffbec969a --- /dev/null +++ b/qa/src/test/java/com/demcha/compose/document/templates/receipt/presets/ReceiptVisualParityTest.java @@ -0,0 +1,87 @@ +package com.demcha.compose.document.templates.receipt.presets; + +import com.demcha.compose.GraphCompose; +import com.demcha.compose.document.api.DocumentPageSize; +import com.demcha.compose.document.api.DocumentSession; +import com.demcha.compose.document.style.DocumentColor; +import com.demcha.compose.document.templates.api.DocumentTemplate; +import com.demcha.compose.document.templates.core.theme.BrandTheme; +import com.demcha.compose.document.templates.data.receipt.ReceiptDocumentSpec; +import com.demcha.compose.testing.visual.PdfVisualRegression; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.nio.file.Path; +import java.util.function.Supplier; +import java.util.stream.Stream; + +/** + * Pixel-diff visual parity gate for the layered receipt presets. + * + *

The preset renders the canonical {@link ReceiptDocumentSpec} on A4 at + * {@code RECOMMENDED_MARGIN}; the PDF is rasterised page-by-page and compared + * per-pixel against a checked-in baseline PNG. The receipt look is carried by + * hairlines, a status chip, and one very large amount — all of which move + * silently under a theme or widget refactor, which is what this locks.

+ * + *

Re-blessing baselines — after a deliberate visual + * change, re-run with {@code -Dgraphcompose.visual.approve=true} and commit + * the updated PNGs in the same change. Baselines live under + * {@code src/test/resources/visual-baselines/receipt-layered/}.

+ */ +class ReceiptVisualParityTest { + + private static final Path BASELINE_ROOT = Path.of( + "src", "test", "resources", "visual-baselines", "receipt-layered"); + + // Mirrors the invoice gate: Helvetica is the PDFBox built-in font with the + // widest cross-platform glyph/colour drift, so the budget is sized for + // Windows-recorded baselines against Linux CI. + private static final long PIXEL_DIFF_BUDGET = 50_000L; + private static final int PER_PIXEL_TOLERANCE = 8; + + @ParameterizedTest(name = "{0}") + @MethodSource("presets") + void rendersWithinPixelDiffBudget(String slug, + double margin, + Supplier> factory) + throws Exception { + DocumentTemplate template = factory.get(); + float m = (float) margin; + byte[] pdfBytes; + try (DocumentSession document = GraphCompose.document() + .pageSize(DocumentPageSize.A4) + .margin(m, m, m, m) + .create()) { + template.compose(document, ReceiptFixtures.canonicalReceipt()); + pdfBytes = document.toPdfBytes(); + } + + PdfVisualRegression.standard() + .baselineRoot(BASELINE_ROOT) + .perPixelTolerance(PER_PIXEL_TOLERANCE) + .mismatchedPixelBudget(PIXEL_DIFF_BUDGET) + .assertMatchesBaseline(slug, pdfBytes); + } + + private static Stream presets() { + return Stream.of( + Arguments.of("modern_receipt", + ModernReceipt.RECOMMENDED_MARGIN, + (Supplier>) + ReceiptVisualParityTest::brandedModernReceipt)); + } + + /** + * The branded factory, so the gate covers the accent surfaces — hero + * strip, direction arrow, reached timeline step — and not only the + * unbranded fallback. No mark: an SVG asset would put a second file in + * front of the baseline. + */ + private static DocumentTemplate brandedModernReceipt() { + return ModernReceipt.create(BrandTheme.receiptModern(), + ModernReceipt.Options.branded(null, DocumentColor.rgb(23, 92, 211))); + } + +} diff --git a/qa/src/test/java/com/demcha/compose/document/templates/rota/presets/CobaltRotaFixtures.java b/qa/src/test/java/com/demcha/compose/document/templates/rota/presets/CobaltRotaFixtures.java new file mode 100644 index 000000000..fcdf1f1f7 --- /dev/null +++ b/qa/src/test/java/com/demcha/compose/document/templates/rota/presets/CobaltRotaFixtures.java @@ -0,0 +1,233 @@ +package com.demcha.compose.document.templates.rota.presets; + +import com.demcha.compose.document.templates.data.rota.RotaCovers; +import com.demcha.compose.document.templates.data.rota.RotaDay; +import com.demcha.compose.document.templates.data.rota.RotaFooter; +import com.demcha.compose.document.templates.data.rota.RotaGroup; +import com.demcha.compose.document.templates.data.rota.RotaLegend; +import com.demcha.compose.document.templates.data.rota.RotaShift; +import com.demcha.compose.document.templates.data.rota.RotaStaff; +import com.demcha.compose.document.templates.data.rota.RotaVenue; +import com.demcha.compose.document.templates.data.rota.RotaWeek; +import com.demcha.compose.document.templates.data.rota.ShiftStatus; +import com.demcha.compose.document.templates.data.rota.StructuredRotaData; +import com.demcha.compose.document.templates.data.rota.StructuredRotaDocumentSpec; + +import java.util.List; + +/** + * Shared fixture data for the Cobalt rota gates — the SAME spec feeds the smoke, + * layout-snapshot and pixel gates, so a geometry shift the pixel budget absorbs + * still trips the exact snapshot, and vice versa. + * + *

The canonical fixture is the preset's reference content: a bar's week — + * seven days with their notes and covers, seven documented statuses, and twelve + * people in three bands, whose cells between them cover every shape a rota cell + * has: a plain span of hours, a marked day, a stacked pair of two marked halves, + * a marked half beside a plain one, and a day left blank.

+ * + *

Twelve people is the page. The sheet holds this many on + * one landscape page and no more; a thirteenth takes a second page, which is the + * preset's own behaviour and not a fault. Growing the fixture therefore changes + * what both gates are measuring.

+ * + *

Every name here — the venue, the people, what is on each night — is + * invented. The shape is a real week's, because that is what the gates have to + * measure against; the content is not anyone's.

+ */ +final class CobaltRotaFixtures { + + private CobaltRotaFixtures() { + } + + /** One week of a bar's rota. */ + static StructuredRotaDocumentSpec canonicalRota() { + return StructuredRotaDocumentSpec.from(StructuredRotaData.builder() + // A short wordmark on purpose: the lockup is sized by a fixed + // fraction of its column, so a long name wraps rather than + // shrinking to fit. + .venue(new RotaVenue("QUAY", "QUAYSIDE BAR", "Quayside Bar")) + .week(new RotaWeek("WEEK COMMENCING 31 AUGUST 2026", "31 Aug - 6 Sep 2026")) + .days(List.of( + day("MONDAY", "31", "ST", "/Clean Crushed Ice Machine & Area", + "104", "50"), + day("TUESDAY", "1", "ST", "Pianist 18:30 / Clean Crushed Ice Machine " + + "& Area", "33", "57"), + day("WEDNESDAY", "2", "ND", "Private dinner 44 pax / Soul GF / " + + "Pianist 18:30", "32", "76"), + day("THURSDAY", "3", "RD", "Pianist 18:30 / Partner meeting 3:30pm", + "22", "48"), + day("FRIDAY", "4", "TH", "Pianist 19:00", "65", "112"), + day("SATURDAY", "5", "TH", "MC 10 pax / Pianist 19:00", "73", "112"), + day("SUNDAY", "6", "TH", "MC 2 pax // 60 pax dinner / FF ex hire / " + + "Clean Cubed Ice Machine & Area", "72", "83"))) + .legend(new RotaLegend("STATUS LEGEND", "COVERS", "L", "D", List.of( + new RotaLegend.Entry("REQUEST", ShiftStatus.REQUEST), + new RotaLegend.Entry("OFF", ShiftStatus.OFF), + new RotaLegend.Entry("HOL", ShiftStatus.HOLIDAY), + new RotaLegend.Entry("STOCK", ShiftStatus.STOCK), + new RotaLegend.Entry("Standby", ShiftStatus.STANDBY), + new RotaLegend.Entry("Training", ShiftStatus.TRAINING), + new RotaLegend.Entry("SUPPORT", ShiftStatus.SUPPORT)))) + .groups(List.of( + new RotaGroup("MANAGEMENT", "management", List.of( + staff("ROSA", + one(RotaShift.strong("09:00-18:00", ShiftStatus.STOCK)), + one(off()), + one(hours("16:00-01:00")), + one(hours("16:00-01:00")), + one(off()), + one(RotaShift.strong("08:00-18:00", ShiftStatus.STOCK)), + split(RotaShift.strong("08:00-16:00", ShiftStatus.STOCK), + RotaShift.soft("16:00-21:00", + ShiftStatus.STOCK))), + staff("TOMAS", + one(hours("16:00-00:00")), + one(hours("16:00-00:00")), + one(off()), + one(off()), + split(hours("11:00-16:00"), hours("16:00-01:00")), + one(hours("16:00-01:00")), + one(hours("16:00-01:00"))))), + new RotaGroup("BARTENDERS", "bartenders", List.of( + staff("NADIA", + split(RotaShift.strong("09:00-16:00", ShiftStatus.STOCK), + RotaShift.soft("16:00-22:00", + ShiftStatus.STOCK)), + one(off()), + one(hours("16:00-00:00")), + one(off()), + one(RotaShift.strong("09:00-18:00", ShiftStatus.STOCK)), + one(off()), + one(RotaShift.strong("09:00-18:00", ShiftStatus.STOCK))), + staff("ELIAS", + one(off()), + one(RotaShift.strong("09:00-17:00", ShiftStatus.STOCK)), + one(hours("16:00-00:00")), + one(hours("16:00-00:00")), + split(RotaShift.strong("12:00-16:00", + ShiftStatus.TRAINING), + RotaShift.soft("16:00-22:00", + ShiftStatus.TRAINING)), + split(RotaShift.strong("09:00-16:00", ShiftStatus.STOCK), + RotaShift.soft("16:00-22:00", + ShiftStatus.STOCK)), + one(off())), + staff("MIRELA", + one(off()), + split(RotaShift.strong("09:00-16:00", ShiftStatus.STOCK), + RotaShift.soft("16:00-22:00", + ShiftStatus.STOCK)), + split(RotaShift.strong("09:00-16:00", ShiftStatus.STOCK), + RotaShift.soft("16:00-22:00", + ShiftStatus.STOCK)), + one(hours("12:00-18:00")), + one(hours("16:00-01:00")), + one(RotaShift.strong("16:00-01:00", + ShiftStatus.TRAINING)), + one(off())), + staff("SOFIA", + split(hours("12:00-16:00"), hours("16:00-00:00")), + one(holiday()), + one(holiday()), + one(holiday()), + one(holiday()), + one(holiday()), + one(holiday())), + staff("KAROL", + one(holiday()), + one(off()), + one(hours("12:00-18:00")), + one(RotaShift.strong("09:00-18:00", ShiftStatus.STOCK)), + one(hours("16:00-01:00")), + none(), + none()), + staff("HENRIK", + one(holiday()), + one(hours("16:00-00:00")), + one(off()), + one(hours("16:00-01:00")), + one(off()), + split(RotaShift.strong("08:00-16:00", ShiftStatus.STOCK), + RotaShift.soft("16:00-21:00", + ShiftStatus.STOCK)), + split(hours("12:00-16:00"), hours("16:00-00:00"))), + staff("LUCIA", + one(off()), + one(off()), + one(off()), + one(RotaShift.strong("16:00-01:00", + ShiftStatus.TRAINING)), + one(RotaShift.strong("16:00-01:00", + ShiftStatus.TRAINING)), + one(RotaShift.strong("16:00-01:00", + ShiftStatus.TRAINING)), + one(hours("16:00-00:00"))))), + new RotaGroup("BARBACKS", "barbacks", List.of( + staff("OSKAR", + one(holiday()), + one(holiday()), + one(holiday()), + one(off()), + split(hours("12:00-16:00"), hours("16:00-01:00")), + split(hours("12:00-16:00"), hours("16:00-01:00")), + split(hours("12:00-16:00"), hours("16:00-01:00"))), + staff("TEODOR", + one(holiday()), + one(off()), + one(hours("16:00-00:00")), + one(off()), + one(hours("16:00-01:00")), + split(hours("12:00-16:00"), hours("16:00-01:00")), + split(hours("12:00-16:00"), hours("16:00-01:00"))), + staff("VESNA", + split(hours("12:00-16:00"), hours("16:00-00:00")), + one(hours("16:00-00:00")), + one(hours("16:00-00:00")), + split(hours("12:00-16:00"), + RotaShift.strong("16:00-00:00", + ShiftStatus.TRAINING)), + one(holiday()), + one(holiday()), + one(holiday())))))) + .footer(new RotaFooter( + "Times shown in 24-hour format. Shift colours follow the status " + + "legend above.")) + .build()); + } + + private static RotaDay day(String name, String ordinal, String suffix, String note, + String lunch, String dinner) { + return new RotaDay(name, ordinal, suffix, note, new RotaCovers(lunch, dinner)); + } + + @SafeVarargs + private static RotaStaff staff(String name, List... days) { + return new RotaStaff(name, List.of(days)); + } + + private static List one(RotaShift shift) { + return List.of(shift); + } + + private static List split(RotaShift first, RotaShift second) { + return List.of(first, second); + } + + /** A day nobody is down for, which is a blank cell and not a missing one. */ + private static List none() { + return List.of(); + } + + private static RotaShift hours(String text) { + return RotaShift.hours(text); + } + + private static RotaShift off() { + return RotaShift.strong("OFF", ShiftStatus.OFF); + } + + private static RotaShift holiday() { + return RotaShift.strong("HOL", ShiftStatus.HOLIDAY); + } +} diff --git a/qa/src/test/java/com/demcha/compose/document/templates/rota/presets/CobaltRotaLayoutSnapshotTest.java b/qa/src/test/java/com/demcha/compose/document/templates/rota/presets/CobaltRotaLayoutSnapshotTest.java new file mode 100644 index 000000000..904dab383 --- /dev/null +++ b/qa/src/test/java/com/demcha/compose/document/templates/rota/presets/CobaltRotaLayoutSnapshotTest.java @@ -0,0 +1,35 @@ +package com.demcha.compose.document.templates.rota.presets; + +import com.demcha.compose.GraphCompose; +import com.demcha.compose.document.api.DocumentSession; +import com.demcha.compose.document.templates.TemplateTestSupport; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Exact layout snapshot gate for {@link CobaltRota} — freezes the page frame and + * the table's own box. + * + *

What this gate can and cannot see. The sheet is composed + * table cells almost end to end, and a composed cell emits fragments rather than + * a placed node, so the snapshot holds two nodes for eighty-four shift cells. It + * pins the page and the table that carries them — which is what would move if + * the margins, the column widths or the orientation changed — and the pixel + * baseline is what pins their contents. Neither gate is sufficient alone here, + * which is why both are fed by one fixture.

+ * + *

Refresh with {@code -Dgraphcompose.updateSnapshots=true} after a deliberate + * layout change, and commit the JSON with the change.

+ */ +class CobaltRotaLayoutSnapshotTest { + + @Test + void canonicalRotaMatchesLayoutSnapshot() throws Exception { + try (DocumentSession session = GraphCompose.document().create()) { + CobaltRota.create().compose(session, CobaltRotaFixtures.canonicalRota()); + assertThat(session.layoutSnapshot().totalPages()).isEqualTo(1); + TemplateTestSupport.assertCanonicalSnapshot(session, "cobalt_rota_layout", "rota"); + } + } +} diff --git a/qa/src/test/java/com/demcha/compose/document/templates/rota/presets/CobaltRotaSmokeTest.java b/qa/src/test/java/com/demcha/compose/document/templates/rota/presets/CobaltRotaSmokeTest.java new file mode 100644 index 000000000..02f40ae74 --- /dev/null +++ b/qa/src/test/java/com/demcha/compose/document/templates/rota/presets/CobaltRotaSmokeTest.java @@ -0,0 +1,324 @@ +package com.demcha.compose.document.templates.rota.presets; + +import com.demcha.compose.GraphCompose; +import com.demcha.compose.document.api.DocumentSession; +import com.demcha.compose.document.templates.api.DocumentTemplate; +import com.demcha.compose.document.templates.data.rota.RotaCovers; +import com.demcha.compose.document.templates.data.rota.RotaDay; +import com.demcha.compose.document.templates.data.rota.RotaGroup; +import com.demcha.compose.document.templates.data.rota.RotaLegend; +import com.demcha.compose.document.templates.data.rota.RotaShift; +import com.demcha.compose.document.templates.data.rota.RotaStaff; +import com.demcha.compose.document.templates.data.rota.ShiftStatus; +import com.demcha.compose.document.templates.data.rota.StructuredRotaData; +import com.demcha.compose.document.templates.data.rota.StructuredRotaDocumentSpec; +import org.apache.pdfbox.Loader; +import org.apache.pdfbox.pdmodel.PDDocument; +import org.apache.pdfbox.rendering.PDFRenderer; +import org.apache.pdfbox.text.PDFTextStripper; +import org.junit.jupiter.api.Test; + +import java.awt.image.BufferedImage; +import java.util.ArrayList; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.assertj.core.api.Assertions.within; + +/** + * Smoke test for {@link CobaltRota} — proves the preset renders a + * {@link StructuredRotaDocumentSpec} end-to-end with its packaged icons on one + * landscape page, renders an empty rota through its guards, follows the + * document's own day count rather than assuming a week, keeps a short or long + * legend square with the sheet, and reports an unknown icon token by name. + * + *

The sheet is composed table cells almost end to end, which emit no placed + * node of their own — the layout snapshot sees two nodes for eighty-four cells — + * so what a cell contains is asserted here, on the text layer.

+ */ +class CobaltRotaSmokeTest { + + private static byte[] render(StructuredRotaDocumentSpec spec) throws Exception { + // The preset owns its page geometry, so the session starts unconfigured. + try (DocumentSession session = GraphCompose.document().create()) { + CobaltRota.create().compose(session, spec); + assertThat(session.roots()).isNotEmpty(); + byte[] pdfBytes = session.toPdfBytes(); + assertThat(pdfBytes).isNotEmpty(); + return pdfBytes; + } + } + + private static String textOf(byte[] pdfBytes) throws Exception { + try (PDDocument document = Loader.loadPDF(pdfBytes)) { + return new PDFTextStripper().getText(document); + } + } + + @Test + void exposesStableIdentity() { + DocumentTemplate template = CobaltRota.create(); + assertThat(template.id()).isEqualTo(CobaltRota.ID); + assertThat(template.displayName()).isEqualTo(CobaltRota.DISPLAY_NAME); + } + + @Test + void rendersTheCanonicalRotaOnOneLandscapePage() throws Exception { + try (DocumentSession session = GraphCompose.document().create()) { + CobaltRota.create().compose(session, CobaltRotaFixtures.canonicalRota()); + assertThat(session.layoutSnapshot().totalPages()).isEqualTo(1); + } + } + + @Test + void rendersEmptyRota() throws Exception { + // Exercises the guards: no lockup, no days, no legend, no bands, no + // staff, and a footer with nothing to sign off with. + render(StructuredRotaDocumentSpec.from(StructuredRotaData.builder().build())); + } + + @Test + void canonicalRenderCarriesEveryRegionOnTheTextLayer() throws Exception { + // Eighty-four composed cells emit no placed node, so this is where their + // content is assertable at all. + String text = textOf(render(CobaltRotaFixtures.canonicalRota())); + assertThat(text) + .contains("QUAY").contains("QUAYSIDE BAR") + .contains("MONDAY").contains("SUNDAY") + .contains("STATUS LEGEND").contains("Standby") + .contains("COVERS").contains("104") + .contains("MANAGEMENT").contains("BARBACKS") + .contains("VESNA") + .contains("09:00-18:00").contains("HOL") + .contains("Quayside Bar"); + } + + @Test + void everyStatusIsDrawnInItsOwnColourAndCoversTheAreaItShould() throws Exception { + // What neither other gate can see. The snapshot holds two nodes for the + // whole sheet, and a pixel budget large enough to survive a font's + // antialiasing is large enough for a couple of dozen chips to lose their + // fill inside it. This counts the ink of each status directly: a chip + // drawn in the wrong colour, or drawn plain when it should be filled, + // moves two of these counts at once. + byte[] pdfBytes = render(CobaltRotaFixtures.canonicalRota()); + BufferedImage page; + try (PDDocument document = Loader.loadPDF(pdfBytes)) { + page = new PDFRenderer(document).renderImage(0, 1.0f); + } + assertArea(page, "REQUEST", 176, 176, 192, 1_278); + assertArea(page, "OFF", 208, 16, 16, 30_528); + assertArea(page, "HOLIDAY", 255, 192, 32, 22_712); + assertArea(page, "STOCK", 16, 160, 80, 13_455); + assertArea(page, "STANDBY", 192, 144, 224, 1_321); + assertArea(page, "TRAINING", 240, 80, 16, 7_475); + assertArea(page, "SUPPORT", 208, 216, 240, 1_290); + // The bands, the covers label and the rules; and the stripe under every + // other person, which is what a reader follows across the columns. + assertArea(page, "navy", 16, 32, 80, 55_857); + assertArea(page, "zebra", 228, 235, 248, 54_983); + } + + /** + * How much of the page one exact colour covers, against what it covered when + * the sheet was known good. + * + *

A tenth either way: a solid fill has no antialiasing in its middle, so + * these counts move with the shapes and not with the renderer. Losing one + * chip of a colour is about a sixth of the smallest count here.

+ */ + private static void assertArea(BufferedImage page, String what, + int red, int green, int blue, int expected) { + int rgb = (red << 16) | (green << 8) | blue; + int found = 0; + for (int y = 0; y < page.getHeight(); y++) { + for (int x = 0; x < page.getWidth(); x++) { + if ((page.getRGB(x, y) & 0xFFFFFF) == rgb) { + found++; + } + } + } + assertThat(found) + .describedAs("area drawn in %s", what) + .isBetween((int) (expected * 0.9), (int) (expected * 1.1)); + } + + @Test + void aSplitDayPrintsBothOfItsHalves() throws Exception { + // The shape the model exists for: a day holding two entries draws two + // chips, not one that wins. + StructuredRotaDocumentSpec spec = StructuredRotaDocumentSpec.from( + StructuredRotaData.builder() + .days(List.of(new RotaDay("MONDAY", "31", "ST"))) + .groups(List.of(new RotaGroup("BAR", List.of( + new RotaStaff("PRIYA", List.of(List.of( + RotaShift.strong("09:00-16:00", ShiftStatus.STOCK), + RotaShift.soft("16:00-22:00", ShiftStatus.STOCK)))))))) + .build()); + assertThat(textOf(render(spec))).contains("09:00-16:00").contains("16:00-22:00"); + } + + @Test + void theSheetHasAsManyDayColumnsAsTheDocumentHasDays() throws Exception { + // Nothing in the preset says a week: a five-day rota is as ordinary as a + // seven-day one, and a person's entries are read by position against the + // document's own day list. + StructuredRotaDocumentSpec spec = StructuredRotaDocumentSpec.from( + StructuredRotaData.builder() + .days(List.of( + new RotaDay("MONDAY", "31", "ST"), + new RotaDay("TUESDAY", "1", "ST"), + new RotaDay("WEDNESDAY", "2", "ND"), + new RotaDay("THURSDAY", "3", "RD"), + new RotaDay("FRIDAY", "4", "TH"))) + .groups(List.of(new RotaGroup("BAR", List.of( + new RotaStaff("PRIYA", List.of( + List.of(RotaShift.hours("09:00-17:00")), + List.of(), + List.of(RotaShift.strong("OFF", ShiftStatus.OFF)))))))) + .build()); + String text = textOf(render(spec)); + assertThat(text).contains("FRIDAY").contains("09:00-17:00").contains("OFF"); + assertThat(text).doesNotContain("SATURDAY"); + } + + @Test + void aLegendLongerThanTheWeekRunsOnRatherThanLosingItsTail() throws Exception { + // A status a reader cannot look up is worse than a second strip. Twelve + // statuses across seven columns are two strips, and every one of the + // twelve is on the sheet — asserting only the first would pass with the + // tail silently dropped. + List entries = new ArrayList<>(); + for (int i = 0; i < 12; i++) { + entries.add(new RotaLegend.Entry("S" + i, ShiftStatus.OFF)); + } + StructuredRotaData base = CobaltRotaFixtures.canonicalRota().rota(); + StructuredRotaDocumentSpec spec = StructuredRotaDocumentSpec.from( + StructuredRotaData.builder() + .venue(base.venue()).days(base.days()) + .legend(new RotaLegend("STATUS", "COVERS", "L", "D", entries)) + .build()); + String text = textOf(render(spec)); + for (int i = 0; i < 12; i++) { + assertThat(text).describedAs("legend entry S%d", i).contains("S" + i); + } + } + + @Test + void aLegendShorterThanTheWeekStillFillsTheSheetsWidth() throws Exception { + // The other side of the same seam: a short strip is closed off with + // empty cells so the table stays square. Two statuses under a seven-day + // week is one strip, and the sheet is as wide as it was. + StructuredRotaData base = CobaltRotaFixtures.canonicalRota().rota(); + StructuredRotaDocumentSpec spec = StructuredRotaDocumentSpec.from( + StructuredRotaData.builder() + .venue(base.venue()).days(base.days()) + .legend(new RotaLegend("STATUS", "COVERS", "L", "D", List.of( + new RotaLegend.Entry("S0", ShiftStatus.OFF), + new RotaLegend.Entry("S1", ShiftStatus.STOCK)))) + .build()); + try (DocumentSession session = GraphCompose.document().create()) { + CobaltRota.create().compose(session, spec); + assertThat(tableWidth(session)).isEqualTo(tableWidth(canonical()), within(0.01)); + assertThat(textOf(session.toPdfBytes())).contains("S0").contains("S1"); + } + } + + private static DocumentSession canonical() { + DocumentSession session = GraphCompose.document().create(); + CobaltRota.create().compose(session, CobaltRotaFixtures.canonicalRota()); + return session; + } + + /** The table's own placed width, which a ragged row would change. */ + private static double tableWidth(DocumentSession session) { + return session.layoutSnapshot().nodes().stream() + .filter(node -> "Schedule".equals(node.entityName())) + .findFirst() + .orElseThrow(() -> new AssertionError("no Schedule node")) + .placementWidth(); + } + + @Test + void aRotaThatStatesNoLegendOrCoversSpendsNoRowsOnThem() throws Exception { + // An empty navy bar over seven empty boxes is two rows of a sheet that + // is short of rows already. A rota that says nothing about either simply + // does not carry them. + StructuredRotaData base = CobaltRotaFixtures.canonicalRota().rota(); + StructuredRotaDocumentSpec spec = StructuredRotaDocumentSpec.from( + StructuredRotaData.builder() + .venue(base.venue()).week(base.week()).days(base.days()) + .groups(base.groups()).footer(base.footer()) + .build()); + try (DocumentSession session = GraphCompose.document().create()) { + CobaltRota.create().compose(session, spec); + assertThat(textOf(session.toPdfBytes())) + .doesNotContain("STATUS LEGEND").doesNotContain("COVERS"); + // Two rows lighter than the canonical sheet, which is the whole point. + assertThat(tableHeight(session)).isLessThan(tableHeight(canonical())); + } + } + + private static double tableHeight(DocumentSession session) { + return session.layoutSnapshot().nodes().stream() + .filter(node -> "Schedule".equals(node.entityName())) + .findFirst() + .orElseThrow(() -> new AssertionError("no Schedule node")) + .placementHeight(); + } + + @Test + void aBandThatNamesNoMarkIsStillTheBand() throws Exception { + // The strip and its label are the design's; the mark before them is the + // document's, and a band naming none simply opens with its label. + StructuredRotaDocumentSpec spec = StructuredRotaDocumentSpec.from( + StructuredRotaData.builder() + .days(List.of(new RotaDay("MONDAY", "31", "ST"))) + .groups(List.of(new RotaGroup("KITCHEN", List.of( + new RotaStaff("PRIYA", List.of(List.of())))))) + .build()); + assertThat(textOf(render(spec))).contains("KITCHEN").contains("PRIYA"); + } + + @Test + void rejectsUnknownIconTokenByName() { + StructuredRotaDocumentSpec spec = StructuredRotaDocumentSpec.from( + StructuredRotaData.builder() + .days(List.of(new RotaDay("MONDAY", "31", "ST"))) + .groups(List.of(new RotaGroup("KITCHEN", "no-such-icon", List.of()))) + .build()); + try (DocumentSession session = GraphCompose.document().create()) { + assertThatThrownBy(() -> CobaltRota.create().compose(session, spec)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("no-such-icon") + .hasMessageContaining("bartenders"); + } + } + + @Test + void aRotaTooLongForOnePageTakesASecondRatherThanLosingAnyone() throws Exception { + // The sheet holds twelve on a page. A thirteenth is not a fault and is + // certainly not dropped: the header repeats and the rota continues. + StructuredRotaData base = CobaltRotaFixtures.canonicalRota().rota(); + List crowd = new ArrayList<>(); + for (int i = 0; i < 40; i++) { + crowd.add(new RotaStaff("PERSON " + i, List.of())); + } + StructuredRotaDocumentSpec spec = StructuredRotaDocumentSpec.from( + StructuredRotaData.builder() + .venue(base.venue()).week(base.week()).days(base.days()) + .legend(base.legend()).footer(base.footer()) + .groups(List.of(new RotaGroup("BAR", "bartenders", crowd))) + .build()); + try (DocumentSession session = GraphCompose.document().create()) { + CobaltRota.create().compose(session, spec); + assertThat(session.layoutSnapshot().totalPages()).isGreaterThan(1); + String text = textOf(session.toPdfBytes()); + assertThat(text).contains("PERSON 0").contains("PERSON 39"); + // The header repeats, so the second page is not seven unlabelled + // columns: repeatHeader carries all four of its rows. + assertThat(text.split("MONDAY", -1).length - 1).isGreaterThan(1); + } + } +} diff --git a/qa/src/test/java/com/demcha/compose/document/templates/rota/presets/CobaltRotaVisualParityTest.java b/qa/src/test/java/com/demcha/compose/document/templates/rota/presets/CobaltRotaVisualParityTest.java new file mode 100644 index 000000000..323fa0f99 --- /dev/null +++ b/qa/src/test/java/com/demcha/compose/document/templates/rota/presets/CobaltRotaVisualParityTest.java @@ -0,0 +1,57 @@ +package com.demcha.compose.document.templates.rota.presets; + +import com.demcha.compose.GraphCompose; +import com.demcha.compose.document.api.DocumentSession; +import com.demcha.compose.testing.visual.PdfVisualRegression; +import org.junit.jupiter.api.Test; + +import java.nio.file.Path; + +/** + * Pixel-diff visual parity gate for {@link CobaltRota} — the canonical + * fixture's page against a checked-in baseline. + * + *

What this gate does and does not hold. The sheet is + * composed table cells almost end to end and the layout snapshot sees two nodes + * for the whole of it, so this baseline is the only thing that sees the sheet at + * all. It is not, on its own, enough: the page is about half a million pixels + * and one chip is under two thousand of them, so a budget wide enough to absorb + * a renderer's antialiasing is wide enough to hide several chips. The budget + * below is therefore a fraction of the sibling presets', and what pins the chips + * themselves is + * {@code CobaltRotaSmokeTest.everyStatusIsDrawnInItsOwnColourAndCoversTheAreaItShould}, + * which counts each status's ink directly.

+ * + *

Re-blessing baselines — after a deliberate visual change, + * re-run with {@code -Dgraphcompose.visual.approve=true} and commit the updated + * PNGs with the change. Baselines live under + * {@code src/test/resources/visual-baselines/rota/}.

+ */ +class CobaltRotaVisualParityTest { + + private static final Path BASELINE_ROOT = + Path.of("src", "test", "resources", "visual-baselines", "rota"); + + /** + * Tight, because the sheet is one deterministic table of solid fills and + * type: what varies between platforms is the antialiasing of the glyph edges + * and nothing else. Two thousand pixels is about one chip. + */ + private static final long PIXEL_DIFF_BUDGET = 2_000L; + private static final int PER_PIXEL_TOLERANCE = 8; + + @Test + void rendersWithinPixelDiffBudget() throws Exception { + byte[] pdfBytes; + try (DocumentSession document = GraphCompose.document().create()) { + CobaltRota.create().compose(document, CobaltRotaFixtures.canonicalRota()); + pdfBytes = document.toPdfBytes(); + } + + PdfVisualRegression.standard() + .baselineRoot(BASELINE_ROOT) + .perPixelTolerance(PER_PIXEL_TOLERANCE) + .mismatchedPixelBudget(PIXEL_DIFF_BUDGET) + .assertMatchesBaseline("cobalt_rota", pdfBytes); + } +} diff --git a/qa/src/test/java/com/demcha/testing/visual/TimelineRailVisualTest.java b/qa/src/test/java/com/demcha/testing/visual/TimelineRailVisualTest.java index cac5344a0..7297276d5 100644 --- a/qa/src/test/java/com/demcha/testing/visual/TimelineRailVisualTest.java +++ b/qa/src/test/java/com/demcha/testing/visual/TimelineRailVisualTest.java @@ -3,7 +3,7 @@ import com.demcha.compose.GraphCompose; import com.demcha.compose.document.api.DocumentSession; import com.demcha.compose.document.dsl.TimelineMarker; -import com.demcha.compose.document.dsl.TimelineRailExtent; +import com.demcha.compose.document.dsl.TimelineRailEnd; import com.demcha.compose.document.node.EllipseNode; import com.demcha.compose.document.style.DocumentColor; import com.demcha.compose.document.style.DocumentInsets; @@ -224,7 +224,7 @@ void entryBoundsRunsThroughEveryEntryAndOverTheSpacingBetweenThem() throws Excep // Entries of deliberately different heights, 16pt apart — and the line covers the // gaps, because an entry's spacing is padding inside its own box rather than a hole // between two boxes. That is the default every existing timeline draws. - try (DocumentSession session = extentScene(TimelineRailExtent.ENTRY_BOUNDS)) { + try (DocumentSession session = extentScene(TimelineRailEnd.ENTRY_BOUND)) { VISUAL.assertMatchesBaseline("timeline-dsl/entry-bounds", session); } } @@ -234,7 +234,7 @@ void markerToMarkerStopsAtTheOuterMarkersOnTheVerySameScene() throws Exception { // The other half. Same page, same entries, same rail — and now the line begins at // the first marker and ends at the last, with the tall entry's body hanging below // it. Read against its twin, the diff is the two ends and nothing else. - try (DocumentSession session = extentScene(TimelineRailExtent.MARKER_TO_MARKER)) { + try (DocumentSession session = extentScene(TimelineRailEnd.MARKER)) { VISUAL.assertMatchesBaseline("timeline-dsl/marker-to-marker", session); } } @@ -255,7 +255,7 @@ void aPaginatedTimelineDrawsItsRailOnEveryPageItReaches() throws Exception { .spacing(14) .markerOnRail() .axisWidth(24) - .rail(rail -> rail.extent(TimelineRailExtent.MARKER_TO_MARKER)) + .rail(rail -> rail.from(TimelineRailEnd.MARKER).to(TimelineRailEnd.MARKER)) .entry(TimelineMarker.dot(10, INK), e -> e .title("Runs on").body(longBody())) .entry(TimelineMarker.dot(10, INK), e -> e @@ -296,7 +296,7 @@ void aHollowMarkerBreaksTheRailCleanlyBecauseTheRailIsUnderIt() throws Exception } /** The scene both extent baselines draw; they differ by this argument and nothing else. */ - private static DocumentSession extentScene(TimelineRailExtent extent) throws Exception { + private static DocumentSession extentScene(TimelineRailEnd both) throws Exception { DocumentSession session = GraphCompose.document() .pageSize(300, 250) .margin(DocumentInsets.of(18)) @@ -305,7 +305,7 @@ private static DocumentSession extentScene(TimelineRailExtent extent) throws Exc .addTimeline(t -> t .connector(RAIL, 1.5) .spacing(16) - .rail(rail -> rail.extent(extent)) + .rail(rail -> rail.from(both).to(both)) .entry(TimelineMarker.dot(9, INK), e -> e .title("Tall entry").meta("2023 - Present") .body("A body long enough to run to three lines on a page this " diff --git a/qa/src/test/resources/fonts/oswald/OFL.txt b/qa/src/test/resources/fonts/oswald/OFL.txt new file mode 100644 index 000000000..9d0219e98 --- /dev/null +++ b/qa/src/test/resources/fonts/oswald/OFL.txt @@ -0,0 +1,93 @@ +Copyright 2016 The Oswald Project Authors (https://github.com/googlefonts/OswaldFont) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +https://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/qa/src/test/resources/fonts/oswald/Oswald-Regular.ttf b/qa/src/test/resources/fonts/oswald/Oswald-Regular.ttf new file mode 100644 index 000000000..4a4373d1a Binary files /dev/null and b/qa/src/test/resources/fonts/oswald/Oswald-Regular.ttf differ diff --git a/qa/src/test/resources/fonts/oswald/Oswald-SemiBold.ttf b/qa/src/test/resources/fonts/oswald/Oswald-SemiBold.ttf new file mode 100644 index 000000000..26aa8f272 Binary files /dev/null and b/qa/src/test/resources/fonts/oswald/Oswald-SemiBold.ttf differ diff --git a/qa/src/test/resources/layout-snapshots/canonical-templates/cv-v2/charcoal_gold_layout.json b/qa/src/test/resources/layout-snapshots/canonical-templates/cv-v2/charcoal_gold_layout.json new file mode 100644 index 000000000..0732e455a --- /dev/null +++ b/qa/src/test/resources/layout-snapshots/canonical-templates/cv-v2/charcoal_gold_layout.json @@ -0,0 +1,8177 @@ +{ + "formatVersion" : "2.0", + "canvas" : { + "pageWidth" : 595.276, + "pageHeight" : 841.89, + "innerWidth" : 595.276, + "innerHeight" : 841.89, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, + "totalPages" : 1, + "nodes" : [ { + "path" : "CharcoalGoldCv[0]", + "entityName" : "CharcoalGoldCv", + "entityKind" : "ContainerNode", + "parentPath" : null, + "childIndex" : 0, + "depth" : 1, + "layer" : 1, + "computedX" : 0.0, + "computedY" : 29.49, + "placementX" : 0.0, + "placementY" : 29.49, + "placementWidth" : 595.276, + "placementHeight" : 812.4, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 595.276, + "contentHeight" : 812.4, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]", + "entityName" : "Body", + "entityKind" : "RowNode", + "parentPath" : "CharcoalGoldCv[0]", + "childIndex" : 0, + "depth" : 2, + "layer" : 2, + "computedX" : 0.0, + "computedY" : 29.49, + "placementX" : 0.0, + "placementY" : 29.49, + "placementWidth" : 595.276, + "placementHeight" : 812.4, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 595.276, + "contentHeight" : 812.4, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]", + "entityName" : "Sidebar", + "entityKind" : "SectionNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]", + "childIndex" : 0, + "depth" : 3, + "layer" : 3, + "computedX" : 0.0, + "computedY" : 44.209, + "placementX" : 0.0, + "placementY" : 44.209, + "placementWidth" : 190.31, + "placementHeight" : 797.68, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 190.31, + "contentHeight" : 797.68, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 24.3, + "right" : 17.0, + "bottom" : 0.0, + "left" : 17.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Align[0]", + "entityName" : null, + "entityKind" : "Align", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 17.0, + "computedY" : 692.229, + "placementX" : 17.0, + "placementY" : 692.229, + "placementWidth" : 156.31, + "placementHeight" : 125.36, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 156.31, + "contentHeight" : 125.36, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Align[0]/ProfilePhoto[0]", + "entityName" : "ProfilePhoto", + "entityKind" : "ShapeContainerNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Align[0]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 32.475, + "computedY" : 692.229, + "placementX" : 32.475, + "placementY" : 692.229, + "placementWidth" : 125.36, + "placementHeight" : 125.36, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 125.36, + "contentHeight" : 125.36, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Align[0]/ProfilePhoto[0]/PortraitImage[0]", + "entityName" : "PortraitImage", + "entityKind" : "ImageNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Align[0]/ProfilePhoto[0]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 32.475, + "computedY" : 692.229, + "placementX" : 32.475, + "placementY" : 692.229, + "placementWidth" : 125.36, + "placementHeight" : 125.36, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 125.36, + "contentHeight" : 125.36, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Contact[1]", + "entityName" : "Contact", + "entityKind" : "SectionNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 17.0, + "computedY" : 542.829, + "placementX" : 17.0, + "placementY" : 542.829, + "placementWidth" : 119.526, + "placementHeight" : 120.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 119.526, + "contentHeight" : 120.0, + "margin" : { + "top" : 29.4, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Contact[1]/Heading_CONTACT[0]", + "entityName" : "Heading_CONTACT", + "entityKind" : "SectionNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Contact[1]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 17.0, + "computedY" : 650.829, + "placementX" : 17.0, + "placementY" : 650.829, + "placementWidth" : 56.6, + "placementHeight" : 12.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 56.6, + "contentHeight" : 12.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 9.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Contact[1]/Heading_CONTACT[0]/HeadingText_CONTACT[0]", + "entityName" : "HeadingText_CONTACT", + "entityKind" : "ParagraphNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Contact[1]/Heading_CONTACT[0]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 26.0, + "computedY" : 650.829, + "placementX" : 26.0, + "placementY" : 650.829, + "placementWidth" : 47.6, + "placementHeight" : 12.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 47.6, + "contentHeight" : 12.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Contact[1]/Contact_0[1]", + "entityName" : "Contact_0", + "entityKind" : "ParagraphNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Contact[1]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 17.0, + "computedY" : 629.469, + "placementX" : 17.0, + "placementY" : 629.469, + "placementWidth" : 75.799, + "placementHeight" : 9.36, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 75.799, + "contentHeight" : 9.36, + "margin" : { + "top" : 12.0, + "right" : 0.0, + "bottom" : 9.84, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Contact[1]/Contact_1[2]", + "entityName" : "Contact_1", + "entityKind" : "ParagraphNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Contact[1]", + "childIndex" : 2, + "depth" : 5, + "layer" : 5, + "computedX" : 17.0, + "computedY" : 610.269, + "placementX" : 17.0, + "placementY" : 610.269, + "placementWidth" : 102.616, + "placementHeight" : 9.36, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 102.616, + "contentHeight" : 9.36, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 9.84, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Contact[1]/Contact_2[3]", + "entityName" : "Contact_2", + "entityKind" : "ParagraphNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Contact[1]", + "childIndex" : 3, + "depth" : 5, + "layer" : 5, + "computedX" : 17.0, + "computedY" : 591.069, + "placementX" : 17.0, + "placementY" : 591.069, + "placementWidth" : 98.599, + "placementHeight" : 9.36, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 98.599, + "contentHeight" : 9.36, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 9.84, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Contact[1]/Contact_3[4]", + "entityName" : "Contact_3", + "entityKind" : "ParagraphNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Contact[1]", + "childIndex" : 4, + "depth" : 5, + "layer" : 5, + "computedX" : 17.0, + "computedY" : 571.869, + "placementX" : 17.0, + "placementY" : 571.869, + "placementWidth" : 79.512, + "placementHeight" : 9.36, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 79.512, + "contentHeight" : 9.36, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 9.84, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Contact[1]/Contact_4[5]", + "entityName" : "Contact_4", + "entityKind" : "ParagraphNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Contact[1]", + "childIndex" : 5, + "depth" : 5, + "layer" : 5, + "computedX" : 17.0, + "computedY" : 552.669, + "placementX" : 17.0, + "placementY" : 552.669, + "placementWidth" : 119.526, + "placementHeight" : 9.36, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 119.526, + "contentHeight" : 9.36, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 9.84, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Divider_AfterContact[2]", + "entityName" : "Divider_AfterContact", + "entityKind" : "LineNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]", + "childIndex" : 2, + "depth" : 4, + "layer" : 4, + "computedX" : 17.0, + "computedY" : 522.829, + "placementX" : 17.0, + "placementY" : 522.829, + "placementWidth" : 156.31, + "placementHeight" : 1.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 156.31, + "contentHeight" : 1.0, + "margin" : { + "top" : 19.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Skills[3]", + "entityName" : "Skills", + "entityKind" : "SectionNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]", + "childIndex" : 3, + "depth" : 4, + "layer" : 4, + "computedX" : 17.0, + "computedY" : 315.829, + "placementX" : 17.0, + "placementY" : 315.829, + "placementWidth" : 156.31, + "placementHeight" : 190.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 156.31, + "contentHeight" : 190.0, + "margin" : { + "top" : 17.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Skills[3]/Heading_SKILLS[0]", + "entityName" : "Heading_SKILLS", + "entityKind" : "SectionNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Skills[3]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 17.0, + "computedY" : 493.829, + "placementX" : 17.0, + "placementY" : 493.829, + "placementWidth" : 39.57, + "placementHeight" : 12.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 39.57, + "contentHeight" : 12.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 9.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Skills[3]/Heading_SKILLS[0]/HeadingText_SKILLS[0]", + "entityName" : "HeadingText_SKILLS", + "entityKind" : "ParagraphNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Skills[3]/Heading_SKILLS[0]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 26.0, + "computedY" : 493.829, + "placementX" : 26.0, + "placementY" : 493.829, + "placementWidth" : 30.57, + "placementHeight" : 12.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 30.57, + "contentHeight" : 12.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Skills[3]/Skill_0Stack[1]", + "entityName" : "Skill_0Stack", + "entityKind" : "LayerStackNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Skills[3]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 17.0, + "computedY" : 472.469, + "placementX" : 17.0, + "placementY" : 472.469, + "placementWidth" : 156.31, + "placementHeight" : 9.36, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 156.31, + "contentHeight" : 9.36, + "margin" : { + "top" : 12.0, + "right" : 0.0, + "bottom" : 7.24, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Skills[3]/Skill_0Stack[1]/Skill_0Layer[0]", + "entityName" : "Skill_0Layer", + "entityKind" : "SectionNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Skills[3]/Skill_0Stack[1]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 17.0, + "computedY" : 472.469, + "placementX" : 17.0, + "placementY" : 472.469, + "placementWidth" : 156.31, + "placementHeight" : 9.36, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 156.31, + "contentHeight" : 9.36, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Skills[3]/Skill_0Stack[1]/Skill_0Layer[0]/Skill_0[0]", + "entityName" : "Skill_0", + "entityKind" : "RowNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Skills[3]/Skill_0Stack[1]/Skill_0Layer[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 17.0, + "computedY" : 472.469, + "placementX" : 17.0, + "placementY" : 472.469, + "placementWidth" : 156.31, + "placementHeight" : 9.36, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 156.31, + "contentHeight" : 9.36, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Skills[3]/Skill_0Stack[1]/Skill_0Layer[0]/Skill_0[0]/SkillBullet_0[0]", + "entityName" : "SkillBullet_0", + "entityKind" : "ParagraphNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Skills[3]/Skill_0Stack[1]/Skill_0Layer[0]/Skill_0[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 17.0, + "computedY" : 472.469, + "placementX" : 17.0, + "placementY" : 472.469, + "placementWidth" : 9.5, + "placementHeight" : 9.36, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 9.5, + "contentHeight" : 9.36, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Skills[3]/Skill_0Stack[1]/Skill_0Layer[0]/Skill_0[0]/SkillName_0[1]", + "entityName" : "SkillName_0", + "entityKind" : "ParagraphNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Skills[3]/Skill_0Stack[1]/Skill_0Layer[0]/Skill_0[0]", + "childIndex" : 1, + "depth" : 8, + "layer" : 8, + "computedX" : 26.5, + "computedY" : 472.469, + "placementX" : 26.5, + "placementY" : 472.469, + "placementWidth" : 71.682, + "placementHeight" : 9.36, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 71.682, + "contentHeight" : 9.36, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Skills[3]/Skill_0Stack[1]/Skill_0Layer[0]/Skill_0[0]/SkillRating_0[2]", + "entityName" : "SkillRating_0", + "entityKind" : "ParagraphNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Skills[3]/Skill_0Stack[1]/Skill_0Layer[0]/Skill_0[0]", + "childIndex" : 2, + "depth" : 8, + "layer" : 8, + "computedX" : 132.203, + "computedY" : 472.469, + "placementX" : 132.203, + "placementY" : 472.469, + "placementWidth" : 41.107, + "placementHeight" : 9.36, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 41.107, + "contentHeight" : 9.36, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Skills[3]/Skill_1Stack[2]", + "entityName" : "Skill_1Stack", + "entityKind" : "LayerStackNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Skills[3]", + "childIndex" : 2, + "depth" : 5, + "layer" : 5, + "computedX" : 17.0, + "computedY" : 455.869, + "placementX" : 17.0, + "placementY" : 455.869, + "placementWidth" : 156.31, + "placementHeight" : 9.36, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 156.31, + "contentHeight" : 9.36, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 7.24, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Skills[3]/Skill_1Stack[2]/Skill_1Layer[0]", + "entityName" : "Skill_1Layer", + "entityKind" : "SectionNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Skills[3]/Skill_1Stack[2]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 17.0, + "computedY" : 455.869, + "placementX" : 17.0, + "placementY" : 455.869, + "placementWidth" : 156.31, + "placementHeight" : 9.36, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 156.31, + "contentHeight" : 9.36, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Skills[3]/Skill_1Stack[2]/Skill_1Layer[0]/Skill_1[0]", + "entityName" : "Skill_1", + "entityKind" : "RowNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Skills[3]/Skill_1Stack[2]/Skill_1Layer[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 17.0, + "computedY" : 455.869, + "placementX" : 17.0, + "placementY" : 455.869, + "placementWidth" : 156.31, + "placementHeight" : 9.36, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 156.31, + "contentHeight" : 9.36, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Skills[3]/Skill_1Stack[2]/Skill_1Layer[0]/Skill_1[0]/SkillBullet_1[0]", + "entityName" : "SkillBullet_1", + "entityKind" : "ParagraphNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Skills[3]/Skill_1Stack[2]/Skill_1Layer[0]/Skill_1[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 17.0, + "computedY" : 455.869, + "placementX" : 17.0, + "placementY" : 455.869, + "placementWidth" : 9.5, + "placementHeight" : 9.36, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 9.5, + "contentHeight" : 9.36, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Skills[3]/Skill_1Stack[2]/Skill_1Layer[0]/Skill_1[0]/SkillName_1[1]", + "entityName" : "SkillName_1", + "entityKind" : "ParagraphNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Skills[3]/Skill_1Stack[2]/Skill_1Layer[0]/Skill_1[0]", + "childIndex" : 1, + "depth" : 8, + "layer" : 8, + "computedX" : 26.5, + "computedY" : 455.869, + "placementX" : 26.5, + "placementY" : 455.869, + "placementWidth" : 84.427, + "placementHeight" : 9.36, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 84.427, + "contentHeight" : 9.36, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Skills[3]/Skill_1Stack[2]/Skill_1Layer[0]/Skill_1[0]/SkillRating_1[2]", + "entityName" : "SkillRating_1", + "entityKind" : "ParagraphNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Skills[3]/Skill_1Stack[2]/Skill_1Layer[0]/Skill_1[0]", + "childIndex" : 2, + "depth" : 8, + "layer" : 8, + "computedX" : 132.203, + "computedY" : 455.869, + "placementX" : 132.203, + "placementY" : 455.869, + "placementWidth" : 41.107, + "placementHeight" : 9.36, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 41.107, + "contentHeight" : 9.36, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Skills[3]/Skill_2Stack[3]", + "entityName" : "Skill_2Stack", + "entityKind" : "LayerStackNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Skills[3]", + "childIndex" : 3, + "depth" : 5, + "layer" : 5, + "computedX" : 17.0, + "computedY" : 439.269, + "placementX" : 17.0, + "placementY" : 439.269, + "placementWidth" : 156.31, + "placementHeight" : 9.36, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 156.31, + "contentHeight" : 9.36, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 7.24, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Skills[3]/Skill_2Stack[3]/Skill_2Layer[0]", + "entityName" : "Skill_2Layer", + "entityKind" : "SectionNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Skills[3]/Skill_2Stack[3]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 17.0, + "computedY" : 439.269, + "placementX" : 17.0, + "placementY" : 439.269, + "placementWidth" : 156.31, + "placementHeight" : 9.36, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 156.31, + "contentHeight" : 9.36, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Skills[3]/Skill_2Stack[3]/Skill_2Layer[0]/Skill_2[0]", + "entityName" : "Skill_2", + "entityKind" : "RowNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Skills[3]/Skill_2Stack[3]/Skill_2Layer[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 17.0, + "computedY" : 439.269, + "placementX" : 17.0, + "placementY" : 439.269, + "placementWidth" : 156.31, + "placementHeight" : 9.36, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 156.31, + "contentHeight" : 9.36, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Skills[3]/Skill_2Stack[3]/Skill_2Layer[0]/Skill_2[0]/SkillBullet_2[0]", + "entityName" : "SkillBullet_2", + "entityKind" : "ParagraphNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Skills[3]/Skill_2Stack[3]/Skill_2Layer[0]/Skill_2[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 17.0, + "computedY" : 439.269, + "placementX" : 17.0, + "placementY" : 439.269, + "placementWidth" : 9.5, + "placementHeight" : 9.36, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 9.5, + "contentHeight" : 9.36, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Skills[3]/Skill_2Stack[3]/Skill_2Layer[0]/Skill_2[0]/SkillName_2[1]", + "entityName" : "SkillName_2", + "entityKind" : "ParagraphNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Skills[3]/Skill_2Stack[3]/Skill_2Layer[0]/Skill_2[0]", + "childIndex" : 1, + "depth" : 8, + "layer" : 8, + "computedX" : 26.5, + "computedY" : 439.269, + "placementX" : 26.5, + "placementY" : 439.269, + "placementWidth" : 97.032, + "placementHeight" : 9.36, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 97.032, + "contentHeight" : 9.36, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Skills[3]/Skill_2Stack[3]/Skill_2Layer[0]/Skill_2[0]/SkillRating_2[2]", + "entityName" : "SkillRating_2", + "entityKind" : "ParagraphNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Skills[3]/Skill_2Stack[3]/Skill_2Layer[0]/Skill_2[0]", + "childIndex" : 2, + "depth" : 8, + "layer" : 8, + "computedX" : 132.203, + "computedY" : 439.269, + "placementX" : 132.203, + "placementY" : 439.269, + "placementWidth" : 41.107, + "placementHeight" : 9.36, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 41.107, + "contentHeight" : 9.36, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Skills[3]/Skill_3Stack[4]", + "entityName" : "Skill_3Stack", + "entityKind" : "LayerStackNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Skills[3]", + "childIndex" : 4, + "depth" : 5, + "layer" : 5, + "computedX" : 17.0, + "computedY" : 422.669, + "placementX" : 17.0, + "placementY" : 422.669, + "placementWidth" : 156.31, + "placementHeight" : 9.36, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 156.31, + "contentHeight" : 9.36, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 7.24, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Skills[3]/Skill_3Stack[4]/Skill_3Layer[0]", + "entityName" : "Skill_3Layer", + "entityKind" : "SectionNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Skills[3]/Skill_3Stack[4]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 17.0, + "computedY" : 422.669, + "placementX" : 17.0, + "placementY" : 422.669, + "placementWidth" : 156.31, + "placementHeight" : 9.36, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 156.31, + "contentHeight" : 9.36, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Skills[3]/Skill_3Stack[4]/Skill_3Layer[0]/Skill_3[0]", + "entityName" : "Skill_3", + "entityKind" : "RowNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Skills[3]/Skill_3Stack[4]/Skill_3Layer[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 17.0, + "computedY" : 422.669, + "placementX" : 17.0, + "placementY" : 422.669, + "placementWidth" : 156.31, + "placementHeight" : 9.36, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 156.31, + "contentHeight" : 9.36, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Skills[3]/Skill_3Stack[4]/Skill_3Layer[0]/Skill_3[0]/SkillBullet_3[0]", + "entityName" : "SkillBullet_3", + "entityKind" : "ParagraphNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Skills[3]/Skill_3Stack[4]/Skill_3Layer[0]/Skill_3[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 17.0, + "computedY" : 422.669, + "placementX" : 17.0, + "placementY" : 422.669, + "placementWidth" : 9.5, + "placementHeight" : 9.36, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 9.5, + "contentHeight" : 9.36, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Skills[3]/Skill_3Stack[4]/Skill_3Layer[0]/Skill_3[0]/SkillName_3[1]", + "entityName" : "SkillName_3", + "entityKind" : "ParagraphNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Skills[3]/Skill_3Stack[4]/Skill_3Layer[0]/Skill_3[0]", + "childIndex" : 1, + "depth" : 8, + "layer" : 8, + "computedX" : 26.5, + "computedY" : 422.669, + "placementX" : 26.5, + "placementY" : 422.669, + "placementWidth" : 61.316, + "placementHeight" : 9.36, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 61.316, + "contentHeight" : 9.36, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Skills[3]/Skill_3Stack[4]/Skill_3Layer[0]/Skill_3[0]/SkillRating_3[2]", + "entityName" : "SkillRating_3", + "entityKind" : "ParagraphNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Skills[3]/Skill_3Stack[4]/Skill_3Layer[0]/Skill_3[0]", + "childIndex" : 2, + "depth" : 8, + "layer" : 8, + "computedX" : 132.203, + "computedY" : 422.669, + "placementX" : 132.203, + "placementY" : 422.669, + "placementWidth" : 41.107, + "placementHeight" : 9.36, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 41.107, + "contentHeight" : 9.36, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Skills[3]/Skill_4Stack[5]", + "entityName" : "Skill_4Stack", + "entityKind" : "LayerStackNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Skills[3]", + "childIndex" : 5, + "depth" : 5, + "layer" : 5, + "computedX" : 17.0, + "computedY" : 406.069, + "placementX" : 17.0, + "placementY" : 406.069, + "placementWidth" : 156.31, + "placementHeight" : 9.36, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 156.31, + "contentHeight" : 9.36, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 7.24, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Skills[3]/Skill_4Stack[5]/Skill_4Layer[0]", + "entityName" : "Skill_4Layer", + "entityKind" : "SectionNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Skills[3]/Skill_4Stack[5]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 17.0, + "computedY" : 406.069, + "placementX" : 17.0, + "placementY" : 406.069, + "placementWidth" : 156.31, + "placementHeight" : 9.36, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 156.31, + "contentHeight" : 9.36, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Skills[3]/Skill_4Stack[5]/Skill_4Layer[0]/Skill_4[0]", + "entityName" : "Skill_4", + "entityKind" : "RowNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Skills[3]/Skill_4Stack[5]/Skill_4Layer[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 17.0, + "computedY" : 406.069, + "placementX" : 17.0, + "placementY" : 406.069, + "placementWidth" : 156.31, + "placementHeight" : 9.36, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 156.31, + "contentHeight" : 9.36, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Skills[3]/Skill_4Stack[5]/Skill_4Layer[0]/Skill_4[0]/SkillBullet_4[0]", + "entityName" : "SkillBullet_4", + "entityKind" : "ParagraphNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Skills[3]/Skill_4Stack[5]/Skill_4Layer[0]/Skill_4[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 17.0, + "computedY" : 406.069, + "placementX" : 17.0, + "placementY" : 406.069, + "placementWidth" : 9.5, + "placementHeight" : 9.36, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 9.5, + "contentHeight" : 9.36, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Skills[3]/Skill_4Stack[5]/Skill_4Layer[0]/Skill_4[0]/SkillName_4[1]", + "entityName" : "SkillName_4", + "entityKind" : "ParagraphNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Skills[3]/Skill_4Stack[5]/Skill_4Layer[0]/Skill_4[0]", + "childIndex" : 1, + "depth" : 8, + "layer" : 8, + "computedX" : 26.5, + "computedY" : 406.069, + "placementX" : 26.5, + "placementY" : 406.069, + "placementWidth" : 46.488, + "placementHeight" : 9.36, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 46.488, + "contentHeight" : 9.36, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Skills[3]/Skill_4Stack[5]/Skill_4Layer[0]/Skill_4[0]/SkillRating_4[2]", + "entityName" : "SkillRating_4", + "entityKind" : "ParagraphNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Skills[3]/Skill_4Stack[5]/Skill_4Layer[0]/Skill_4[0]", + "childIndex" : 2, + "depth" : 8, + "layer" : 8, + "computedX" : 132.203, + "computedY" : 406.069, + "placementX" : 132.203, + "placementY" : 406.069, + "placementWidth" : 41.107, + "placementHeight" : 9.36, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 41.107, + "contentHeight" : 9.36, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Skills[3]/Skill_5Stack[6]", + "entityName" : "Skill_5Stack", + "entityKind" : "LayerStackNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Skills[3]", + "childIndex" : 6, + "depth" : 5, + "layer" : 5, + "computedX" : 17.0, + "computedY" : 389.469, + "placementX" : 17.0, + "placementY" : 389.469, + "placementWidth" : 156.31, + "placementHeight" : 9.36, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 156.31, + "contentHeight" : 9.36, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 7.24, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Skills[3]/Skill_5Stack[6]/Skill_5Layer[0]", + "entityName" : "Skill_5Layer", + "entityKind" : "SectionNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Skills[3]/Skill_5Stack[6]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 17.0, + "computedY" : 389.469, + "placementX" : 17.0, + "placementY" : 389.469, + "placementWidth" : 156.31, + "placementHeight" : 9.36, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 156.31, + "contentHeight" : 9.36, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Skills[3]/Skill_5Stack[6]/Skill_5Layer[0]/Skill_5[0]", + "entityName" : "Skill_5", + "entityKind" : "RowNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Skills[3]/Skill_5Stack[6]/Skill_5Layer[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 17.0, + "computedY" : 389.469, + "placementX" : 17.0, + "placementY" : 389.469, + "placementWidth" : 156.31, + "placementHeight" : 9.36, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 156.31, + "contentHeight" : 9.36, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Skills[3]/Skill_5Stack[6]/Skill_5Layer[0]/Skill_5[0]/SkillBullet_5[0]", + "entityName" : "SkillBullet_5", + "entityKind" : "ParagraphNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Skills[3]/Skill_5Stack[6]/Skill_5Layer[0]/Skill_5[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 17.0, + "computedY" : 389.469, + "placementX" : 17.0, + "placementY" : 389.469, + "placementWidth" : 9.5, + "placementHeight" : 9.36, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 9.5, + "contentHeight" : 9.36, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Skills[3]/Skill_5Stack[6]/Skill_5Layer[0]/Skill_5[0]/SkillName_5[1]", + "entityName" : "SkillName_5", + "entityKind" : "ParagraphNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Skills[3]/Skill_5Stack[6]/Skill_5Layer[0]/Skill_5[0]", + "childIndex" : 1, + "depth" : 8, + "layer" : 8, + "computedX" : 26.5, + "computedY" : 389.469, + "placementX" : 26.5, + "placementY" : 389.469, + "placementWidth" : 74.623, + "placementHeight" : 9.36, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 74.623, + "contentHeight" : 9.36, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Skills[3]/Skill_5Stack[6]/Skill_5Layer[0]/Skill_5[0]/SkillRating_5[2]", + "entityName" : "SkillRating_5", + "entityKind" : "ParagraphNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Skills[3]/Skill_5Stack[6]/Skill_5Layer[0]/Skill_5[0]", + "childIndex" : 2, + "depth" : 8, + "layer" : 8, + "computedX" : 132.203, + "computedY" : 389.469, + "placementX" : 132.203, + "placementY" : 389.469, + "placementWidth" : 41.107, + "placementHeight" : 9.36, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 41.107, + "contentHeight" : 9.36, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Skills[3]/Skill_6Stack[7]", + "entityName" : "Skill_6Stack", + "entityKind" : "LayerStackNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Skills[3]", + "childIndex" : 7, + "depth" : 5, + "layer" : 5, + "computedX" : 17.0, + "computedY" : 372.869, + "placementX" : 17.0, + "placementY" : 372.869, + "placementWidth" : 156.31, + "placementHeight" : 9.36, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 156.31, + "contentHeight" : 9.36, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 7.24, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Skills[3]/Skill_6Stack[7]/Skill_6Layer[0]", + "entityName" : "Skill_6Layer", + "entityKind" : "SectionNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Skills[3]/Skill_6Stack[7]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 17.0, + "computedY" : 372.869, + "placementX" : 17.0, + "placementY" : 372.869, + "placementWidth" : 156.31, + "placementHeight" : 9.36, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 156.31, + "contentHeight" : 9.36, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Skills[3]/Skill_6Stack[7]/Skill_6Layer[0]/Skill_6[0]", + "entityName" : "Skill_6", + "entityKind" : "RowNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Skills[3]/Skill_6Stack[7]/Skill_6Layer[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 17.0, + "computedY" : 372.869, + "placementX" : 17.0, + "placementY" : 372.869, + "placementWidth" : 156.31, + "placementHeight" : 9.36, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 156.31, + "contentHeight" : 9.36, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Skills[3]/Skill_6Stack[7]/Skill_6Layer[0]/Skill_6[0]/SkillBullet_6[0]", + "entityName" : "SkillBullet_6", + "entityKind" : "ParagraphNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Skills[3]/Skill_6Stack[7]/Skill_6Layer[0]/Skill_6[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 17.0, + "computedY" : 372.869, + "placementX" : 17.0, + "placementY" : 372.869, + "placementWidth" : 9.5, + "placementHeight" : 9.36, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 9.5, + "contentHeight" : 9.36, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Skills[3]/Skill_6Stack[7]/Skill_6Layer[0]/Skill_6[0]/SkillName_6[1]", + "entityName" : "SkillName_6", + "entityKind" : "ParagraphNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Skills[3]/Skill_6Stack[7]/Skill_6Layer[0]/Skill_6[0]", + "childIndex" : 1, + "depth" : 8, + "layer" : 8, + "computedX" : 26.5, + "computedY" : 372.869, + "placementX" : 26.5, + "placementY" : 372.869, + "placementWidth" : 53.524, + "placementHeight" : 9.36, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 53.524, + "contentHeight" : 9.36, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Skills[3]/Skill_6Stack[7]/Skill_6Layer[0]/Skill_6[0]/SkillRating_6[2]", + "entityName" : "SkillRating_6", + "entityKind" : "ParagraphNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Skills[3]/Skill_6Stack[7]/Skill_6Layer[0]/Skill_6[0]", + "childIndex" : 2, + "depth" : 8, + "layer" : 8, + "computedX" : 132.203, + "computedY" : 372.869, + "placementX" : 132.203, + "placementY" : 372.869, + "placementWidth" : 41.107, + "placementHeight" : 9.36, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 41.107, + "contentHeight" : 9.36, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Skills[3]/Skill_7Stack[8]", + "entityName" : "Skill_7Stack", + "entityKind" : "LayerStackNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Skills[3]", + "childIndex" : 8, + "depth" : 5, + "layer" : 5, + "computedX" : 17.0, + "computedY" : 356.269, + "placementX" : 17.0, + "placementY" : 356.269, + "placementWidth" : 156.31, + "placementHeight" : 9.36, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 156.31, + "contentHeight" : 9.36, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 7.24, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Skills[3]/Skill_7Stack[8]/Skill_7Layer[0]", + "entityName" : "Skill_7Layer", + "entityKind" : "SectionNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Skills[3]/Skill_7Stack[8]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 17.0, + "computedY" : 356.269, + "placementX" : 17.0, + "placementY" : 356.269, + "placementWidth" : 156.31, + "placementHeight" : 9.36, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 156.31, + "contentHeight" : 9.36, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Skills[3]/Skill_7Stack[8]/Skill_7Layer[0]/Skill_7[0]", + "entityName" : "Skill_7", + "entityKind" : "RowNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Skills[3]/Skill_7Stack[8]/Skill_7Layer[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 17.0, + "computedY" : 356.269, + "placementX" : 17.0, + "placementY" : 356.269, + "placementWidth" : 156.31, + "placementHeight" : 9.36, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 156.31, + "contentHeight" : 9.36, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Skills[3]/Skill_7Stack[8]/Skill_7Layer[0]/Skill_7[0]/SkillBullet_7[0]", + "entityName" : "SkillBullet_7", + "entityKind" : "ParagraphNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Skills[3]/Skill_7Stack[8]/Skill_7Layer[0]/Skill_7[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 17.0, + "computedY" : 356.269, + "placementX" : 17.0, + "placementY" : 356.269, + "placementWidth" : 9.5, + "placementHeight" : 9.36, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 9.5, + "contentHeight" : 9.36, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Skills[3]/Skill_7Stack[8]/Skill_7Layer[0]/Skill_7[0]/SkillName_7[1]", + "entityName" : "SkillName_7", + "entityKind" : "ParagraphNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Skills[3]/Skill_7Stack[8]/Skill_7Layer[0]/Skill_7[0]", + "childIndex" : 1, + "depth" : 8, + "layer" : 8, + "computedX" : 26.5, + "computedY" : 356.269, + "placementX" : 26.5, + "placementY" : 356.269, + "placementWidth" : 31.426, + "placementHeight" : 9.36, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 31.426, + "contentHeight" : 9.36, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Skills[3]/Skill_7Stack[8]/Skill_7Layer[0]/Skill_7[0]/SkillRating_7[2]", + "entityName" : "SkillRating_7", + "entityKind" : "ParagraphNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Skills[3]/Skill_7Stack[8]/Skill_7Layer[0]/Skill_7[0]", + "childIndex" : 2, + "depth" : 8, + "layer" : 8, + "computedX" : 132.203, + "computedY" : 356.269, + "placementX" : 132.203, + "placementY" : 356.269, + "placementWidth" : 41.107, + "placementHeight" : 9.36, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 41.107, + "contentHeight" : 9.36, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Skills[3]/Skill_8Stack[9]", + "entityName" : "Skill_8Stack", + "entityKind" : "LayerStackNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Skills[3]", + "childIndex" : 9, + "depth" : 5, + "layer" : 5, + "computedX" : 17.0, + "computedY" : 339.669, + "placementX" : 17.0, + "placementY" : 339.669, + "placementWidth" : 156.31, + "placementHeight" : 9.36, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 156.31, + "contentHeight" : 9.36, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 7.24, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Skills[3]/Skill_8Stack[9]/Skill_8Layer[0]", + "entityName" : "Skill_8Layer", + "entityKind" : "SectionNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Skills[3]/Skill_8Stack[9]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 17.0, + "computedY" : 339.669, + "placementX" : 17.0, + "placementY" : 339.669, + "placementWidth" : 156.31, + "placementHeight" : 9.36, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 156.31, + "contentHeight" : 9.36, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Skills[3]/Skill_8Stack[9]/Skill_8Layer[0]/Skill_8[0]", + "entityName" : "Skill_8", + "entityKind" : "RowNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Skills[3]/Skill_8Stack[9]/Skill_8Layer[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 17.0, + "computedY" : 339.669, + "placementX" : 17.0, + "placementY" : 339.669, + "placementWidth" : 156.31, + "placementHeight" : 9.36, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 156.31, + "contentHeight" : 9.36, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Skills[3]/Skill_8Stack[9]/Skill_8Layer[0]/Skill_8[0]/SkillBullet_8[0]", + "entityName" : "SkillBullet_8", + "entityKind" : "ParagraphNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Skills[3]/Skill_8Stack[9]/Skill_8Layer[0]/Skill_8[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 17.0, + "computedY" : 339.669, + "placementX" : 17.0, + "placementY" : 339.669, + "placementWidth" : 9.5, + "placementHeight" : 9.36, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 9.5, + "contentHeight" : 9.36, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Skills[3]/Skill_8Stack[9]/Skill_8Layer[0]/Skill_8[0]/SkillName_8[1]", + "entityName" : "SkillName_8", + "entityKind" : "ParagraphNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Skills[3]/Skill_8Stack[9]/Skill_8Layer[0]/Skill_8[0]", + "childIndex" : 1, + "depth" : 8, + "layer" : 8, + "computedX" : 26.5, + "computedY" : 339.669, + "placementX" : 26.5, + "placementY" : 339.669, + "placementWidth" : 58.64, + "placementHeight" : 9.36, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 58.64, + "contentHeight" : 9.36, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Skills[3]/Skill_8Stack[9]/Skill_8Layer[0]/Skill_8[0]/SkillRating_8[2]", + "entityName" : "SkillRating_8", + "entityKind" : "ParagraphNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Skills[3]/Skill_8Stack[9]/Skill_8Layer[0]/Skill_8[0]", + "childIndex" : 2, + "depth" : 8, + "layer" : 8, + "computedX" : 132.203, + "computedY" : 339.669, + "placementX" : 132.203, + "placementY" : 339.669, + "placementWidth" : 41.107, + "placementHeight" : 9.36, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 41.107, + "contentHeight" : 9.36, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Skills[3]/Skill_9Stack[10]", + "entityName" : "Skill_9Stack", + "entityKind" : "LayerStackNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Skills[3]", + "childIndex" : 10, + "depth" : 5, + "layer" : 5, + "computedX" : 17.0, + "computedY" : 323.069, + "placementX" : 17.0, + "placementY" : 323.069, + "placementWidth" : 156.31, + "placementHeight" : 9.36, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 156.31, + "contentHeight" : 9.36, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 7.24, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Skills[3]/Skill_9Stack[10]/Skill_9Layer[0]", + "entityName" : "Skill_9Layer", + "entityKind" : "SectionNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Skills[3]/Skill_9Stack[10]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 17.0, + "computedY" : 323.069, + "placementX" : 17.0, + "placementY" : 323.069, + "placementWidth" : 156.31, + "placementHeight" : 9.36, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 156.31, + "contentHeight" : 9.36, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Skills[3]/Skill_9Stack[10]/Skill_9Layer[0]/Skill_9[0]", + "entityName" : "Skill_9", + "entityKind" : "RowNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Skills[3]/Skill_9Stack[10]/Skill_9Layer[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 17.0, + "computedY" : 323.069, + "placementX" : 17.0, + "placementY" : 323.069, + "placementWidth" : 156.31, + "placementHeight" : 9.36, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 156.31, + "contentHeight" : 9.36, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Skills[3]/Skill_9Stack[10]/Skill_9Layer[0]/Skill_9[0]/SkillBullet_9[0]", + "entityName" : "SkillBullet_9", + "entityKind" : "ParagraphNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Skills[3]/Skill_9Stack[10]/Skill_9Layer[0]/Skill_9[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 17.0, + "computedY" : 323.069, + "placementX" : 17.0, + "placementY" : 323.069, + "placementWidth" : 9.5, + "placementHeight" : 9.36, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 9.5, + "contentHeight" : 9.36, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Skills[3]/Skill_9Stack[10]/Skill_9Layer[0]/Skill_9[0]/SkillName_9[1]", + "entityName" : "SkillName_9", + "entityKind" : "ParagraphNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Skills[3]/Skill_9Stack[10]/Skill_9Layer[0]/Skill_9[0]", + "childIndex" : 1, + "depth" : 8, + "layer" : 8, + "computedX" : 26.5, + "computedY" : 323.069, + "placementX" : 26.5, + "placementY" : 323.069, + "placementWidth" : 70.582, + "placementHeight" : 9.36, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 70.582, + "contentHeight" : 9.36, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Skills[3]/Skill_9Stack[10]/Skill_9Layer[0]/Skill_9[0]/SkillRating_9[2]", + "entityName" : "SkillRating_9", + "entityKind" : "ParagraphNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Skills[3]/Skill_9Stack[10]/Skill_9Layer[0]/Skill_9[0]", + "childIndex" : 2, + "depth" : 8, + "layer" : 8, + "computedX" : 132.203, + "computedY" : 323.069, + "placementX" : 132.203, + "placementY" : 323.069, + "placementWidth" : 41.107, + "placementHeight" : 9.36, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 41.107, + "contentHeight" : 9.36, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Divider_AfterSkills[4]", + "entityName" : "Divider_AfterSkills", + "entityKind" : "LineNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]", + "childIndex" : 4, + "depth" : 4, + "layer" : 4, + "computedX" : 17.0, + "computedY" : 295.829, + "placementX" : 17.0, + "placementY" : 295.829, + "placementWidth" : 156.31, + "placementHeight" : 1.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 156.31, + "contentHeight" : 1.0, + "margin" : { + "top" : 19.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Languages[5]", + "entityName" : "Languages", + "entityKind" : "SectionNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]", + "childIndex" : 5, + "depth" : 4, + "layer" : 4, + "computedX" : 17.0, + "computedY" : 187.229, + "placementX" : 17.0, + "placementY" : 187.229, + "placementWidth" : 156.31, + "placementHeight" : 91.6, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 156.31, + "contentHeight" : 91.6, + "margin" : { + "top" : 17.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Languages[5]/Heading_LANGUAGES[0]", + "entityName" : "Heading_LANGUAGES", + "entityKind" : "SectionNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Languages[5]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 17.0, + "computedY" : 266.829, + "placementX" : 17.0, + "placementY" : 266.829, + "placementWidth" : 68.51, + "placementHeight" : 12.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 68.51, + "contentHeight" : 12.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 9.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Languages[5]/Heading_LANGUAGES[0]/HeadingText_LANGUAGES[0]", + "entityName" : "HeadingText_LANGUAGES", + "entityKind" : "ParagraphNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Languages[5]/Heading_LANGUAGES[0]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 26.0, + "computedY" : 266.829, + "placementX" : 26.0, + "placementY" : 266.829, + "placementWidth" : 59.51, + "placementHeight" : 12.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 59.51, + "contentHeight" : 12.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Languages[5]/Language_0Stack[1]", + "entityName" : "Language_0Stack", + "entityKind" : "LayerStackNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Languages[5]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 17.0, + "computedY" : 243.909, + "placementX" : 17.0, + "placementY" : 243.909, + "placementWidth" : 156.31, + "placementHeight" : 10.92, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 156.31, + "contentHeight" : 10.92, + "margin" : { + "top" : 12.0, + "right" : 0.0, + "bottom" : 5.98, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Languages[5]/Language_0Stack[1]/Language_0Layer[0]", + "entityName" : "Language_0Layer", + "entityKind" : "SectionNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Languages[5]/Language_0Stack[1]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 17.0, + "computedY" : 243.909, + "placementX" : 17.0, + "placementY" : 243.909, + "placementWidth" : 156.31, + "placementHeight" : 10.92, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 156.31, + "contentHeight" : 10.92, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Languages[5]/Language_0Stack[1]/Language_0Layer[0]/Language_0[0]", + "entityName" : "Language_0", + "entityKind" : "RowNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Languages[5]/Language_0Stack[1]/Language_0Layer[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 17.0, + "computedY" : 243.909, + "placementX" : 17.0, + "placementY" : 243.909, + "placementWidth" : 156.31, + "placementHeight" : 10.92, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 156.31, + "contentHeight" : 10.92, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Languages[5]/Language_0Stack[1]/Language_0Layer[0]/Language_0[0]/LanguageName_0[0]", + "entityName" : "LanguageName_0", + "entityKind" : "ParagraphNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Languages[5]/Language_0Stack[1]/Language_0Layer[0]/Language_0[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 17.0, + "computedY" : 243.909, + "placementX" : 17.0, + "placementY" : 243.909, + "placementWidth" : 28.419, + "placementHeight" : 10.92, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 28.419, + "contentHeight" : 10.92, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Languages[5]/Language_0Stack[1]/Language_0Layer[0]/Language_0[0]/LanguageLevel_0[1]", + "entityName" : "LanguageLevel_0", + "entityKind" : "ParagraphNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Languages[5]/Language_0Stack[1]/Language_0Layer[0]/Language_0[0]", + "childIndex" : 1, + "depth" : 8, + "layer" : 8, + "computedX" : 73.271, + "computedY" : 243.909, + "placementX" : 73.271, + "placementY" : 243.909, + "placementWidth" : 26.426, + "placementHeight" : 10.92, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 26.426, + "contentHeight" : 10.92, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Languages[5]/Language_1Stack[2]", + "entityName" : "Language_1Stack", + "entityKind" : "LayerStackNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Languages[5]", + "childIndex" : 2, + "depth" : 5, + "layer" : 5, + "computedX" : 17.0, + "computedY" : 227.009, + "placementX" : 17.0, + "placementY" : 227.009, + "placementWidth" : 156.31, + "placementHeight" : 10.92, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 156.31, + "contentHeight" : 10.92, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 5.98, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Languages[5]/Language_1Stack[2]/Language_1Layer[0]", + "entityName" : "Language_1Layer", + "entityKind" : "SectionNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Languages[5]/Language_1Stack[2]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 17.0, + "computedY" : 227.009, + "placementX" : 17.0, + "placementY" : 227.009, + "placementWidth" : 156.31, + "placementHeight" : 10.92, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 156.31, + "contentHeight" : 10.92, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Languages[5]/Language_1Stack[2]/Language_1Layer[0]/Language_1[0]", + "entityName" : "Language_1", + "entityKind" : "RowNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Languages[5]/Language_1Stack[2]/Language_1Layer[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 17.0, + "computedY" : 227.009, + "placementX" : 17.0, + "placementY" : 227.009, + "placementWidth" : 156.31, + "placementHeight" : 10.92, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 156.31, + "contentHeight" : 10.92, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Languages[5]/Language_1Stack[2]/Language_1Layer[0]/Language_1[0]/LanguageName_1[0]", + "entityName" : "LanguageName_1", + "entityKind" : "ParagraphNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Languages[5]/Language_1Stack[2]/Language_1Layer[0]/Language_1[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 17.0, + "computedY" : 227.009, + "placementX" : 17.0, + "placementY" : 227.009, + "placementWidth" : 30.449, + "placementHeight" : 10.92, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 30.449, + "contentHeight" : 10.92, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Languages[5]/Language_1Stack[2]/Language_1Layer[0]/Language_1[0]/LanguageLevel_1[1]", + "entityName" : "LanguageLevel_1", + "entityKind" : "ParagraphNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Languages[5]/Language_1Stack[2]/Language_1Layer[0]/Language_1[0]", + "childIndex" : 1, + "depth" : 8, + "layer" : 8, + "computedX" : 73.271, + "computedY" : 227.009, + "placementX" : 73.271, + "placementY" : 227.009, + "placementWidth" : 26.426, + "placementHeight" : 10.92, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 26.426, + "contentHeight" : 10.92, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Languages[5]/Language_2Stack[3]", + "entityName" : "Language_2Stack", + "entityKind" : "LayerStackNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Languages[5]", + "childIndex" : 3, + "depth" : 5, + "layer" : 5, + "computedX" : 17.0, + "computedY" : 210.109, + "placementX" : 17.0, + "placementY" : 210.109, + "placementWidth" : 156.31, + "placementHeight" : 10.92, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 156.31, + "contentHeight" : 10.92, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 5.98, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Languages[5]/Language_2Stack[3]/Language_2Layer[0]", + "entityName" : "Language_2Layer", + "entityKind" : "SectionNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Languages[5]/Language_2Stack[3]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 17.0, + "computedY" : 210.109, + "placementX" : 17.0, + "placementY" : 210.109, + "placementWidth" : 156.31, + "placementHeight" : 10.92, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 156.31, + "contentHeight" : 10.92, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Languages[5]/Language_2Stack[3]/Language_2Layer[0]/Language_2[0]", + "entityName" : "Language_2", + "entityKind" : "RowNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Languages[5]/Language_2Stack[3]/Language_2Layer[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 17.0, + "computedY" : 210.109, + "placementX" : 17.0, + "placementY" : 210.109, + "placementWidth" : 156.31, + "placementHeight" : 10.92, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 156.31, + "contentHeight" : 10.92, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Languages[5]/Language_2Stack[3]/Language_2Layer[0]/Language_2[0]/LanguageName_2[0]", + "entityName" : "LanguageName_2", + "entityKind" : "ParagraphNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Languages[5]/Language_2Stack[3]/Language_2Layer[0]/Language_2[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 17.0, + "computedY" : 210.109, + "placementX" : 17.0, + "placementY" : 210.109, + "placementWidth" : 27.773, + "placementHeight" : 10.92, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 27.773, + "contentHeight" : 10.92, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Languages[5]/Language_2Stack[3]/Language_2Layer[0]/Language_2[0]/LanguageLevel_2[1]", + "entityName" : "LanguageLevel_2", + "entityKind" : "ParagraphNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Languages[5]/Language_2Stack[3]/Language_2Layer[0]/Language_2[0]", + "childIndex" : 1, + "depth" : 8, + "layer" : 8, + "computedX" : 73.271, + "computedY" : 210.109, + "placementX" : 73.271, + "placementY" : 210.109, + "placementWidth" : 99.636, + "placementHeight" : 10.92, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 99.636, + "contentHeight" : 10.92, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Languages[5]/Language_3Stack[4]", + "entityName" : "Language_3Stack", + "entityKind" : "LayerStackNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Languages[5]", + "childIndex" : 4, + "depth" : 5, + "layer" : 5, + "computedX" : 17.0, + "computedY" : 193.209, + "placementX" : 17.0, + "placementY" : 193.209, + "placementWidth" : 156.31, + "placementHeight" : 10.92, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 156.31, + "contentHeight" : 10.92, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 5.98, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Languages[5]/Language_3Stack[4]/Language_3Layer[0]", + "entityName" : "Language_3Layer", + "entityKind" : "SectionNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Languages[5]/Language_3Stack[4]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 17.0, + "computedY" : 193.209, + "placementX" : 17.0, + "placementY" : 193.209, + "placementWidth" : 156.31, + "placementHeight" : 10.92, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 156.31, + "contentHeight" : 10.92, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Languages[5]/Language_3Stack[4]/Language_3Layer[0]/Language_3[0]", + "entityName" : "Language_3", + "entityKind" : "RowNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Languages[5]/Language_3Stack[4]/Language_3Layer[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 17.0, + "computedY" : 193.209, + "placementX" : 17.0, + "placementY" : 193.209, + "placementWidth" : 156.31, + "placementHeight" : 10.92, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 156.31, + "contentHeight" : 10.92, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Languages[5]/Language_3Stack[4]/Language_3Layer[0]/Language_3[0]/LanguageName_3[0]", + "entityName" : "LanguageName_3", + "entityKind" : "ParagraphNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Languages[5]/Language_3Stack[4]/Language_3Layer[0]/Language_3[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 17.0, + "computedY" : 193.209, + "placementX" : 17.0, + "placementY" : 193.209, + "placementWidth" : 31.859, + "placementHeight" : 10.92, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 31.859, + "contentHeight" : 10.92, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Languages[5]/Language_3Stack[4]/Language_3Layer[0]/Language_3[0]/LanguageLevel_3[1]", + "entityName" : "LanguageLevel_3", + "entityKind" : "ParagraphNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Languages[5]/Language_3Stack[4]/Language_3Layer[0]/Language_3[0]", + "childIndex" : 1, + "depth" : 8, + "layer" : 8, + "computedX" : 73.271, + "computedY" : 193.209, + "placementX" : 73.271, + "placementY" : 193.209, + "placementWidth" : 42.26, + "placementHeight" : 10.92, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 42.26, + "contentHeight" : 10.92, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Divider_AfterLanguages[6]", + "entityName" : "Divider_AfterLanguages", + "entityKind" : "LineNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]", + "childIndex" : 6, + "depth" : 4, + "layer" : 4, + "computedX" : 17.0, + "computedY" : 167.229, + "placementX" : 17.0, + "placementY" : 167.229, + "placementWidth" : 156.31, + "placementHeight" : 1.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 156.31, + "contentHeight" : 1.0, + "margin" : { + "top" : 19.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Education[7]", + "entityName" : "Education", + "entityKind" : "SectionNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]", + "childIndex" : 7, + "depth" : 4, + "layer" : 4, + "computedX" : 17.0, + "computedY" : 44.209, + "placementX" : 17.0, + "placementY" : 44.209, + "placementWidth" : 107.569, + "placementHeight" : 106.02, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 107.569, + "contentHeight" : 106.02, + "margin" : { + "top" : 17.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Education[7]/Heading_EDUCATION[0]", + "entityName" : "Heading_EDUCATION", + "entityKind" : "SectionNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Education[7]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 17.0, + "computedY" : 138.229, + "placementX" : 17.0, + "placementY" : 138.229, + "placementWidth" : 67.56, + "placementHeight" : 12.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 67.56, + "contentHeight" : 12.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 9.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Education[7]/Heading_EDUCATION[0]/HeadingText_EDUCATION[0]", + "entityName" : "HeadingText_EDUCATION", + "entityKind" : "ParagraphNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Education[7]/Heading_EDUCATION[0]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 26.0, + "computedY" : 138.229, + "placementX" : 26.0, + "placementY" : 138.229, + "placementWidth" : 58.56, + "placementHeight" : 12.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 58.56, + "contentHeight" : 12.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Education[7]/EducationEntry_0[1]", + "entityName" : "EducationEntry_0", + "entityKind" : "SectionNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Education[7]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 17.0, + "computedY" : 92.069, + "placementX" : 17.0, + "placementY" : 92.069, + "placementWidth" : 102.95, + "placementHeight" : 34.16, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 102.95, + "contentHeight" : 34.16, + "margin" : { + "top" : 12.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Education[7]/EducationEntry_0[1]/Degree_0[0]", + "entityName" : "Degree_0", + "entityKind" : "ParagraphNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Education[7]/EducationEntry_0[1]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 17.0, + "computedY" : 116.869, + "placementX" : 17.0, + "placementY" : 116.869, + "placementWidth" : 98.708, + "placementHeight" : 9.36, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 98.708, + "contentHeight" : 9.36, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 3.04, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Education[7]/EducationEntry_0[1]/EducationDetail_0[1]", + "entityName" : "EducationDetail_0", + "entityKind" : "SectionNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Education[7]/EducationEntry_0[1]", + "childIndex" : 1, + "depth" : 6, + "layer" : 6, + "computedX" : 17.0, + "computedY" : 92.069, + "placementX" : 17.0, + "placementY" : 92.069, + "placementWidth" : 102.95, + "placementHeight" : 21.76, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 102.95, + "contentHeight" : 21.76, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 12.4 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Education[7]/EducationEntry_0[1]/EducationDetail_0[1]/Institution_0[0]", + "entityName" : "Institution_0", + "entityKind" : "ParagraphNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Education[7]/EducationEntry_0[1]/EducationDetail_0[1]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 29.4, + "computedY" : 104.469, + "placementX" : 29.4, + "placementY" : 104.469, + "placementWidth" : 90.55, + "placementHeight" : 9.36, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 90.55, + "contentHeight" : 9.36, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 3.04, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Education[7]/EducationEntry_0[1]/EducationDetail_0[1]/Period_0[1]", + "entityName" : "Period_0", + "entityKind" : "ParagraphNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Education[7]/EducationEntry_0[1]/EducationDetail_0[1]", + "childIndex" : 1, + "depth" : 7, + "layer" : 7, + "computedX" : 29.4, + "computedY" : 92.069, + "placementX" : 29.4, + "placementY" : 92.069, + "placementWidth" : 44.71, + "placementHeight" : 9.36, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 44.71, + "contentHeight" : 9.36, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Education[7]/EducationEntry_1[2]", + "entityName" : "EducationEntry_1", + "entityKind" : "SectionNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Education[7]", + "childIndex" : 2, + "depth" : 5, + "layer" : 5, + "computedX" : 17.0, + "computedY" : 44.209, + "placementX" : 17.0, + "placementY" : 44.209, + "placementWidth" : 107.569, + "placementHeight" : 34.16, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 107.569, + "contentHeight" : 34.16, + "margin" : { + "top" : 13.7, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Education[7]/EducationEntry_1[2]/Degree_1[0]", + "entityName" : "Degree_1", + "entityKind" : "ParagraphNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Education[7]/EducationEntry_1[2]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 17.0, + "computedY" : 69.009, + "placementX" : 17.0, + "placementY" : 69.009, + "placementWidth" : 107.569, + "placementHeight" : 9.36, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 107.569, + "contentHeight" : 9.36, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 3.04, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Education[7]/EducationEntry_1[2]/EducationDetail_1[1]", + "entityName" : "EducationDetail_1", + "entityKind" : "SectionNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Education[7]/EducationEntry_1[2]", + "childIndex" : 1, + "depth" : 6, + "layer" : 6, + "computedX" : 17.0, + "computedY" : 44.209, + "placementX" : 17.0, + "placementY" : 44.209, + "placementWidth" : 88.349, + "placementHeight" : 21.76, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 88.349, + "contentHeight" : 21.76, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 12.4 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Education[7]/EducationEntry_1[2]/EducationDetail_1[1]/Institution_1[0]", + "entityName" : "Institution_1", + "entityKind" : "ParagraphNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Education[7]/EducationEntry_1[2]/EducationDetail_1[1]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 29.4, + "computedY" : 56.609, + "placementX" : 29.4, + "placementY" : 56.609, + "placementWidth" : 75.949, + "placementHeight" : 9.36, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 75.949, + "contentHeight" : 9.36, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 3.04, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Education[7]/EducationEntry_1[2]/EducationDetail_1[1]/Period_1[1]", + "entityName" : "Period_1", + "entityKind" : "ParagraphNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/Sidebar[0]/Education[7]/EducationEntry_1[2]/EducationDetail_1[1]", + "childIndex" : 1, + "depth" : 7, + "layer" : 7, + "computedX" : 29.4, + "computedY" : 44.209, + "placementX" : 29.4, + "placementY" : 44.209, + "placementWidth" : 44.71, + "placementHeight" : 9.36, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 44.71, + "contentHeight" : 9.36, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]", + "entityName" : "MainColumn", + "entityKind" : "SectionNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]", + "childIndex" : 1, + "depth" : 3, + "layer" : 3, + "computedX" : 190.31, + "computedY" : 29.49, + "placementX" : 190.31, + "placementY" : 29.49, + "placementWidth" : 404.966, + "placementHeight" : 812.4, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 404.966, + "contentHeight" : 812.4, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 25.7, + "right" : 28.8, + "bottom" : 0.0, + "left" : 23.7 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Masthead[0]", + "entityName" : "Masthead", + "entityKind" : "SectionNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 214.01, + "computedY" : 691.43, + "placementX" : 214.01, + "placementY" : 691.43, + "placementWidth" : 193.815, + "placementHeight" : 124.76, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 193.815, + "contentHeight" : 124.76, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Masthead[0]/GivenName[0]", + "entityName" : "GivenName", + "entityKind" : "ParagraphNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Masthead[0]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 214.01, + "computedY" : 773.35, + "placementX" : 214.01, + "placementY" : 773.35, + "placementWidth" : 193.815, + "placementHeight" : 42.84, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 193.815, + "contentHeight" : 42.84, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Masthead[0]/FamilyName[1]", + "entityName" : "FamilyName", + "entityKind" : "ParagraphNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Masthead[0]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 214.01, + "computedY" : 718.75, + "placementX" : 214.01, + "placementY" : 718.75, + "placementWidth" : 141.369, + "placementHeight" : 54.6, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 141.369, + "contentHeight" : 54.6, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Masthead[0]/JobTitle[2]", + "entityName" : "JobTitle", + "entityKind" : "ParagraphNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Masthead[0]", + "childIndex" : 2, + "depth" : 5, + "layer" : 5, + "computedX" : 214.01, + "computedY" : 704.83, + "placementX" : 214.01, + "placementY" : 704.83, + "placementWidth" : 152.099, + "placementHeight" : 13.92, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 152.099, + "contentHeight" : 13.92, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 12.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Masthead[0]/MastheadRule[3]", + "entityName" : "MastheadRule", + "entityKind" : "LineNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Masthead[0]", + "childIndex" : 3, + "depth" : 5, + "layer" : 5, + "computedX" : 214.01, + "computedY" : 691.43, + "placementX" : 214.01, + "placementY" : 691.43, + "placementWidth" : 32.779, + "placementHeight" : 1.4, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 32.779, + "contentHeight" : 1.4, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Summary[1]", + "entityName" : "Summary", + "entityKind" : "SectionNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 214.01, + "computedY" : 627.19, + "placementX" : 214.01, + "placementY" : 627.19, + "placementWidth" : 336.736, + "placementHeight" : 46.44, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 336.736, + "contentHeight" : 46.44, + "margin" : { + "top" : 17.8, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Summary[1]/Summary_0[0]", + "entityName" : "Summary_0", + "entityKind" : "ParagraphNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Summary[1]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 214.01, + "computedY" : 650.41, + "placementX" : 214.01, + "placementY" : 650.41, + "placementWidth" : 336.736, + "placementHeight" : 23.22, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 336.736, + "contentHeight" : 23.22, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Summary[1]/Summary_1[1]", + "entityName" : "Summary_1", + "entityKind" : "ParagraphNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Summary[1]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 214.01, + "computedY" : 627.19, + "placementX" : 214.01, + "placementY" : 627.19, + "placementWidth" : 327.09, + "placementHeight" : 23.22, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 327.09, + "contentHeight" : 23.22, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Experience[2]", + "entityName" : "Experience", + "entityKind" : "SectionNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]", + "childIndex" : 2, + "depth" : 4, + "layer" : 4, + "computedX" : 214.01, + "computedY" : 274.21, + "placementX" : 214.01, + "placementY" : 274.21, + "placementWidth" : 352.466, + "placementHeight" : 324.58, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 352.466, + "contentHeight" : 324.58, + "margin" : { + "top" : 28.4, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Experience[2]/Heading_EXPERIENCE[0]", + "entityName" : "Heading_EXPERIENCE", + "entityKind" : "SectionNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Experience[2]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 214.01, + "computedY" : 585.59, + "placementX" : 214.01, + "placementY" : 585.59, + "placementWidth" : 352.466, + "placementHeight" : 13.2, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 352.466, + "contentHeight" : 13.2, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 9.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Experience[2]/Heading_EXPERIENCE[0]/HeadingRow_EXPERIENCEStack[0]", + "entityName" : "HeadingRow_EXPERIENCEStack", + "entityKind" : "LayerStackNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Experience[2]/Heading_EXPERIENCE[0]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 223.01, + "computedY" : 585.59, + "placementX" : 223.01, + "placementY" : 585.59, + "placementWidth" : 343.466, + "placementHeight" : 13.2, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 343.466, + "contentHeight" : 13.2, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Experience[2]/Heading_EXPERIENCE[0]/HeadingRow_EXPERIENCEStack[0]/HeadingRow_EXPERIENCELayer[0]", + "entityName" : "HeadingRow_EXPERIENCELayer", + "entityKind" : "SectionNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Experience[2]/Heading_EXPERIENCE[0]/HeadingRow_EXPERIENCEStack[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 223.01, + "computedY" : 585.59, + "placementX" : 223.01, + "placementY" : 585.59, + "placementWidth" : 343.466, + "placementHeight" : 13.2, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 343.466, + "contentHeight" : 13.2, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Experience[2]/Heading_EXPERIENCE[0]/HeadingRow_EXPERIENCEStack[0]/HeadingRow_EXPERIENCELayer[0]/HeadingRow_EXPERIENCE[0]", + "entityName" : "HeadingRow_EXPERIENCE", + "entityKind" : "RowNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Experience[2]/Heading_EXPERIENCE[0]/HeadingRow_EXPERIENCEStack[0]/HeadingRow_EXPERIENCELayer[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 223.01, + "computedY" : 585.59, + "placementX" : 223.01, + "placementY" : 585.59, + "placementWidth" : 343.466, + "placementHeight" : 13.2, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 343.466, + "contentHeight" : 13.2, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Experience[2]/Heading_EXPERIENCE[0]/HeadingRow_EXPERIENCEStack[0]/HeadingRow_EXPERIENCELayer[0]/HeadingRow_EXPERIENCE[0]/HeadingText_EXPERIENCE[0]", + "entityName" : "HeadingText_EXPERIENCE", + "entityKind" : "ParagraphNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Experience[2]/Heading_EXPERIENCE[0]/HeadingRow_EXPERIENCEStack[0]/HeadingRow_EXPERIENCELayer[0]/HeadingRow_EXPERIENCE[0]", + "childIndex" : 0, + "depth" : 9, + "layer" : 9, + "computedX" : 223.01, + "computedY" : 585.59, + "placementX" : 223.01, + "placementY" : 585.59, + "placementWidth" : 64.911, + "placementHeight" : 13.2, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 64.911, + "contentHeight" : 13.2, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Experience[2]/Heading_EXPERIENCE[0]/HeadingRow_EXPERIENCEStack[0]/HeadingRow_EXPERIENCELayer[0]/HeadingRow_EXPERIENCE[0]/HeadingRule_EXPERIENCE[1]", + "entityName" : "HeadingRule_EXPERIENCE", + "entityKind" : "LineNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Experience[2]/Heading_EXPERIENCE[0]/HeadingRow_EXPERIENCEStack[0]/HeadingRow_EXPERIENCELayer[0]/HeadingRow_EXPERIENCE[0]", + "childIndex" : 1, + "depth" : 9, + "layer" : 9, + "computedX" : 296.921, + "computedY" : 591.69, + "placementX" : 296.921, + "placementY" : 591.69, + "placementWidth" : 269.555, + "placementHeight" : 1.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 269.555, + "contentHeight" : 1.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 9.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Experience[2]/ExperienceRail[1]", + "entityName" : "ExperienceRail", + "entityKind" : "LayerStackNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Experience[2]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 214.01, + "computedY" : 274.21, + "placementX" : 214.01, + "placementY" : 274.21, + "placementWidth" : 352.466, + "placementHeight" : 311.38, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 352.466, + "contentHeight" : 311.38, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Experience[2]/ExperienceRail[1]/ExperienceRailHolder[0]", + "entityName" : "ExperienceRailHolder", + "entityKind" : "SectionNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Experience[2]/ExperienceRail[1]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 214.01, + "computedY" : 274.21, + "placementX" : 214.01, + "placementY" : 274.21, + "placementWidth" : 352.466, + "placementHeight" : 292.38, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 352.466, + "contentHeight" : 292.38, + "margin" : { + "top" : 19.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Experience[2]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]", + "entityName" : null, + "entityKind" : "SectionNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Experience[2]/ExperienceRail[1]/ExperienceRailHolder[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 214.01, + "computedY" : 274.21, + "placementX" : 214.01, + "placementY" : 274.21, + "placementWidth" : 352.466, + "placementHeight" : 292.38, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 352.466, + "contentHeight" : 292.38, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Experience[2]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]", + "entityName" : null, + "entityKind" : "LayoutAnchorNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Experience[2]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 214.01, + "computedY" : 455.41, + "placementX" : 214.01, + "placementY" : 455.41, + "placementWidth" : 352.466, + "placementHeight" : 111.18, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 352.466, + "contentHeight" : 111.18, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Experience[2]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]", + "entityName" : null, + "entityKind" : "SectionNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Experience[2]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]", + "childIndex" : 0, + "depth" : 9, + "layer" : 9, + "computedX" : 214.01, + "computedY" : 455.41, + "placementX" : 214.01, + "placementY" : 455.41, + "placementWidth" : 352.466, + "placementHeight" : 111.18, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 352.466, + "contentHeight" : 111.18, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 19.6, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Experience[2]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]", + "entityName" : null, + "entityKind" : "HorizontalBandsNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Experience[2]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]", + "childIndex" : 0, + "depth" : 10, + "layer" : 10, + "computedX" : 214.01, + "computedY" : 475.01, + "placementX" : 214.01, + "placementY" : 475.01, + "placementWidth" : 352.466, + "placementHeight" : 91.58, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 352.466, + "contentHeight" : 91.58, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Experience[2]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]", + "entityName" : null, + "entityKind" : "RowNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Experience[2]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]", + "childIndex" : 0, + "depth" : 11, + "layer" : 11, + "computedX" : 214.01, + "computedY" : 475.01, + "placementX" : 214.01, + "placementY" : 475.01, + "placementWidth" : 352.466, + "placementHeight" : 91.58, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 352.466, + "contentHeight" : 91.58, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Experience[2]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]", + "entityName" : null, + "entityKind" : "SectionNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Experience[2]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]", + "childIndex" : 0, + "depth" : 12, + "layer" : 12, + "computedX" : 214.01, + "computedY" : 557.23, + "placementX" : 214.01, + "placementY" : 557.23, + "placementWidth" : 52.907, + "placementHeight" : 9.36, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 52.907, + "contentHeight" : 9.36, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Experience[2]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Period_0[0]", + "entityName" : "Period_0", + "entityKind" : "ParagraphNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Experience[2]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]", + "childIndex" : 0, + "depth" : 13, + "layer" : 13, + "computedX" : 214.01, + "computedY" : 557.23, + "placementX" : 214.01, + "placementY" : 557.23, + "placementWidth" : 52.907, + "placementHeight" : 9.36, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 52.907, + "contentHeight" : 9.36, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Experience[2]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]", + "entityName" : null, + "entityKind" : "SectionNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Experience[2]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]", + "childIndex" : 1, + "depth" : 12, + "layer" : 12, + "computedX" : 272.588, + "computedY" : 566.59, + "placementX" : 272.588, + "placementY" : 566.59, + "placementWidth" : 0.0, + "placementHeight" : 0.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 0.0, + "contentHeight" : 0.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Experience[2]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[2]", + "entityName" : null, + "entityKind" : "SectionNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Experience[2]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]", + "childIndex" : 2, + "depth" : 12, + "layer" : 12, + "computedX" : 285.488, + "computedY" : 560.39, + "placementX" : 285.488, + "placementY" : 560.39, + "placementWidth" : 6.2, + "placementHeight" : 6.2, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 6.2, + "contentHeight" : 6.2, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Experience[2]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[2]/Align[0]", + "entityName" : null, + "entityKind" : "Align", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Experience[2]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[2]", + "childIndex" : 0, + "depth" : 13, + "layer" : 13, + "computedX" : 285.488, + "computedY" : 560.39, + "placementX" : 285.488, + "placementY" : 560.39, + "placementWidth" : 6.2, + "placementHeight" : 6.2, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 6.2, + "contentHeight" : 6.2, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Experience[2]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[2]/Align[0]/LayoutAnchorNode[0]", + "entityName" : null, + "entityKind" : "LayoutAnchorNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Experience[2]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[2]/Align[0]", + "childIndex" : 0, + "depth" : 14, + "layer" : 14, + "computedX" : 285.488, + "computedY" : 560.39, + "placementX" : 285.488, + "placementY" : 560.39, + "placementWidth" : 6.2, + "placementHeight" : 6.2, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 6.2, + "contentHeight" : 6.2, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Experience[2]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[2]/Align[0]/LayoutAnchorNode[0]/marker[0]", + "entityName" : "marker", + "entityKind" : "CanvasLayerNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Experience[2]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[2]/Align[0]/LayoutAnchorNode[0]", + "childIndex" : 0, + "depth" : 15, + "layer" : 15, + "computedX" : 285.488, + "computedY" : 560.39, + "placementX" : 285.488, + "placementY" : 560.39, + "placementWidth" : 6.2, + "placementHeight" : 6.2, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 6.2, + "contentHeight" : 6.2, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Experience[2]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[2]/Align[0]/LayoutAnchorNode[0]/marker[0]/SectionNode[0]", + "entityName" : null, + "entityKind" : "SectionNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Experience[2]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[2]/Align[0]/LayoutAnchorNode[0]/marker[0]", + "childIndex" : 0, + "depth" : 16, + "layer" : 16, + "computedX" : 285.488, + "computedY" : 560.39, + "placementX" : 285.488, + "placementY" : 560.39, + "placementWidth" : 6.2, + "placementHeight" : 6.2, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 6.2, + "contentHeight" : 6.2, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Experience[2]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[2]/Align[0]/LayoutAnchorNode[0]/marker[0]/SectionNode[0]/MarkerRing_0[0]", + "entityName" : "MarkerRing_0", + "entityKind" : "EllipseNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Experience[2]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[2]/Align[0]/LayoutAnchorNode[0]/marker[0]/SectionNode[0]", + "childIndex" : 0, + "depth" : 17, + "layer" : 17, + "computedX" : 285.488, + "computedY" : 560.39, + "placementX" : 285.488, + "placementY" : 560.39, + "placementWidth" : 6.2, + "placementHeight" : 6.2, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 6.2, + "contentHeight" : 6.2, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Experience[2]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[3]", + "entityName" : null, + "entityKind" : "SectionNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Experience[2]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]", + "childIndex" : 3, + "depth" : 12, + "layer" : 12, + "computedX" : 291.688, + "computedY" : 566.59, + "placementX" : 291.688, + "placementY" : 566.59, + "placementWidth" : 0.0, + "placementHeight" : 0.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 0.0, + "contentHeight" : 0.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Experience[2]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[4]", + "entityName" : null, + "entityKind" : "SectionNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Experience[2]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]", + "childIndex" : 4, + "depth" : 12, + "layer" : 12, + "computedX" : 304.588, + "computedY" : 475.01, + "placementX" : 304.588, + "placementY" : 475.01, + "placementWidth" : 236.368, + "placementHeight" : 91.58, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 236.368, + "contentHeight" : 91.58, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Experience[2]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[4]/Role_0[0]", + "entityName" : "Role_0", + "entityKind" : "ParagraphNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Experience[2]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[4]", + "childIndex" : 0, + "depth" : 13, + "layer" : 13, + "computedX" : 304.588, + "computedY" : 553.99, + "placementX" : 304.588, + "placementY" : 553.99, + "placementWidth" : 110.292, + "placementHeight" : 12.6, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 110.292, + "contentHeight" : 12.6, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 3.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Experience[2]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[4]/Employer_0[1]", + "entityName" : "Employer_0", + "entityKind" : "ParagraphNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Experience[2]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[4]", + "childIndex" : 1, + "depth" : 13, + "layer" : 13, + "computedX" : 304.588, + "computedY" : 542.71, + "placementX" : 304.588, + "placementY" : 542.71, + "placementWidth" : 137.903, + "placementHeight" : 8.28, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 137.903, + "contentHeight" : 8.28, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 7.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Experience[2]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[4]/Highlights_0[2]", + "entityName" : "Highlights_0", + "entityKind" : "ListNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Experience[2]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[4]", + "childIndex" : 2, + "depth" : 13, + "layer" : 13, + "computedX" : 304.588, + "computedY" : 475.01, + "placementX" : 304.588, + "placementY" : 475.01, + "placementWidth" : 236.368, + "placementHeight" : 60.7, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 236.368, + "contentHeight" : 60.7, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Experience[2]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]", + "entityName" : null, + "entityKind" : "LayoutAnchorNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Experience[2]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]", + "childIndex" : 1, + "depth" : 8, + "layer" : 8, + "computedX" : 214.01, + "computedY" : 355.01, + "placementX" : 214.01, + "placementY" : 355.01, + "placementWidth" : 352.466, + "placementHeight" : 100.4, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 352.466, + "contentHeight" : 100.4, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Experience[2]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]", + "entityName" : null, + "entityKind" : "SectionNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Experience[2]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]", + "childIndex" : 0, + "depth" : 9, + "layer" : 9, + "computedX" : 214.01, + "computedY" : 355.01, + "placementX" : 214.01, + "placementY" : 355.01, + "placementWidth" : 352.466, + "placementHeight" : 100.4, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 352.466, + "contentHeight" : 100.4, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 19.6, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Experience[2]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]", + "entityName" : null, + "entityKind" : "HorizontalBandsNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Experience[2]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]", + "childIndex" : 0, + "depth" : 10, + "layer" : 10, + "computedX" : 214.01, + "computedY" : 374.61, + "placementX" : 214.01, + "placementY" : 374.61, + "placementWidth" : 352.466, + "placementHeight" : 80.8, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 352.466, + "contentHeight" : 80.8, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Experience[2]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]", + "entityName" : null, + "entityKind" : "RowNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Experience[2]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]", + "childIndex" : 0, + "depth" : 11, + "layer" : 11, + "computedX" : 214.01, + "computedY" : 374.61, + "placementX" : 214.01, + "placementY" : 374.61, + "placementWidth" : 352.466, + "placementHeight" : 80.8, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 352.466, + "contentHeight" : 80.8, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Experience[2]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]", + "entityName" : null, + "entityKind" : "SectionNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Experience[2]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]", + "childIndex" : 0, + "depth" : 12, + "layer" : 12, + "computedX" : 214.01, + "computedY" : 446.05, + "placementX" : 214.01, + "placementY" : 446.05, + "placementWidth" : 44.71, + "placementHeight" : 9.36, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 44.71, + "contentHeight" : 9.36, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Experience[2]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Period_1[0]", + "entityName" : "Period_1", + "entityKind" : "ParagraphNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Experience[2]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]", + "childIndex" : 0, + "depth" : 13, + "layer" : 13, + "computedX" : 214.01, + "computedY" : 446.05, + "placementX" : 214.01, + "placementY" : 446.05, + "placementWidth" : 44.71, + "placementHeight" : 9.36, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 44.71, + "contentHeight" : 9.36, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Experience[2]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]", + "entityName" : null, + "entityKind" : "SectionNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Experience[2]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]", + "childIndex" : 1, + "depth" : 12, + "layer" : 12, + "computedX" : 272.588, + "computedY" : 455.41, + "placementX" : 272.588, + "placementY" : 455.41, + "placementWidth" : 0.0, + "placementHeight" : 0.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 0.0, + "contentHeight" : 0.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Experience[2]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[2]", + "entityName" : null, + "entityKind" : "SectionNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Experience[2]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]", + "childIndex" : 2, + "depth" : 12, + "layer" : 12, + "computedX" : 285.488, + "computedY" : 449.21, + "placementX" : 285.488, + "placementY" : 449.21, + "placementWidth" : 6.2, + "placementHeight" : 6.2, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 6.2, + "contentHeight" : 6.2, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Experience[2]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[2]/Align[0]", + "entityName" : null, + "entityKind" : "Align", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Experience[2]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[2]", + "childIndex" : 0, + "depth" : 13, + "layer" : 13, + "computedX" : 285.488, + "computedY" : 449.21, + "placementX" : 285.488, + "placementY" : 449.21, + "placementWidth" : 6.2, + "placementHeight" : 6.2, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 6.2, + "contentHeight" : 6.2, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Experience[2]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[2]/Align[0]/LayoutAnchorNode[0]", + "entityName" : null, + "entityKind" : "LayoutAnchorNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Experience[2]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[2]/Align[0]", + "childIndex" : 0, + "depth" : 14, + "layer" : 14, + "computedX" : 285.488, + "computedY" : 449.21, + "placementX" : 285.488, + "placementY" : 449.21, + "placementWidth" : 6.2, + "placementHeight" : 6.2, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 6.2, + "contentHeight" : 6.2, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Experience[2]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[2]/Align[0]/LayoutAnchorNode[0]/marker[0]", + "entityName" : "marker", + "entityKind" : "CanvasLayerNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Experience[2]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[2]/Align[0]/LayoutAnchorNode[0]", + "childIndex" : 0, + "depth" : 15, + "layer" : 15, + "computedX" : 285.488, + "computedY" : 449.21, + "placementX" : 285.488, + "placementY" : 449.21, + "placementWidth" : 6.2, + "placementHeight" : 6.2, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 6.2, + "contentHeight" : 6.2, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Experience[2]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[2]/Align[0]/LayoutAnchorNode[0]/marker[0]/SectionNode[0]", + "entityName" : null, + "entityKind" : "SectionNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Experience[2]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[2]/Align[0]/LayoutAnchorNode[0]/marker[0]", + "childIndex" : 0, + "depth" : 16, + "layer" : 16, + "computedX" : 285.488, + "computedY" : 449.21, + "placementX" : 285.488, + "placementY" : 449.21, + "placementWidth" : 6.2, + "placementHeight" : 6.2, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 6.2, + "contentHeight" : 6.2, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Experience[2]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[2]/Align[0]/LayoutAnchorNode[0]/marker[0]/SectionNode[0]/MarkerRing_1[0]", + "entityName" : "MarkerRing_1", + "entityKind" : "EllipseNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Experience[2]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[2]/Align[0]/LayoutAnchorNode[0]/marker[0]/SectionNode[0]", + "childIndex" : 0, + "depth" : 17, + "layer" : 17, + "computedX" : 285.488, + "computedY" : 449.21, + "placementX" : 285.488, + "placementY" : 449.21, + "placementWidth" : 6.2, + "placementHeight" : 6.2, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 6.2, + "contentHeight" : 6.2, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Experience[2]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[3]", + "entityName" : null, + "entityKind" : "SectionNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Experience[2]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]", + "childIndex" : 3, + "depth" : 12, + "layer" : 12, + "computedX" : 291.688, + "computedY" : 455.41, + "placementX" : 291.688, + "placementY" : 455.41, + "placementWidth" : 0.0, + "placementHeight" : 0.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 0.0, + "contentHeight" : 0.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Experience[2]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[4]", + "entityName" : null, + "entityKind" : "SectionNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Experience[2]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]", + "childIndex" : 4, + "depth" : 12, + "layer" : 12, + "computedX" : 304.588, + "computedY" : 374.61, + "placementX" : 304.588, + "placementY" : 374.61, + "placementWidth" : 220.671, + "placementHeight" : 80.8, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 220.671, + "contentHeight" : 80.8, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Experience[2]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[4]/Role_1[0]", + "entityName" : "Role_1", + "entityKind" : "ParagraphNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Experience[2]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[4]", + "childIndex" : 0, + "depth" : 13, + "layer" : 13, + "computedX" : 304.588, + "computedY" : 442.81, + "placementX" : 304.588, + "placementY" : 442.81, + "placementWidth" : 77.826, + "placementHeight" : 12.6, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 77.826, + "contentHeight" : 12.6, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 3.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Experience[2]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[4]/Employer_1[1]", + "entityName" : "Employer_1", + "entityKind" : "ParagraphNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Experience[2]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[4]", + "childIndex" : 1, + "depth" : 13, + "layer" : 13, + "computedX" : 304.588, + "computedY" : 431.53, + "placementX" : 304.588, + "placementY" : 431.53, + "placementWidth" : 133.729, + "placementHeight" : 8.28, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 133.729, + "contentHeight" : 8.28, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 7.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Experience[2]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[4]/Highlights_1[2]", + "entityName" : "Highlights_1", + "entityKind" : "ListNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Experience[2]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[4]", + "childIndex" : 2, + "depth" : 13, + "layer" : 13, + "computedX" : 304.588, + "computedY" : 374.61, + "placementX" : 304.588, + "placementY" : 374.61, + "placementWidth" : 220.671, + "placementHeight" : 49.92, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 220.671, + "contentHeight" : 49.92, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Experience[2]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]", + "entityName" : null, + "entityKind" : "LayoutAnchorNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Experience[2]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]", + "childIndex" : 2, + "depth" : 8, + "layer" : 8, + "computedX" : 214.01, + "computedY" : 274.21, + "placementX" : 214.01, + "placementY" : 274.21, + "placementWidth" : 352.466, + "placementHeight" : 80.8, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 352.466, + "contentHeight" : 80.8, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Experience[2]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]", + "entityName" : null, + "entityKind" : "SectionNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Experience[2]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]", + "childIndex" : 0, + "depth" : 9, + "layer" : 9, + "computedX" : 214.01, + "computedY" : 274.21, + "placementX" : 214.01, + "placementY" : 274.21, + "placementWidth" : 352.466, + "placementHeight" : 80.8, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 352.466, + "contentHeight" : 80.8, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Experience[2]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]", + "entityName" : null, + "entityKind" : "HorizontalBandsNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Experience[2]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]", + "childIndex" : 0, + "depth" : 10, + "layer" : 10, + "computedX" : 214.01, + "computedY" : 274.21, + "placementX" : 214.01, + "placementY" : 274.21, + "placementWidth" : 352.466, + "placementHeight" : 80.8, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 352.466, + "contentHeight" : 80.8, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Experience[2]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]", + "entityName" : null, + "entityKind" : "RowNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Experience[2]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]", + "childIndex" : 0, + "depth" : 11, + "layer" : 11, + "computedX" : 214.01, + "computedY" : 274.21, + "placementX" : 214.01, + "placementY" : 274.21, + "placementWidth" : 352.466, + "placementHeight" : 80.8, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 352.466, + "contentHeight" : 80.8, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Experience[2]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]", + "entityName" : null, + "entityKind" : "SectionNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Experience[2]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]", + "childIndex" : 0, + "depth" : 12, + "layer" : 12, + "computedX" : 214.01, + "computedY" : 345.65, + "placementX" : 214.01, + "placementY" : 345.65, + "placementWidth" : 44.71, + "placementHeight" : 9.36, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 44.71, + "contentHeight" : 9.36, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Experience[2]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Period_2[0]", + "entityName" : "Period_2", + "entityKind" : "ParagraphNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Experience[2]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]", + "childIndex" : 0, + "depth" : 13, + "layer" : 13, + "computedX" : 214.01, + "computedY" : 345.65, + "placementX" : 214.01, + "placementY" : 345.65, + "placementWidth" : 44.71, + "placementHeight" : 9.36, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 44.71, + "contentHeight" : 9.36, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Experience[2]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]", + "entityName" : null, + "entityKind" : "SectionNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Experience[2]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]", + "childIndex" : 1, + "depth" : 12, + "layer" : 12, + "computedX" : 272.588, + "computedY" : 355.01, + "placementX" : 272.588, + "placementY" : 355.01, + "placementWidth" : 0.0, + "placementHeight" : 0.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 0.0, + "contentHeight" : 0.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Experience[2]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[2]", + "entityName" : null, + "entityKind" : "SectionNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Experience[2]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]", + "childIndex" : 2, + "depth" : 12, + "layer" : 12, + "computedX" : 285.488, + "computedY" : 348.81, + "placementX" : 285.488, + "placementY" : 348.81, + "placementWidth" : 6.2, + "placementHeight" : 6.2, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 6.2, + "contentHeight" : 6.2, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Experience[2]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[2]/Align[0]", + "entityName" : null, + "entityKind" : "Align", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Experience[2]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[2]", + "childIndex" : 0, + "depth" : 13, + "layer" : 13, + "computedX" : 285.488, + "computedY" : 348.81, + "placementX" : 285.488, + "placementY" : 348.81, + "placementWidth" : 6.2, + "placementHeight" : 6.2, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 6.2, + "contentHeight" : 6.2, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Experience[2]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[2]/Align[0]/LayoutAnchorNode[0]", + "entityName" : null, + "entityKind" : "LayoutAnchorNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Experience[2]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[2]/Align[0]", + "childIndex" : 0, + "depth" : 14, + "layer" : 14, + "computedX" : 285.488, + "computedY" : 348.81, + "placementX" : 285.488, + "placementY" : 348.81, + "placementWidth" : 6.2, + "placementHeight" : 6.2, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 6.2, + "contentHeight" : 6.2, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Experience[2]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[2]/Align[0]/LayoutAnchorNode[0]/marker[0]", + "entityName" : "marker", + "entityKind" : "CanvasLayerNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Experience[2]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[2]/Align[0]/LayoutAnchorNode[0]", + "childIndex" : 0, + "depth" : 15, + "layer" : 15, + "computedX" : 285.488, + "computedY" : 348.81, + "placementX" : 285.488, + "placementY" : 348.81, + "placementWidth" : 6.2, + "placementHeight" : 6.2, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 6.2, + "contentHeight" : 6.2, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Experience[2]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[2]/Align[0]/LayoutAnchorNode[0]/marker[0]/SectionNode[0]", + "entityName" : null, + "entityKind" : "SectionNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Experience[2]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[2]/Align[0]/LayoutAnchorNode[0]/marker[0]", + "childIndex" : 0, + "depth" : 16, + "layer" : 16, + "computedX" : 285.488, + "computedY" : 348.81, + "placementX" : 285.488, + "placementY" : 348.81, + "placementWidth" : 6.2, + "placementHeight" : 6.2, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 6.2, + "contentHeight" : 6.2, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Experience[2]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[2]/Align[0]/LayoutAnchorNode[0]/marker[0]/SectionNode[0]/MarkerRing_2[0]", + "entityName" : "MarkerRing_2", + "entityKind" : "EllipseNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Experience[2]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[2]/Align[0]/LayoutAnchorNode[0]/marker[0]/SectionNode[0]", + "childIndex" : 0, + "depth" : 17, + "layer" : 17, + "computedX" : 285.488, + "computedY" : 348.81, + "placementX" : 285.488, + "placementY" : 348.81, + "placementWidth" : 6.2, + "placementHeight" : 6.2, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 6.2, + "contentHeight" : 6.2, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Experience[2]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[3]", + "entityName" : null, + "entityKind" : "SectionNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Experience[2]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]", + "childIndex" : 3, + "depth" : 12, + "layer" : 12, + "computedX" : 291.688, + "computedY" : 355.01, + "placementX" : 291.688, + "placementY" : 355.01, + "placementWidth" : 0.0, + "placementHeight" : 0.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 0.0, + "contentHeight" : 0.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Experience[2]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[4]", + "entityName" : null, + "entityKind" : "SectionNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Experience[2]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]", + "childIndex" : 4, + "depth" : 12, + "layer" : 12, + "computedX" : 304.588, + "computedY" : 274.21, + "placementX" : 304.588, + "placementY" : 274.21, + "placementWidth" : 197.35, + "placementHeight" : 80.8, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 197.35, + "contentHeight" : 80.8, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Experience[2]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[4]/Role_2[0]", + "entityName" : "Role_2", + "entityKind" : "ParagraphNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Experience[2]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[4]", + "childIndex" : 0, + "depth" : 13, + "layer" : 13, + "computedX" : 304.588, + "computedY" : 342.41, + "placementX" : 304.588, + "placementY" : 342.41, + "placementWidth" : 92.935, + "placementHeight" : 12.6, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 92.935, + "contentHeight" : 12.6, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 3.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Experience[2]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[4]/Employer_2[1]", + "entityName" : "Employer_2", + "entityKind" : "ParagraphNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Experience[2]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[4]", + "childIndex" : 1, + "depth" : 13, + "layer" : 13, + "computedX" : 304.588, + "computedY" : 331.13, + "placementX" : 304.588, + "placementY" : 331.13, + "placementWidth" : 149.93, + "placementHeight" : 8.28, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 149.93, + "contentHeight" : 8.28, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 7.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Experience[2]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[4]/Highlights_2[2]", + "entityName" : "Highlights_2", + "entityKind" : "ListNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Experience[2]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[4]", + "childIndex" : 2, + "depth" : 13, + "layer" : 13, + "computedX" : 304.588, + "computedY" : 274.21, + "placementX" : 304.588, + "placementY" : 274.21, + "placementWidth" : 197.35, + "placementHeight" : 49.92, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 197.35, + "contentHeight" : 49.92, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Credentials[3]", + "entityName" : "Credentials", + "entityKind" : "SectionNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]", + "childIndex" : 3, + "depth" : 4, + "layer" : 4, + "computedX" : 214.01, + "computedY" : 77.75, + "placementX" : 214.01, + "placementY" : 77.75, + "placementWidth" : 352.466, + "placementHeight" : 178.46, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 352.466, + "contentHeight" : 178.46, + "margin" : { + "top" : 18.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Credentials[3]/CredentialsRule[0]", + "entityName" : "CredentialsRule", + "entityKind" : "LineNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Credentials[3]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 214.01, + "computedY" : 255.21, + "placementX" : 214.01, + "placementY" : 255.21, + "placementWidth" : 352.466, + "placementHeight" : 1.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 352.466, + "contentHeight" : 1.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Credentials[3]/CredentialColumnsStack[1]", + "entityName" : "CredentialColumnsStack", + "entityKind" : "LayerStackNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Credentials[3]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 214.01, + "computedY" : 77.75, + "placementX" : 214.01, + "placementY" : 77.75, + "placementWidth" : 352.466, + "placementHeight" : 155.46, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 352.466, + "contentHeight" : 155.46, + "margin" : { + "top" : 22.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Credentials[3]/CredentialColumnsStack[1]/CredentialColumnsLayer[0]", + "entityName" : "CredentialColumnsLayer", + "entityKind" : "SectionNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Credentials[3]/CredentialColumnsStack[1]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 214.01, + "computedY" : 77.75, + "placementX" : 214.01, + "placementY" : 77.75, + "placementWidth" : 352.466, + "placementHeight" : 155.46, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 352.466, + "contentHeight" : 155.46, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Credentials[3]/CredentialColumnsStack[1]/CredentialColumnsLayer[0]/CredentialColumns[0]", + "entityName" : "CredentialColumns", + "entityKind" : "RowNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Credentials[3]/CredentialColumnsStack[1]/CredentialColumnsLayer[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 214.01, + "computedY" : 77.75, + "placementX" : 214.01, + "placementY" : 77.75, + "placementWidth" : 352.466, + "placementHeight" : 155.46, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 352.466, + "contentHeight" : 155.46, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Credentials[3]/CredentialColumnsStack[1]/CredentialColumnsLayer[0]/CredentialColumns[0]/Certifications[0]", + "entityName" : "Certifications", + "entityKind" : "SectionNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Credentials[3]/CredentialColumnsStack[1]/CredentialColumnsLayer[0]/CredentialColumns[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 214.01, + "computedY" : 77.75, + "placementX" : 214.01, + "placementY" : 77.75, + "placementWidth" : 163.544, + "placementHeight" : 155.46, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 163.544, + "contentHeight" : 155.46, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 21.853, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Credentials[3]/CredentialColumnsStack[1]/CredentialColumnsLayer[0]/CredentialColumns[0]/Certifications[0]/Heading_CERTIFICATIONS[0]", + "entityName" : "Heading_CERTIFICATIONS", + "entityKind" : "SectionNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Credentials[3]/CredentialColumnsStack[1]/CredentialColumnsLayer[0]/CredentialColumns[0]/Certifications[0]", + "childIndex" : 0, + "depth" : 9, + "layer" : 9, + "computedX" : 214.01, + "computedY" : 220.01, + "placementX" : 214.01, + "placementY" : 220.01, + "placementWidth" : 96.054, + "placementHeight" : 13.2, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 96.054, + "contentHeight" : 13.2, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 9.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Credentials[3]/CredentialColumnsStack[1]/CredentialColumnsLayer[0]/CredentialColumns[0]/Certifications[0]/Heading_CERTIFICATIONS[0]/HeadingText_CERTIFICATIONS[0]", + "entityName" : "HeadingText_CERTIFICATIONS", + "entityKind" : "ParagraphNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Credentials[3]/CredentialColumnsStack[1]/CredentialColumnsLayer[0]/CredentialColumns[0]/Certifications[0]/Heading_CERTIFICATIONS[0]", + "childIndex" : 0, + "depth" : 10, + "layer" : 10, + "computedX" : 223.01, + "computedY" : 220.01, + "placementX" : 223.01, + "placementY" : 220.01, + "placementWidth" : 87.054, + "placementHeight" : 13.2, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 87.054, + "contentHeight" : 13.2, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Credentials[3]/CredentialColumnsStack[1]/CredentialColumnsLayer[0]/CredentialColumns[0]/Certifications[0]/Certification_0Stack[1]", + "entityName" : "Certification_0Stack", + "entityKind" : "LayerStackNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Credentials[3]/CredentialColumnsStack[1]/CredentialColumnsLayer[0]/CredentialColumns[0]/Certifications[0]", + "childIndex" : 1, + "depth" : 9, + "layer" : 9, + "computedX" : 214.01, + "computedY" : 170.39, + "placementX" : 214.01, + "placementY" : 170.39, + "placementWidth" : 141.691, + "placementHeight" : 32.32, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 141.691, + "contentHeight" : 32.32, + "margin" : { + "top" : 17.3, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Credentials[3]/CredentialColumnsStack[1]/CredentialColumnsLayer[0]/CredentialColumns[0]/Certifications[0]/Certification_0Stack[1]/Certification_0Layer[0]", + "entityName" : "Certification_0Layer", + "entityKind" : "SectionNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Credentials[3]/CredentialColumnsStack[1]/CredentialColumnsLayer[0]/CredentialColumns[0]/Certifications[0]/Certification_0Stack[1]", + "childIndex" : 0, + "depth" : 10, + "layer" : 10, + "computedX" : 214.01, + "computedY" : 170.39, + "placementX" : 214.01, + "placementY" : 170.39, + "placementWidth" : 141.691, + "placementHeight" : 32.32, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 141.691, + "contentHeight" : 32.32, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Credentials[3]/CredentialColumnsStack[1]/CredentialColumnsLayer[0]/CredentialColumns[0]/Certifications[0]/Certification_0Stack[1]/Certification_0Layer[0]/Certification_0[0]", + "entityName" : "Certification_0", + "entityKind" : "RowNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Credentials[3]/CredentialColumnsStack[1]/CredentialColumnsLayer[0]/CredentialColumns[0]/Certifications[0]/Certification_0Stack[1]/Certification_0Layer[0]", + "childIndex" : 0, + "depth" : 11, + "layer" : 11, + "computedX" : 214.01, + "computedY" : 170.39, + "placementX" : 214.01, + "placementY" : 170.39, + "placementWidth" : 141.691, + "placementHeight" : 32.32, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 141.691, + "contentHeight" : 32.32, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Credentials[3]/CredentialColumnsStack[1]/CredentialColumnsLayer[0]/CredentialColumns[0]/Certifications[0]/Certification_0Stack[1]/Certification_0Layer[0]/Certification_0[0]/CertificationIcon_0[0]", + "entityName" : "CertificationIcon_0", + "entityKind" : "SectionNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Credentials[3]/CredentialColumnsStack[1]/CredentialColumnsLayer[0]/CredentialColumns[0]/Certifications[0]/Certification_0Stack[1]/Certification_0Layer[0]/Certification_0[0]", + "childIndex" : 0, + "depth" : 12, + "layer" : 12, + "computedX" : 214.01, + "computedY" : 187.71, + "placementX" : 214.01, + "placementY" : 187.71, + "placementWidth" : 15.0, + "placementHeight" : 15.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 15.0, + "contentHeight" : 15.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Credentials[3]/CredentialColumnsStack[1]/CredentialColumnsLayer[0]/CredentialColumns[0]/Certifications[0]/Certification_0Stack[1]/Certification_0Layer[0]/Certification_0[0]/CertificationIcon_0[0]/SvgIcon[0]", + "entityName" : "SvgIcon", + "entityKind" : "LayerStackNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Credentials[3]/CredentialColumnsStack[1]/CredentialColumnsLayer[0]/CredentialColumns[0]/Certifications[0]/Certification_0Stack[1]/Certification_0Layer[0]/Certification_0[0]/CertificationIcon_0[0]", + "childIndex" : 0, + "depth" : 13, + "layer" : 13, + "computedX" : 214.01, + "computedY" : 187.71, + "placementX" : 214.01, + "placementY" : 187.71, + "placementWidth" : 15.0, + "placementHeight" : 15.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 15.0, + "contentHeight" : 15.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Credentials[3]/CredentialColumnsStack[1]/CredentialColumnsLayer[0]/CredentialColumns[0]/Certifications[0]/Certification_0Stack[1]/Certification_0Layer[0]/Certification_0[0]/CertificationIcon_0[0]/SvgIcon[0]/SvgLayer0[0]", + "entityName" : "SvgLayer0", + "entityKind" : "Path", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Credentials[3]/CredentialColumnsStack[1]/CredentialColumnsLayer[0]/CredentialColumns[0]/Certifications[0]/Certification_0Stack[1]/Certification_0Layer[0]/Certification_0[0]/CertificationIcon_0[0]/SvgIcon[0]", + "childIndex" : 0, + "depth" : 14, + "layer" : 14, + "computedX" : 214.01, + "computedY" : 187.71, + "placementX" : 214.01, + "placementY" : 187.71, + "placementWidth" : 15.0, + "placementHeight" : 15.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 15.0, + "contentHeight" : 15.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Credentials[3]/CredentialColumnsStack[1]/CredentialColumnsLayer[0]/CredentialColumns[0]/Certifications[0]/Certification_0Stack[1]/Certification_0Layer[0]/Certification_0[0]/CertificationText_0[1]", + "entityName" : "CertificationText_0", + "entityKind" : "SectionNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Credentials[3]/CredentialColumnsStack[1]/CredentialColumnsLayer[0]/CredentialColumns[0]/Certifications[0]/Certification_0Stack[1]/Certification_0Layer[0]/Certification_0[0]", + "childIndex" : 1, + "depth" : 12, + "layer" : 12, + "computedX" : 233.846, + "computedY" : 170.39, + "placementX" : 233.846, + "placementY" : 170.39, + "placementWidth" : 103.648, + "placementHeight" : 32.32, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 103.648, + "contentHeight" : 32.32, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Credentials[3]/CredentialColumnsStack[1]/CredentialColumnsLayer[0]/CredentialColumns[0]/Certifications[0]/Certification_0Stack[1]/Certification_0Layer[0]/Certification_0[0]/CertificationText_0[1]/CertificationName_0[0]", + "entityName" : "CertificationName_0", + "entityKind" : "ParagraphNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Credentials[3]/CredentialColumnsStack[1]/CredentialColumnsLayer[0]/CredentialColumns[0]/Certifications[0]/Certification_0Stack[1]/Certification_0Layer[0]/Certification_0[0]/CertificationText_0[1]", + "childIndex" : 0, + "depth" : 13, + "layer" : 13, + "computedX" : 233.846, + "computedY" : 193.35, + "placementX" : 233.846, + "placementY" : 193.35, + "placementWidth" : 68.741, + "placementHeight" : 9.36, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 68.741, + "contentHeight" : 9.36, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 2.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Credentials[3]/CredentialColumnsStack[1]/CredentialColumnsLayer[0]/CredentialColumns[0]/Certifications[0]/Certification_0Stack[1]/Certification_0Layer[0]/Certification_0[0]/CertificationText_0[1]/CertificationIssuer_0[1]", + "entityName" : "CertificationIssuer_0", + "entityKind" : "ParagraphNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Credentials[3]/CredentialColumnsStack[1]/CredentialColumnsLayer[0]/CredentialColumns[0]/Certifications[0]/Certification_0Stack[1]/Certification_0Layer[0]/Certification_0[0]/CertificationText_0[1]", + "childIndex" : 1, + "depth" : 13, + "layer" : 13, + "computedX" : 233.846, + "computedY" : 181.87, + "placementX" : 233.846, + "placementY" : 181.87, + "placementWidth" : 103.648, + "placementHeight" : 9.48, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 103.648, + "contentHeight" : 9.48, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 2.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Credentials[3]/CredentialColumnsStack[1]/CredentialColumnsLayer[0]/CredentialColumns[0]/Certifications[0]/Certification_0Stack[1]/Certification_0Layer[0]/Certification_0[0]/CertificationText_0[1]/CertificationYear_0[2]", + "entityName" : "CertificationYear_0", + "entityKind" : "ParagraphNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Credentials[3]/CredentialColumnsStack[1]/CredentialColumnsLayer[0]/CredentialColumns[0]/Certifications[0]/Certification_0Stack[1]/Certification_0Layer[0]/Certification_0[0]/CertificationText_0[1]", + "childIndex" : 2, + "depth" : 13, + "layer" : 13, + "computedX" : 233.846, + "computedY" : 170.39, + "placementX" : 233.846, + "placementY" : 170.39, + "placementWidth" : 18.328, + "placementHeight" : 9.48, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 18.328, + "contentHeight" : 9.48, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Credentials[3]/CredentialColumnsStack[1]/CredentialColumnsLayer[0]/CredentialColumns[0]/Certifications[0]/CertificationRule_0[2]", + "entityName" : "CertificationRule_0", + "entityKind" : "LineNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Credentials[3]/CredentialColumnsStack[1]/CredentialColumnsLayer[0]/CredentialColumns[0]/Certifications[0]", + "childIndex" : 2, + "depth" : 9, + "layer" : 9, + "computedX" : 214.01, + "computedY" : 162.89, + "placementX" : 214.01, + "placementY" : 162.89, + "placementWidth" : 141.691, + "placementHeight" : 1.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 141.691, + "contentHeight" : 1.0, + "margin" : { + "top" : 6.5, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Credentials[3]/CredentialColumnsStack[1]/CredentialColumnsLayer[0]/CredentialColumns[0]/Certifications[0]/Certification_1Stack[3]", + "entityName" : "Certification_1Stack", + "entityKind" : "LayerStackNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Credentials[3]/CredentialColumnsStack[1]/CredentialColumnsLayer[0]/CredentialColumns[0]/Certifications[0]", + "childIndex" : 3, + "depth" : 9, + "layer" : 9, + "computedX" : 214.01, + "computedY" : 124.07, + "placementX" : 214.01, + "placementY" : 124.07, + "placementWidth" : 141.691, + "placementHeight" : 32.32, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 141.691, + "contentHeight" : 32.32, + "margin" : { + "top" : 6.5, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Credentials[3]/CredentialColumnsStack[1]/CredentialColumnsLayer[0]/CredentialColumns[0]/Certifications[0]/Certification_1Stack[3]/Certification_1Layer[0]", + "entityName" : "Certification_1Layer", + "entityKind" : "SectionNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Credentials[3]/CredentialColumnsStack[1]/CredentialColumnsLayer[0]/CredentialColumns[0]/Certifications[0]/Certification_1Stack[3]", + "childIndex" : 0, + "depth" : 10, + "layer" : 10, + "computedX" : 214.01, + "computedY" : 124.07, + "placementX" : 214.01, + "placementY" : 124.07, + "placementWidth" : 141.691, + "placementHeight" : 32.32, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 141.691, + "contentHeight" : 32.32, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Credentials[3]/CredentialColumnsStack[1]/CredentialColumnsLayer[0]/CredentialColumns[0]/Certifications[0]/Certification_1Stack[3]/Certification_1Layer[0]/Certification_1[0]", + "entityName" : "Certification_1", + "entityKind" : "RowNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Credentials[3]/CredentialColumnsStack[1]/CredentialColumnsLayer[0]/CredentialColumns[0]/Certifications[0]/Certification_1Stack[3]/Certification_1Layer[0]", + "childIndex" : 0, + "depth" : 11, + "layer" : 11, + "computedX" : 214.01, + "computedY" : 124.07, + "placementX" : 214.01, + "placementY" : 124.07, + "placementWidth" : 141.691, + "placementHeight" : 32.32, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 141.691, + "contentHeight" : 32.32, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Credentials[3]/CredentialColumnsStack[1]/CredentialColumnsLayer[0]/CredentialColumns[0]/Certifications[0]/Certification_1Stack[3]/Certification_1Layer[0]/Certification_1[0]/CertificationIcon_1[0]", + "entityName" : "CertificationIcon_1", + "entityKind" : "SectionNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Credentials[3]/CredentialColumnsStack[1]/CredentialColumnsLayer[0]/CredentialColumns[0]/Certifications[0]/Certification_1Stack[3]/Certification_1Layer[0]/Certification_1[0]", + "childIndex" : 0, + "depth" : 12, + "layer" : 12, + "computedX" : 214.01, + "computedY" : 141.39, + "placementX" : 214.01, + "placementY" : 141.39, + "placementWidth" : 15.0, + "placementHeight" : 15.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 15.0, + "contentHeight" : 15.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Credentials[3]/CredentialColumnsStack[1]/CredentialColumnsLayer[0]/CredentialColumns[0]/Certifications[0]/Certification_1Stack[3]/Certification_1Layer[0]/Certification_1[0]/CertificationIcon_1[0]/SvgIcon[0]", + "entityName" : "SvgIcon", + "entityKind" : "LayerStackNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Credentials[3]/CredentialColumnsStack[1]/CredentialColumnsLayer[0]/CredentialColumns[0]/Certifications[0]/Certification_1Stack[3]/Certification_1Layer[0]/Certification_1[0]/CertificationIcon_1[0]", + "childIndex" : 0, + "depth" : 13, + "layer" : 13, + "computedX" : 214.01, + "computedY" : 141.39, + "placementX" : 214.01, + "placementY" : 141.39, + "placementWidth" : 15.0, + "placementHeight" : 15.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 15.0, + "contentHeight" : 15.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Credentials[3]/CredentialColumnsStack[1]/CredentialColumnsLayer[0]/CredentialColumns[0]/Certifications[0]/Certification_1Stack[3]/Certification_1Layer[0]/Certification_1[0]/CertificationIcon_1[0]/SvgIcon[0]/SvgLayer0[0]", + "entityName" : "SvgLayer0", + "entityKind" : "Path", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Credentials[3]/CredentialColumnsStack[1]/CredentialColumnsLayer[0]/CredentialColumns[0]/Certifications[0]/Certification_1Stack[3]/Certification_1Layer[0]/Certification_1[0]/CertificationIcon_1[0]/SvgIcon[0]", + "childIndex" : 0, + "depth" : 14, + "layer" : 14, + "computedX" : 214.01, + "computedY" : 141.39, + "placementX" : 214.01, + "placementY" : 141.39, + "placementWidth" : 15.0, + "placementHeight" : 15.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 15.0, + "contentHeight" : 15.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Credentials[3]/CredentialColumnsStack[1]/CredentialColumnsLayer[0]/CredentialColumns[0]/Certifications[0]/Certification_1Stack[3]/Certification_1Layer[0]/Certification_1[0]/CertificationText_1[1]", + "entityName" : "CertificationText_1", + "entityKind" : "SectionNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Credentials[3]/CredentialColumnsStack[1]/CredentialColumnsLayer[0]/CredentialColumns[0]/Certifications[0]/Certification_1Stack[3]/Certification_1Layer[0]/Certification_1[0]", + "childIndex" : 1, + "depth" : 12, + "layer" : 12, + "computedX" : 233.846, + "computedY" : 124.07, + "placementX" : 233.846, + "placementY" : 124.07, + "placementWidth" : 80.527, + "placementHeight" : 32.32, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 80.527, + "contentHeight" : 32.32, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Credentials[3]/CredentialColumnsStack[1]/CredentialColumnsLayer[0]/CredentialColumns[0]/Certifications[0]/Certification_1Stack[3]/Certification_1Layer[0]/Certification_1[0]/CertificationText_1[1]/CertificationName_1[0]", + "entityName" : "CertificationName_1", + "entityKind" : "ParagraphNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Credentials[3]/CredentialColumnsStack[1]/CredentialColumnsLayer[0]/CredentialColumns[0]/Certifications[0]/Certification_1Stack[3]/Certification_1Layer[0]/Certification_1[0]/CertificationText_1[1]", + "childIndex" : 0, + "depth" : 13, + "layer" : 13, + "computedX" : 233.846, + "computedY" : 147.03, + "placementX" : 233.846, + "placementY" : 147.03, + "placementWidth" : 80.527, + "placementHeight" : 9.36, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 80.527, + "contentHeight" : 9.36, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 2.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Credentials[3]/CredentialColumnsStack[1]/CredentialColumnsLayer[0]/CredentialColumns[0]/Certifications[0]/Certification_1Stack[3]/Certification_1Layer[0]/Certification_1[0]/CertificationText_1[1]/CertificationIssuer_1[1]", + "entityName" : "CertificationIssuer_1", + "entityKind" : "ParagraphNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Credentials[3]/CredentialColumnsStack[1]/CredentialColumnsLayer[0]/CredentialColumns[0]/Certifications[0]/Certification_1Stack[3]/Certification_1Layer[0]/Certification_1[0]/CertificationText_1[1]", + "childIndex" : 1, + "depth" : 13, + "layer" : 13, + "computedX" : 233.846, + "computedY" : 135.55, + "placementX" : 233.846, + "placementY" : 135.55, + "placementWidth" : 29.72, + "placementHeight" : 9.48, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 29.72, + "contentHeight" : 9.48, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 2.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Credentials[3]/CredentialColumnsStack[1]/CredentialColumnsLayer[0]/CredentialColumns[0]/Certifications[0]/Certification_1Stack[3]/Certification_1Layer[0]/Certification_1[0]/CertificationText_1[1]/CertificationYear_1[2]", + "entityName" : "CertificationYear_1", + "entityKind" : "ParagraphNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Credentials[3]/CredentialColumnsStack[1]/CredentialColumnsLayer[0]/CredentialColumns[0]/Certifications[0]/Certification_1Stack[3]/Certification_1Layer[0]/Certification_1[0]/CertificationText_1[1]", + "childIndex" : 2, + "depth" : 13, + "layer" : 13, + "computedX" : 233.846, + "computedY" : 124.07, + "placementX" : 233.846, + "placementY" : 124.07, + "placementWidth" : 18.328, + "placementHeight" : 9.48, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 18.328, + "contentHeight" : 9.48, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Credentials[3]/CredentialColumnsStack[1]/CredentialColumnsLayer[0]/CredentialColumns[0]/Certifications[0]/CertificationRule_1[4]", + "entityName" : "CertificationRule_1", + "entityKind" : "LineNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Credentials[3]/CredentialColumnsStack[1]/CredentialColumnsLayer[0]/CredentialColumns[0]/Certifications[0]", + "childIndex" : 4, + "depth" : 9, + "layer" : 9, + "computedX" : 214.01, + "computedY" : 116.57, + "placementX" : 214.01, + "placementY" : 116.57, + "placementWidth" : 141.691, + "placementHeight" : 1.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 141.691, + "contentHeight" : 1.0, + "margin" : { + "top" : 6.5, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Credentials[3]/CredentialColumnsStack[1]/CredentialColumnsLayer[0]/CredentialColumns[0]/Certifications[0]/Certification_2Stack[5]", + "entityName" : "Certification_2Stack", + "entityKind" : "LayerStackNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Credentials[3]/CredentialColumnsStack[1]/CredentialColumnsLayer[0]/CredentialColumns[0]/Certifications[0]", + "childIndex" : 5, + "depth" : 9, + "layer" : 9, + "computedX" : 214.01, + "computedY" : 77.75, + "placementX" : 214.01, + "placementY" : 77.75, + "placementWidth" : 141.691, + "placementHeight" : 32.32, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 141.691, + "contentHeight" : 32.32, + "margin" : { + "top" : 6.5, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Credentials[3]/CredentialColumnsStack[1]/CredentialColumnsLayer[0]/CredentialColumns[0]/Certifications[0]/Certification_2Stack[5]/Certification_2Layer[0]", + "entityName" : "Certification_2Layer", + "entityKind" : "SectionNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Credentials[3]/CredentialColumnsStack[1]/CredentialColumnsLayer[0]/CredentialColumns[0]/Certifications[0]/Certification_2Stack[5]", + "childIndex" : 0, + "depth" : 10, + "layer" : 10, + "computedX" : 214.01, + "computedY" : 77.75, + "placementX" : 214.01, + "placementY" : 77.75, + "placementWidth" : 141.691, + "placementHeight" : 32.32, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 141.691, + "contentHeight" : 32.32, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Credentials[3]/CredentialColumnsStack[1]/CredentialColumnsLayer[0]/CredentialColumns[0]/Certifications[0]/Certification_2Stack[5]/Certification_2Layer[0]/Certification_2[0]", + "entityName" : "Certification_2", + "entityKind" : "RowNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Credentials[3]/CredentialColumnsStack[1]/CredentialColumnsLayer[0]/CredentialColumns[0]/Certifications[0]/Certification_2Stack[5]/Certification_2Layer[0]", + "childIndex" : 0, + "depth" : 11, + "layer" : 11, + "computedX" : 214.01, + "computedY" : 77.75, + "placementX" : 214.01, + "placementY" : 77.75, + "placementWidth" : 141.691, + "placementHeight" : 32.32, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 141.691, + "contentHeight" : 32.32, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Credentials[3]/CredentialColumnsStack[1]/CredentialColumnsLayer[0]/CredentialColumns[0]/Certifications[0]/Certification_2Stack[5]/Certification_2Layer[0]/Certification_2[0]/CertificationIcon_2[0]", + "entityName" : "CertificationIcon_2", + "entityKind" : "SectionNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Credentials[3]/CredentialColumnsStack[1]/CredentialColumnsLayer[0]/CredentialColumns[0]/Certifications[0]/Certification_2Stack[5]/Certification_2Layer[0]/Certification_2[0]", + "childIndex" : 0, + "depth" : 12, + "layer" : 12, + "computedX" : 214.01, + "computedY" : 95.07, + "placementX" : 214.01, + "placementY" : 95.07, + "placementWidth" : 15.0, + "placementHeight" : 15.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 15.0, + "contentHeight" : 15.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Credentials[3]/CredentialColumnsStack[1]/CredentialColumnsLayer[0]/CredentialColumns[0]/Certifications[0]/Certification_2Stack[5]/Certification_2Layer[0]/Certification_2[0]/CertificationIcon_2[0]/SvgIcon[0]", + "entityName" : "SvgIcon", + "entityKind" : "LayerStackNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Credentials[3]/CredentialColumnsStack[1]/CredentialColumnsLayer[0]/CredentialColumns[0]/Certifications[0]/Certification_2Stack[5]/Certification_2Layer[0]/Certification_2[0]/CertificationIcon_2[0]", + "childIndex" : 0, + "depth" : 13, + "layer" : 13, + "computedX" : 214.01, + "computedY" : 95.07, + "placementX" : 214.01, + "placementY" : 95.07, + "placementWidth" : 15.0, + "placementHeight" : 15.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 15.0, + "contentHeight" : 15.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Credentials[3]/CredentialColumnsStack[1]/CredentialColumnsLayer[0]/CredentialColumns[0]/Certifications[0]/Certification_2Stack[5]/Certification_2Layer[0]/Certification_2[0]/CertificationIcon_2[0]/SvgIcon[0]/SvgLayer0[0]", + "entityName" : "SvgLayer0", + "entityKind" : "Path", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Credentials[3]/CredentialColumnsStack[1]/CredentialColumnsLayer[0]/CredentialColumns[0]/Certifications[0]/Certification_2Stack[5]/Certification_2Layer[0]/Certification_2[0]/CertificationIcon_2[0]/SvgIcon[0]", + "childIndex" : 0, + "depth" : 14, + "layer" : 14, + "computedX" : 214.01, + "computedY" : 95.07, + "placementX" : 214.01, + "placementY" : 95.07, + "placementWidth" : 15.0, + "placementHeight" : 15.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 15.0, + "contentHeight" : 15.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Credentials[3]/CredentialColumnsStack[1]/CredentialColumnsLayer[0]/CredentialColumns[0]/Certifications[0]/Certification_2Stack[5]/Certification_2Layer[0]/Certification_2[0]/CertificationText_2[1]", + "entityName" : "CertificationText_2", + "entityKind" : "SectionNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Credentials[3]/CredentialColumnsStack[1]/CredentialColumnsLayer[0]/CredentialColumns[0]/Certifications[0]/Certification_2Stack[5]/Certification_2Layer[0]/Certification_2[0]", + "childIndex" : 1, + "depth" : 12, + "layer" : 12, + "computedX" : 233.846, + "computedY" : 77.75, + "placementX" : 233.846, + "placementY" : 77.75, + "placementWidth" : 78.008, + "placementHeight" : 32.32, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 78.008, + "contentHeight" : 32.32, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Credentials[3]/CredentialColumnsStack[1]/CredentialColumnsLayer[0]/CredentialColumns[0]/Certifications[0]/Certification_2Stack[5]/Certification_2Layer[0]/Certification_2[0]/CertificationText_2[1]/CertificationName_2[0]", + "entityName" : "CertificationName_2", + "entityKind" : "ParagraphNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Credentials[3]/CredentialColumnsStack[1]/CredentialColumnsLayer[0]/CredentialColumns[0]/Certifications[0]/Certification_2Stack[5]/Certification_2Layer[0]/Certification_2[0]/CertificationText_2[1]", + "childIndex" : 0, + "depth" : 13, + "layer" : 13, + "computedX" : 233.846, + "computedY" : 100.71, + "placementX" : 233.846, + "placementY" : 100.71, + "placementWidth" : 78.008, + "placementHeight" : 9.36, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 78.008, + "contentHeight" : 9.36, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 2.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Credentials[3]/CredentialColumnsStack[1]/CredentialColumnsLayer[0]/CredentialColumns[0]/Certifications[0]/Certification_2Stack[5]/Certification_2Layer[0]/Certification_2[0]/CertificationText_2[1]/CertificationIssuer_2[1]", + "entityName" : "CertificationIssuer_2", + "entityKind" : "ParagraphNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Credentials[3]/CredentialColumnsStack[1]/CredentialColumnsLayer[0]/CredentialColumns[0]/Certifications[0]/Certification_2Stack[5]/Certification_2Layer[0]/Certification_2[0]/CertificationText_2[1]", + "childIndex" : 1, + "depth" : 13, + "layer" : 13, + "computedX" : 233.846, + "computedY" : 89.23, + "placementX" : 233.846, + "placementY" : 89.23, + "placementWidth" : 69.481, + "placementHeight" : 9.48, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 69.481, + "contentHeight" : 9.48, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 2.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Credentials[3]/CredentialColumnsStack[1]/CredentialColumnsLayer[0]/CredentialColumns[0]/Certifications[0]/Certification_2Stack[5]/Certification_2Layer[0]/Certification_2[0]/CertificationText_2[1]/CertificationYear_2[2]", + "entityName" : "CertificationYear_2", + "entityKind" : "ParagraphNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Credentials[3]/CredentialColumnsStack[1]/CredentialColumnsLayer[0]/CredentialColumns[0]/Certifications[0]/Certification_2Stack[5]/Certification_2Layer[0]/Certification_2[0]/CertificationText_2[1]", + "childIndex" : 2, + "depth" : 13, + "layer" : 13, + "computedX" : 233.846, + "computedY" : 77.75, + "placementX" : 233.846, + "placementY" : 77.75, + "placementWidth" : 18.328, + "placementHeight" : 9.48, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 18.328, + "contentHeight" : 9.48, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Credentials[3]/CredentialColumnsStack[1]/CredentialColumnsLayer[0]/CredentialColumns[0]/Achievements[1]", + "entityName" : "Achievements", + "entityKind" : "SectionNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Credentials[3]/CredentialColumnsStack[1]/CredentialColumnsLayer[0]/CredentialColumns[0]", + "childIndex" : 1, + "depth" : 8, + "layer" : 8, + "computedX" : 377.554, + "computedY" : 77.75, + "placementX" : 377.554, + "placementY" : 77.75, + "placementWidth" : 188.922, + "placementHeight" : 155.46, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 188.922, + "contentHeight" : 155.46, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 21.853 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Credentials[3]/CredentialColumnsStack[1]/CredentialColumnsLayer[0]/CredentialColumns[0]/Achievements[1]/Heading_ACHIEVEMENTS[0]", + "entityName" : "Heading_ACHIEVEMENTS", + "entityKind" : "SectionNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Credentials[3]/CredentialColumnsStack[1]/CredentialColumnsLayer[0]/CredentialColumns[0]/Achievements[1]", + "childIndex" : 0, + "depth" : 9, + "layer" : 9, + "computedX" : 399.407, + "computedY" : 220.01, + "placementX" : 399.407, + "placementY" : 220.01, + "placementWidth" : 92.897, + "placementHeight" : 13.2, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 92.897, + "contentHeight" : 13.2, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 9.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Credentials[3]/CredentialColumnsStack[1]/CredentialColumnsLayer[0]/CredentialColumns[0]/Achievements[1]/Heading_ACHIEVEMENTS[0]/HeadingText_ACHIEVEMENTS[0]", + "entityName" : "HeadingText_ACHIEVEMENTS", + "entityKind" : "ParagraphNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Credentials[3]/CredentialColumnsStack[1]/CredentialColumnsLayer[0]/CredentialColumns[0]/Achievements[1]/Heading_ACHIEVEMENTS[0]", + "childIndex" : 0, + "depth" : 10, + "layer" : 10, + "computedX" : 408.407, + "computedY" : 220.01, + "placementX" : 408.407, + "placementY" : 220.01, + "placementWidth" : 83.897, + "placementHeight" : 13.2, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 83.897, + "contentHeight" : 13.2, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Credentials[3]/CredentialColumnsStack[1]/CredentialColumnsLayer[0]/CredentialColumns[0]/Achievements[1]/Achievement_0Stack[1]", + "entityName" : "Achievement_0Stack", + "entityKind" : "LayerStackNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Credentials[3]/CredentialColumnsStack[1]/CredentialColumnsLayer[0]/CredentialColumns[0]/Achievements[1]", + "childIndex" : 1, + "depth" : 9, + "layer" : 9, + "computedX" : 399.407, + "computedY" : 170.39, + "placementX" : 399.407, + "placementY" : 170.39, + "placementWidth" : 167.069, + "placementHeight" : 32.32, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 167.069, + "contentHeight" : 32.32, + "margin" : { + "top" : 17.3, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Credentials[3]/CredentialColumnsStack[1]/CredentialColumnsLayer[0]/CredentialColumns[0]/Achievements[1]/Achievement_0Stack[1]/Achievement_0Layer[0]", + "entityName" : "Achievement_0Layer", + "entityKind" : "SectionNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Credentials[3]/CredentialColumnsStack[1]/CredentialColumnsLayer[0]/CredentialColumns[0]/Achievements[1]/Achievement_0Stack[1]", + "childIndex" : 0, + "depth" : 10, + "layer" : 10, + "computedX" : 399.407, + "computedY" : 170.39, + "placementX" : 399.407, + "placementY" : 170.39, + "placementWidth" : 167.069, + "placementHeight" : 32.32, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 167.069, + "contentHeight" : 32.32, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Credentials[3]/CredentialColumnsStack[1]/CredentialColumnsLayer[0]/CredentialColumns[0]/Achievements[1]/Achievement_0Stack[1]/Achievement_0Layer[0]/Achievement_0[0]", + "entityName" : "Achievement_0", + "entityKind" : "RowNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Credentials[3]/CredentialColumnsStack[1]/CredentialColumnsLayer[0]/CredentialColumns[0]/Achievements[1]/Achievement_0Stack[1]/Achievement_0Layer[0]", + "childIndex" : 0, + "depth" : 11, + "layer" : 11, + "computedX" : 399.407, + "computedY" : 170.39, + "placementX" : 399.407, + "placementY" : 170.39, + "placementWidth" : 167.069, + "placementHeight" : 32.32, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 167.069, + "contentHeight" : 32.32, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Credentials[3]/CredentialColumnsStack[1]/CredentialColumnsLayer[0]/CredentialColumns[0]/Achievements[1]/Achievement_0Stack[1]/Achievement_0Layer[0]/Achievement_0[0]/AchievementIcon_0[0]", + "entityName" : "AchievementIcon_0", + "entityKind" : "SectionNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Credentials[3]/CredentialColumnsStack[1]/CredentialColumnsLayer[0]/CredentialColumns[0]/Achievements[1]/Achievement_0Stack[1]/Achievement_0Layer[0]/Achievement_0[0]", + "childIndex" : 0, + "depth" : 12, + "layer" : 12, + "computedX" : 399.407, + "computedY" : 187.71, + "placementX" : 399.407, + "placementY" : 187.71, + "placementWidth" : 15.0, + "placementHeight" : 15.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 15.0, + "contentHeight" : 15.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Credentials[3]/CredentialColumnsStack[1]/CredentialColumnsLayer[0]/CredentialColumns[0]/Achievements[1]/Achievement_0Stack[1]/Achievement_0Layer[0]/Achievement_0[0]/AchievementIcon_0[0]/SvgIcon[0]", + "entityName" : "SvgIcon", + "entityKind" : "LayerStackNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Credentials[3]/CredentialColumnsStack[1]/CredentialColumnsLayer[0]/CredentialColumns[0]/Achievements[1]/Achievement_0Stack[1]/Achievement_0Layer[0]/Achievement_0[0]/AchievementIcon_0[0]", + "childIndex" : 0, + "depth" : 13, + "layer" : 13, + "computedX" : 399.407, + "computedY" : 187.71, + "placementX" : 399.407, + "placementY" : 187.71, + "placementWidth" : 15.0, + "placementHeight" : 15.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 15.0, + "contentHeight" : 15.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Credentials[3]/CredentialColumnsStack[1]/CredentialColumnsLayer[0]/CredentialColumns[0]/Achievements[1]/Achievement_0Stack[1]/Achievement_0Layer[0]/Achievement_0[0]/AchievementIcon_0[0]/SvgIcon[0]/SvgLayer0[0]", + "entityName" : "SvgLayer0", + "entityKind" : "Path", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Credentials[3]/CredentialColumnsStack[1]/CredentialColumnsLayer[0]/CredentialColumns[0]/Achievements[1]/Achievement_0Stack[1]/Achievement_0Layer[0]/Achievement_0[0]/AchievementIcon_0[0]/SvgIcon[0]", + "childIndex" : 0, + "depth" : 14, + "layer" : 14, + "computedX" : 399.407, + "computedY" : 187.71, + "placementX" : 399.407, + "placementY" : 187.71, + "placementWidth" : 15.0, + "placementHeight" : 15.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 15.0, + "contentHeight" : 15.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Credentials[3]/CredentialColumnsStack[1]/CredentialColumnsLayer[0]/CredentialColumns[0]/Achievements[1]/Achievement_0Stack[1]/Achievement_0Layer[0]/Achievement_0[0]/AchievementText_0[1]", + "entityName" : "AchievementText_0", + "entityKind" : "SectionNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Credentials[3]/CredentialColumnsStack[1]/CredentialColumnsLayer[0]/CredentialColumns[0]/Achievements[1]/Achievement_0Stack[1]/Achievement_0Layer[0]/Achievement_0[0]", + "childIndex" : 1, + "depth" : 12, + "layer" : 12, + "computedX" : 422.796, + "computedY" : 170.39, + "placementX" : 422.796, + "placementY" : 170.39, + "placementWidth" : 101.244, + "placementHeight" : 32.32, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 101.244, + "contentHeight" : 32.32, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Credentials[3]/CredentialColumnsStack[1]/CredentialColumnsLayer[0]/CredentialColumns[0]/Achievements[1]/Achievement_0Stack[1]/Achievement_0Layer[0]/Achievement_0[0]/AchievementText_0[1]/AchievementName_0[0]", + "entityName" : "AchievementName_0", + "entityKind" : "ParagraphNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Credentials[3]/CredentialColumnsStack[1]/CredentialColumnsLayer[0]/CredentialColumns[0]/Achievements[1]/Achievement_0Stack[1]/Achievement_0Layer[0]/Achievement_0[0]/AchievementText_0[1]", + "childIndex" : 0, + "depth" : 13, + "layer" : 13, + "computedX" : 422.796, + "computedY" : 193.35, + "placementX" : 422.796, + "placementY" : 193.35, + "placementWidth" : 101.244, + "placementHeight" : 9.36, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 101.244, + "contentHeight" : 9.36, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 2.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Credentials[3]/CredentialColumnsStack[1]/CredentialColumnsLayer[0]/CredentialColumns[0]/Achievements[1]/Achievement_0Stack[1]/Achievement_0Layer[0]/Achievement_0[0]/AchievementText_0[1]/AchievementIssuer_0[1]", + "entityName" : "AchievementIssuer_0", + "entityKind" : "ParagraphNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Credentials[3]/CredentialColumnsStack[1]/CredentialColumnsLayer[0]/CredentialColumns[0]/Achievements[1]/Achievement_0Stack[1]/Achievement_0Layer[0]/Achievement_0[0]/AchievementText_0[1]", + "childIndex" : 1, + "depth" : 13, + "layer" : 13, + "computedX" : 422.796, + "computedY" : 181.87, + "placementX" : 422.796, + "placementY" : 181.87, + "placementWidth" : 69.986, + "placementHeight" : 9.48, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 69.986, + "contentHeight" : 9.48, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 2.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Credentials[3]/CredentialColumnsStack[1]/CredentialColumnsLayer[0]/CredentialColumns[0]/Achievements[1]/Achievement_0Stack[1]/Achievement_0Layer[0]/Achievement_0[0]/AchievementText_0[1]/AchievementYear_0[2]", + "entityName" : "AchievementYear_0", + "entityKind" : "ParagraphNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Credentials[3]/CredentialColumnsStack[1]/CredentialColumnsLayer[0]/CredentialColumns[0]/Achievements[1]/Achievement_0Stack[1]/Achievement_0Layer[0]/Achievement_0[0]/AchievementText_0[1]", + "childIndex" : 2, + "depth" : 13, + "layer" : 13, + "computedX" : 422.796, + "computedY" : 170.39, + "placementX" : 422.796, + "placementY" : 170.39, + "placementWidth" : 18.328, + "placementHeight" : 9.48, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 18.328, + "contentHeight" : 9.48, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Credentials[3]/CredentialColumnsStack[1]/CredentialColumnsLayer[0]/CredentialColumns[0]/Achievements[1]/AchievementRule_0[2]", + "entityName" : "AchievementRule_0", + "entityKind" : "LineNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Credentials[3]/CredentialColumnsStack[1]/CredentialColumnsLayer[0]/CredentialColumns[0]/Achievements[1]", + "childIndex" : 2, + "depth" : 9, + "layer" : 9, + "computedX" : 399.407, + "computedY" : 162.89, + "placementX" : 399.407, + "placementY" : 162.89, + "placementWidth" : 167.069, + "placementHeight" : 1.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 167.069, + "contentHeight" : 1.0, + "margin" : { + "top" : 6.5, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Credentials[3]/CredentialColumnsStack[1]/CredentialColumnsLayer[0]/CredentialColumns[0]/Achievements[1]/Achievement_1Stack[3]", + "entityName" : "Achievement_1Stack", + "entityKind" : "LayerStackNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Credentials[3]/CredentialColumnsStack[1]/CredentialColumnsLayer[0]/CredentialColumns[0]/Achievements[1]", + "childIndex" : 3, + "depth" : 9, + "layer" : 9, + "computedX" : 399.407, + "computedY" : 124.07, + "placementX" : 399.407, + "placementY" : 124.07, + "placementWidth" : 167.069, + "placementHeight" : 32.32, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 167.069, + "contentHeight" : 32.32, + "margin" : { + "top" : 6.5, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Credentials[3]/CredentialColumnsStack[1]/CredentialColumnsLayer[0]/CredentialColumns[0]/Achievements[1]/Achievement_1Stack[3]/Achievement_1Layer[0]", + "entityName" : "Achievement_1Layer", + "entityKind" : "SectionNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Credentials[3]/CredentialColumnsStack[1]/CredentialColumnsLayer[0]/CredentialColumns[0]/Achievements[1]/Achievement_1Stack[3]", + "childIndex" : 0, + "depth" : 10, + "layer" : 10, + "computedX" : 399.407, + "computedY" : 124.07, + "placementX" : 399.407, + "placementY" : 124.07, + "placementWidth" : 167.069, + "placementHeight" : 32.32, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 167.069, + "contentHeight" : 32.32, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Credentials[3]/CredentialColumnsStack[1]/CredentialColumnsLayer[0]/CredentialColumns[0]/Achievements[1]/Achievement_1Stack[3]/Achievement_1Layer[0]/Achievement_1[0]", + "entityName" : "Achievement_1", + "entityKind" : "RowNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Credentials[3]/CredentialColumnsStack[1]/CredentialColumnsLayer[0]/CredentialColumns[0]/Achievements[1]/Achievement_1Stack[3]/Achievement_1Layer[0]", + "childIndex" : 0, + "depth" : 11, + "layer" : 11, + "computedX" : 399.407, + "computedY" : 124.07, + "placementX" : 399.407, + "placementY" : 124.07, + "placementWidth" : 167.069, + "placementHeight" : 32.32, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 167.069, + "contentHeight" : 32.32, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Credentials[3]/CredentialColumnsStack[1]/CredentialColumnsLayer[0]/CredentialColumns[0]/Achievements[1]/Achievement_1Stack[3]/Achievement_1Layer[0]/Achievement_1[0]/AchievementIcon_1[0]", + "entityName" : "AchievementIcon_1", + "entityKind" : "SectionNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Credentials[3]/CredentialColumnsStack[1]/CredentialColumnsLayer[0]/CredentialColumns[0]/Achievements[1]/Achievement_1Stack[3]/Achievement_1Layer[0]/Achievement_1[0]", + "childIndex" : 0, + "depth" : 12, + "layer" : 12, + "computedX" : 399.407, + "computedY" : 141.39, + "placementX" : 399.407, + "placementY" : 141.39, + "placementWidth" : 15.0, + "placementHeight" : 15.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 15.0, + "contentHeight" : 15.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Credentials[3]/CredentialColumnsStack[1]/CredentialColumnsLayer[0]/CredentialColumns[0]/Achievements[1]/Achievement_1Stack[3]/Achievement_1Layer[0]/Achievement_1[0]/AchievementIcon_1[0]/SvgIcon[0]", + "entityName" : "SvgIcon", + "entityKind" : "LayerStackNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Credentials[3]/CredentialColumnsStack[1]/CredentialColumnsLayer[0]/CredentialColumns[0]/Achievements[1]/Achievement_1Stack[3]/Achievement_1Layer[0]/Achievement_1[0]/AchievementIcon_1[0]", + "childIndex" : 0, + "depth" : 13, + "layer" : 13, + "computedX" : 399.407, + "computedY" : 141.39, + "placementX" : 399.407, + "placementY" : 141.39, + "placementWidth" : 15.0, + "placementHeight" : 15.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 15.0, + "contentHeight" : 15.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Credentials[3]/CredentialColumnsStack[1]/CredentialColumnsLayer[0]/CredentialColumns[0]/Achievements[1]/Achievement_1Stack[3]/Achievement_1Layer[0]/Achievement_1[0]/AchievementIcon_1[0]/SvgIcon[0]/SvgLayer0[0]", + "entityName" : "SvgLayer0", + "entityKind" : "Path", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Credentials[3]/CredentialColumnsStack[1]/CredentialColumnsLayer[0]/CredentialColumns[0]/Achievements[1]/Achievement_1Stack[3]/Achievement_1Layer[0]/Achievement_1[0]/AchievementIcon_1[0]/SvgIcon[0]", + "childIndex" : 0, + "depth" : 14, + "layer" : 14, + "computedX" : 399.407, + "computedY" : 141.39, + "placementX" : 399.407, + "placementY" : 141.39, + "placementWidth" : 15.0, + "placementHeight" : 15.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 15.0, + "contentHeight" : 15.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Credentials[3]/CredentialColumnsStack[1]/CredentialColumnsLayer[0]/CredentialColumns[0]/Achievements[1]/Achievement_1Stack[3]/Achievement_1Layer[0]/Achievement_1[0]/AchievementText_1[1]", + "entityName" : "AchievementText_1", + "entityKind" : "SectionNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Credentials[3]/CredentialColumnsStack[1]/CredentialColumnsLayer[0]/CredentialColumns[0]/Achievements[1]/Achievement_1Stack[3]/Achievement_1Layer[0]/Achievement_1[0]", + "childIndex" : 1, + "depth" : 12, + "layer" : 12, + "computedX" : 422.796, + "computedY" : 124.07, + "placementX" : 422.796, + "placementY" : 124.07, + "placementWidth" : 108.475, + "placementHeight" : 32.32, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 108.475, + "contentHeight" : 32.32, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Credentials[3]/CredentialColumnsStack[1]/CredentialColumnsLayer[0]/CredentialColumns[0]/Achievements[1]/Achievement_1Stack[3]/Achievement_1Layer[0]/Achievement_1[0]/AchievementText_1[1]/AchievementName_1[0]", + "entityName" : "AchievementName_1", + "entityKind" : "ParagraphNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Credentials[3]/CredentialColumnsStack[1]/CredentialColumnsLayer[0]/CredentialColumns[0]/Achievements[1]/Achievement_1Stack[3]/Achievement_1Layer[0]/Achievement_1[0]/AchievementText_1[1]", + "childIndex" : 0, + "depth" : 13, + "layer" : 13, + "computedX" : 422.796, + "computedY" : 147.03, + "placementX" : 422.796, + "placementY" : 147.03, + "placementWidth" : 108.475, + "placementHeight" : 9.36, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 108.475, + "contentHeight" : 9.36, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 2.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Credentials[3]/CredentialColumnsStack[1]/CredentialColumnsLayer[0]/CredentialColumns[0]/Achievements[1]/Achievement_1Stack[3]/Achievement_1Layer[0]/Achievement_1[0]/AchievementText_1[1]/AchievementIssuer_1[1]", + "entityName" : "AchievementIssuer_1", + "entityKind" : "ParagraphNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Credentials[3]/CredentialColumnsStack[1]/CredentialColumnsLayer[0]/CredentialColumns[0]/Achievements[1]/Achievement_1Stack[3]/Achievement_1Layer[0]/Achievement_1[0]/AchievementText_1[1]", + "childIndex" : 1, + "depth" : 13, + "layer" : 13, + "computedX" : 422.796, + "computedY" : 135.55, + "placementX" : 422.796, + "placementY" : 135.55, + "placementWidth" : 97.17, + "placementHeight" : 9.48, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 97.17, + "contentHeight" : 9.48, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 2.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Credentials[3]/CredentialColumnsStack[1]/CredentialColumnsLayer[0]/CredentialColumns[0]/Achievements[1]/Achievement_1Stack[3]/Achievement_1Layer[0]/Achievement_1[0]/AchievementText_1[1]/AchievementYear_1[2]", + "entityName" : "AchievementYear_1", + "entityKind" : "ParagraphNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Credentials[3]/CredentialColumnsStack[1]/CredentialColumnsLayer[0]/CredentialColumns[0]/Achievements[1]/Achievement_1Stack[3]/Achievement_1Layer[0]/Achievement_1[0]/AchievementText_1[1]", + "childIndex" : 2, + "depth" : 13, + "layer" : 13, + "computedX" : 422.796, + "computedY" : 124.07, + "placementX" : 422.796, + "placementY" : 124.07, + "placementWidth" : 18.328, + "placementHeight" : 9.48, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 18.328, + "contentHeight" : 9.48, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Credentials[3]/CredentialColumnsStack[1]/CredentialColumnsLayer[0]/CredentialColumns[0]/Achievements[1]/AchievementRule_1[4]", + "entityName" : "AchievementRule_1", + "entityKind" : "LineNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Credentials[3]/CredentialColumnsStack[1]/CredentialColumnsLayer[0]/CredentialColumns[0]/Achievements[1]", + "childIndex" : 4, + "depth" : 9, + "layer" : 9, + "computedX" : 399.407, + "computedY" : 116.57, + "placementX" : 399.407, + "placementY" : 116.57, + "placementWidth" : 167.069, + "placementHeight" : 1.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 167.069, + "contentHeight" : 1.0, + "margin" : { + "top" : 6.5, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Credentials[3]/CredentialColumnsStack[1]/CredentialColumnsLayer[0]/CredentialColumns[0]/Achievements[1]/Achievement_2Stack[5]", + "entityName" : "Achievement_2Stack", + "entityKind" : "LayerStackNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Credentials[3]/CredentialColumnsStack[1]/CredentialColumnsLayer[0]/CredentialColumns[0]/Achievements[1]", + "childIndex" : 5, + "depth" : 9, + "layer" : 9, + "computedX" : 399.407, + "computedY" : 77.75, + "placementX" : 399.407, + "placementY" : 77.75, + "placementWidth" : 167.069, + "placementHeight" : 32.32, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 167.069, + "contentHeight" : 32.32, + "margin" : { + "top" : 6.5, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Credentials[3]/CredentialColumnsStack[1]/CredentialColumnsLayer[0]/CredentialColumns[0]/Achievements[1]/Achievement_2Stack[5]/Achievement_2Layer[0]", + "entityName" : "Achievement_2Layer", + "entityKind" : "SectionNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Credentials[3]/CredentialColumnsStack[1]/CredentialColumnsLayer[0]/CredentialColumns[0]/Achievements[1]/Achievement_2Stack[5]", + "childIndex" : 0, + "depth" : 10, + "layer" : 10, + "computedX" : 399.407, + "computedY" : 77.75, + "placementX" : 399.407, + "placementY" : 77.75, + "placementWidth" : 167.069, + "placementHeight" : 32.32, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 167.069, + "contentHeight" : 32.32, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Credentials[3]/CredentialColumnsStack[1]/CredentialColumnsLayer[0]/CredentialColumns[0]/Achievements[1]/Achievement_2Stack[5]/Achievement_2Layer[0]/Achievement_2[0]", + "entityName" : "Achievement_2", + "entityKind" : "RowNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Credentials[3]/CredentialColumnsStack[1]/CredentialColumnsLayer[0]/CredentialColumns[0]/Achievements[1]/Achievement_2Stack[5]/Achievement_2Layer[0]", + "childIndex" : 0, + "depth" : 11, + "layer" : 11, + "computedX" : 399.407, + "computedY" : 77.75, + "placementX" : 399.407, + "placementY" : 77.75, + "placementWidth" : 167.069, + "placementHeight" : 32.32, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 167.069, + "contentHeight" : 32.32, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Credentials[3]/CredentialColumnsStack[1]/CredentialColumnsLayer[0]/CredentialColumns[0]/Achievements[1]/Achievement_2Stack[5]/Achievement_2Layer[0]/Achievement_2[0]/AchievementIcon_2[0]", + "entityName" : "AchievementIcon_2", + "entityKind" : "SectionNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Credentials[3]/CredentialColumnsStack[1]/CredentialColumnsLayer[0]/CredentialColumns[0]/Achievements[1]/Achievement_2Stack[5]/Achievement_2Layer[0]/Achievement_2[0]", + "childIndex" : 0, + "depth" : 12, + "layer" : 12, + "computedX" : 399.407, + "computedY" : 95.07, + "placementX" : 399.407, + "placementY" : 95.07, + "placementWidth" : 15.0, + "placementHeight" : 15.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 15.0, + "contentHeight" : 15.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Credentials[3]/CredentialColumnsStack[1]/CredentialColumnsLayer[0]/CredentialColumns[0]/Achievements[1]/Achievement_2Stack[5]/Achievement_2Layer[0]/Achievement_2[0]/AchievementIcon_2[0]/SvgIcon[0]", + "entityName" : "SvgIcon", + "entityKind" : "LayerStackNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Credentials[3]/CredentialColumnsStack[1]/CredentialColumnsLayer[0]/CredentialColumns[0]/Achievements[1]/Achievement_2Stack[5]/Achievement_2Layer[0]/Achievement_2[0]/AchievementIcon_2[0]", + "childIndex" : 0, + "depth" : 13, + "layer" : 13, + "computedX" : 399.407, + "computedY" : 95.07, + "placementX" : 399.407, + "placementY" : 95.07, + "placementWidth" : 15.0, + "placementHeight" : 15.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 15.0, + "contentHeight" : 15.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Credentials[3]/CredentialColumnsStack[1]/CredentialColumnsLayer[0]/CredentialColumns[0]/Achievements[1]/Achievement_2Stack[5]/Achievement_2Layer[0]/Achievement_2[0]/AchievementIcon_2[0]/SvgIcon[0]/SvgLayer0[0]", + "entityName" : "SvgLayer0", + "entityKind" : "Path", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Credentials[3]/CredentialColumnsStack[1]/CredentialColumnsLayer[0]/CredentialColumns[0]/Achievements[1]/Achievement_2Stack[5]/Achievement_2Layer[0]/Achievement_2[0]/AchievementIcon_2[0]/SvgIcon[0]", + "childIndex" : 0, + "depth" : 14, + "layer" : 14, + "computedX" : 399.407, + "computedY" : 95.07, + "placementX" : 399.407, + "placementY" : 95.07, + "placementWidth" : 15.0, + "placementHeight" : 15.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 15.0, + "contentHeight" : 15.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Credentials[3]/CredentialColumnsStack[1]/CredentialColumnsLayer[0]/CredentialColumns[0]/Achievements[1]/Achievement_2Stack[5]/Achievement_2Layer[0]/Achievement_2[0]/AchievementText_2[1]", + "entityName" : "AchievementText_2", + "entityKind" : "SectionNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Credentials[3]/CredentialColumnsStack[1]/CredentialColumnsLayer[0]/CredentialColumns[0]/Achievements[1]/Achievement_2Stack[5]/Achievement_2Layer[0]/Achievement_2[0]", + "childIndex" : 1, + "depth" : 12, + "layer" : 12, + "computedX" : 422.796, + "computedY" : 77.75, + "placementX" : 422.796, + "placementY" : 77.75, + "placementWidth" : 69.441, + "placementHeight" : 32.32, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 69.441, + "contentHeight" : 32.32, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Credentials[3]/CredentialColumnsStack[1]/CredentialColumnsLayer[0]/CredentialColumns[0]/Achievements[1]/Achievement_2Stack[5]/Achievement_2Layer[0]/Achievement_2[0]/AchievementText_2[1]/AchievementName_2[0]", + "entityName" : "AchievementName_2", + "entityKind" : "ParagraphNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Credentials[3]/CredentialColumnsStack[1]/CredentialColumnsLayer[0]/CredentialColumns[0]/Achievements[1]/Achievement_2Stack[5]/Achievement_2Layer[0]/Achievement_2[0]/AchievementText_2[1]", + "childIndex" : 0, + "depth" : 13, + "layer" : 13, + "computedX" : 422.796, + "computedY" : 100.71, + "placementX" : 422.796, + "placementY" : 100.71, + "placementWidth" : 51.199, + "placementHeight" : 9.36, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 51.199, + "contentHeight" : 9.36, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 2.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Credentials[3]/CredentialColumnsStack[1]/CredentialColumnsLayer[0]/CredentialColumns[0]/Achievements[1]/Achievement_2Stack[5]/Achievement_2Layer[0]/Achievement_2[0]/AchievementText_2[1]/AchievementIssuer_2[1]", + "entityName" : "AchievementIssuer_2", + "entityKind" : "ParagraphNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Credentials[3]/CredentialColumnsStack[1]/CredentialColumnsLayer[0]/CredentialColumns[0]/Achievements[1]/Achievement_2Stack[5]/Achievement_2Layer[0]/Achievement_2[0]/AchievementText_2[1]", + "childIndex" : 1, + "depth" : 13, + "layer" : 13, + "computedX" : 422.796, + "computedY" : 89.23, + "placementX" : 422.796, + "placementY" : 89.23, + "placementWidth" : 69.441, + "placementHeight" : 9.48, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 69.441, + "contentHeight" : 9.48, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 2.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Credentials[3]/CredentialColumnsStack[1]/CredentialColumnsLayer[0]/CredentialColumns[0]/Achievements[1]/Achievement_2Stack[5]/Achievement_2Layer[0]/Achievement_2[0]/AchievementText_2[1]/AchievementYear_2[2]", + "entityName" : "AchievementYear_2", + "entityKind" : "ParagraphNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/Credentials[3]/CredentialColumnsStack[1]/CredentialColumnsLayer[0]/CredentialColumns[0]/Achievements[1]/Achievement_2Stack[5]/Achievement_2Layer[0]/Achievement_2[0]/AchievementText_2[1]", + "childIndex" : 2, + "depth" : 13, + "layer" : 13, + "computedX" : 422.796, + "computedY" : 77.75, + "placementX" : 422.796, + "placementY" : 77.75, + "placementWidth" : 18.328, + "placementHeight" : 9.48, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 18.328, + "contentHeight" : 9.48, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/TechnicalTools[4]", + "entityName" : "TechnicalTools", + "entityKind" : "SectionNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]", + "childIndex" : 4, + "depth" : 4, + "layer" : 4, + "computedX" : 214.01, + "computedY" : 29.49, + "placementX" : 214.01, + "placementY" : 29.49, + "placementWidth" : 352.466, + "placementHeight" : 34.56, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 352.466, + "contentHeight" : 34.56, + "margin" : { + "top" : 13.7, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/TechnicalTools[4]/Heading_TECHNICALTOOLS[0]", + "entityName" : "Heading_TECHNICALTOOLS", + "entityKind" : "SectionNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/TechnicalTools[4]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 214.01, + "computedY" : 50.85, + "placementX" : 214.01, + "placementY" : 50.85, + "placementWidth" : 352.466, + "placementHeight" : 13.2, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 352.466, + "contentHeight" : 13.2, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 9.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/TechnicalTools[4]/Heading_TECHNICALTOOLS[0]/HeadingRow_TECHNICALTOOLSStack[0]", + "entityName" : "HeadingRow_TECHNICALTOOLSStack", + "entityKind" : "LayerStackNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/TechnicalTools[4]/Heading_TECHNICALTOOLS[0]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 223.01, + "computedY" : 50.85, + "placementX" : 223.01, + "placementY" : 50.85, + "placementWidth" : 343.466, + "placementHeight" : 13.2, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 343.466, + "contentHeight" : 13.2, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/TechnicalTools[4]/Heading_TECHNICALTOOLS[0]/HeadingRow_TECHNICALTOOLSStack[0]/HeadingRow_TECHNICALTOOLSLayer[0]", + "entityName" : "HeadingRow_TECHNICALTOOLSLayer", + "entityKind" : "SectionNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/TechnicalTools[4]/Heading_TECHNICALTOOLS[0]/HeadingRow_TECHNICALTOOLSStack[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 223.01, + "computedY" : 50.85, + "placementX" : 223.01, + "placementY" : 50.85, + "placementWidth" : 343.466, + "placementHeight" : 13.2, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 343.466, + "contentHeight" : 13.2, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/TechnicalTools[4]/Heading_TECHNICALTOOLS[0]/HeadingRow_TECHNICALTOOLSStack[0]/HeadingRow_TECHNICALTOOLSLayer[0]/HeadingRow_TECHNICALTOOLS[0]", + "entityName" : "HeadingRow_TECHNICALTOOLS", + "entityKind" : "RowNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/TechnicalTools[4]/Heading_TECHNICALTOOLS[0]/HeadingRow_TECHNICALTOOLSStack[0]/HeadingRow_TECHNICALTOOLSLayer[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 223.01, + "computedY" : 50.85, + "placementX" : 223.01, + "placementY" : 50.85, + "placementWidth" : 343.466, + "placementHeight" : 13.2, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 343.466, + "contentHeight" : 13.2, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/TechnicalTools[4]/Heading_TECHNICALTOOLS[0]/HeadingRow_TECHNICALTOOLSStack[0]/HeadingRow_TECHNICALTOOLSLayer[0]/HeadingRow_TECHNICALTOOLS[0]/HeadingText_TECHNICALTOOLS[0]", + "entityName" : "HeadingText_TECHNICALTOOLS", + "entityKind" : "ParagraphNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/TechnicalTools[4]/Heading_TECHNICALTOOLS[0]/HeadingRow_TECHNICALTOOLSStack[0]/HeadingRow_TECHNICALTOOLSLayer[0]/HeadingRow_TECHNICALTOOLS[0]", + "childIndex" : 0, + "depth" : 9, + "layer" : 9, + "computedX" : 223.01, + "computedY" : 50.85, + "placementX" : 223.01, + "placementY" : 50.85, + "placementWidth" : 99.11, + "placementHeight" : 13.2, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 99.11, + "contentHeight" : 13.2, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/TechnicalTools[4]/Heading_TECHNICALTOOLS[0]/HeadingRow_TECHNICALTOOLSStack[0]/HeadingRow_TECHNICALTOOLSLayer[0]/HeadingRow_TECHNICALTOOLS[0]/HeadingRule_TECHNICALTOOLS[1]", + "entityName" : "HeadingRule_TECHNICALTOOLS", + "entityKind" : "LineNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/TechnicalTools[4]/Heading_TECHNICALTOOLS[0]/HeadingRow_TECHNICALTOOLSStack[0]/HeadingRow_TECHNICALTOOLSLayer[0]/HeadingRow_TECHNICALTOOLS[0]", + "childIndex" : 1, + "depth" : 9, + "layer" : 9, + "computedX" : 331.12, + "computedY" : 56.95, + "placementX" : 331.12, + "placementY" : 56.95, + "placementWidth" : 235.356, + "placementHeight" : 1.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 235.356, + "contentHeight" : 1.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 9.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/TechnicalTools[4]/ToolsStrip[1]", + "entityName" : "ToolsStrip", + "entityKind" : "ParagraphNode", + "parentPath" : "CharcoalGoldCv[0]/Body[0]/MainColumn[1]/TechnicalTools[4]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 214.01, + "computedY" : 29.49, + "placementX" : 214.01, + "placementY" : 29.49, + "placementWidth" : 352.466, + "placementHeight" : 9.36, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 352.466, + "contentHeight" : 9.36, + "margin" : { + "top" : 12.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + } ] +} diff --git a/qa/src/test/resources/layout-snapshots/canonical-templates/cv-v2/midnight_navy_layout.json b/qa/src/test/resources/layout-snapshots/canonical-templates/cv-v2/midnight_navy_layout.json new file mode 100644 index 000000000..859b69735 --- /dev/null +++ b/qa/src/test/resources/layout-snapshots/canonical-templates/cv-v2/midnight_navy_layout.json @@ -0,0 +1,8117 @@ +{ + "formatVersion" : "2.0", + "canvas" : { + "pageWidth" : 595.276, + "pageHeight" : 841.89, + "innerWidth" : 595.276, + "innerHeight" : 841.89, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, + "totalPages" : 1, + "nodes" : [ { + "path" : "MidnightNavyCv[0]", + "entityName" : "MidnightNavyCv", + "entityKind" : "ContainerNode", + "parentPath" : null, + "childIndex" : 0, + "depth" : 1, + "layer" : 1, + "computedX" : 0.0, + "computedY" : 28.659, + "placementX" : 0.0, + "placementY" : 28.659, + "placementWidth" : 595.276, + "placementHeight" : 813.231, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 595.276, + "contentHeight" : 813.231, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]", + "entityName" : "PageGrid", + "entityKind" : "RowNode", + "parentPath" : "MidnightNavyCv[0]", + "childIndex" : 0, + "depth" : 2, + "layer" : 2, + "computedX" : 0.0, + "computedY" : 28.659, + "placementX" : 0.0, + "placementY" : 28.659, + "placementWidth" : 595.276, + "placementHeight" : 813.231, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 595.276, + "contentHeight" : 813.231, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]", + "entityName" : "Aside", + "entityKind" : "SectionNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]", + "childIndex" : 0, + "depth" : 3, + "layer" : 3, + "computedX" : 0.0, + "computedY" : 56.397, + "placementX" : 0.0, + "placementY" : 56.397, + "placementWidth" : 185.247, + "placementHeight" : 785.492, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 185.247, + "contentHeight" : 785.492, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 30.498, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Align[0]", + "entityName" : null, + "entityKind" : "Align", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 0.0, + "computedY" : 735.147, + "placementX" : 0.0, + "placementY" : 735.147, + "placementWidth" : 185.247, + "placementHeight" : 76.245, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 185.247, + "contentHeight" : 76.245, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Align[0]/Monogram[0]", + "entityName" : "Monogram", + "entityKind" : "ShapeContainerNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Align[0]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 54.501, + "computedY" : 735.147, + "placementX" : 54.501, + "placementY" : 735.147, + "placementWidth" : 76.245, + "placementHeight" : 76.245, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 76.245, + "contentHeight" : 76.245, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Align[0]/Monogram[0]/MonogramLine1[0]", + "entityName" : "MonogramLine1", + "entityKind" : "ParagraphNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Align[0]/Monogram[0]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 54.501, + "computedY" : 771.535, + "placementX" : 54.501, + "placementY" : 771.535, + "placementWidth" : 76.245, + "placementHeight" : 30.014, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 76.245, + "contentHeight" : 30.014, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Align[0]/Monogram[0]/MonogramLine2[1]", + "entityName" : "MonogramLine2", + "entityKind" : "ParagraphNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Align[0]/Monogram[0]", + "childIndex" : 1, + "depth" : 6, + "layer" : 6, + "computedX" : 54.501, + "computedY" : 744.99, + "placementX" : 54.501, + "placementY" : 744.99, + "placementWidth" : 76.245, + "placementHeight" : 30.014, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 76.245, + "contentHeight" : 30.014, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Name[1]", + "entityName" : "Name", + "entityKind" : "ParagraphNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 0.0, + "computedY" : 701.357, + "placementX" : 0.0, + "placementY" : 701.357, + "placementWidth" : 185.247, + "placementHeight" : 22.268, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 185.247, + "contentHeight" : 22.268, + "margin" : { + "top" : 11.521, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Role[2]", + "entityName" : "Role", + "entityKind" : "ParagraphNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]", + "childIndex" : 2, + "depth" : 4, + "layer" : 4, + "computedX" : 0.0, + "computedY" : 684.276, + "placementX" : 0.0, + "placementY" : 684.276, + "placementWidth" : 185.247, + "placementHeight" : 9.682, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 185.247, + "contentHeight" : 9.682, + "margin" : { + "top" : 7.399, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/NameDivider[3]", + "entityName" : "NameDivider", + "entityKind" : "LineNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]", + "childIndex" : 3, + "depth" : 4, + "layer" : 4, + "computedX" : 35.863, + "computedY" : 671.021, + "placementX" : 35.863, + "placementY" : 671.021, + "placementWidth" : 113.52, + "placementHeight" : 1.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 113.52, + "contentHeight" : 1.0, + "margin" : { + "top" : 12.256, + "right" : 0.0, + "bottom" : 0.0, + "left" : 35.863 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Contact[4]", + "entityName" : "Contact", + "entityKind" : "SectionNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]", + "childIndex" : 4, + "depth" : 4, + "layer" : 4, + "computedX" : 0.0, + "computedY" : 544.059, + "placementX" : 0.0, + "placementY" : 544.059, + "placementWidth" : 156.577, + "placementHeight" : 105.952, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 156.577, + "contentHeight" : 105.952, + "margin" : { + "top" : 21.01, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 24.285, + "bottom" : 0.0, + "left" : 29.368 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Contact[4]/AsideHeadingCONTACT[0]", + "entityName" : "AsideHeadingCONTACT", + "entityKind" : "ParagraphNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Contact[4]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 29.368, + "computedY" : 637.425, + "placementX" : 29.368, + "placementY" : 637.425, + "placementWidth" : 45.553, + "placementHeight" : 12.586, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 45.553, + "contentHeight" : 12.586, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Contact[4]/AsideRuleCONTACT[1]", + "entityName" : "AsideRuleCONTACT", + "entityKind" : "LineNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Contact[4]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 29.368, + "computedY" : 635.165, + "placementX" : 29.368, + "placementY" : 635.165, + "placementWidth" : 56.478, + "placementHeight" : 1.13, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 56.478, + "contentHeight" : 1.13, + "margin" : { + "top" : 1.13, + "right" : 0.0, + "bottom" : 12.199, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Contact[4]/ContactRows[2]", + "entityName" : "ContactRows", + "entityKind" : "SectionNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Contact[4]", + "childIndex" : 2, + "depth" : 5, + "layer" : 5, + "computedX" : 29.368, + "computedY" : 544.059, + "placementX" : 29.368, + "placementY" : 544.059, + "placementWidth" : 102.923, + "placementHeight" : 78.908, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 102.923, + "contentHeight" : 78.908, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Contact[4]/ContactRows[2]/Contact0[0]", + "entityName" : "Contact0", + "entityKind" : "ParagraphNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Contact[4]/ContactRows[2]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 29.368, + "computedY" : 613.284, + "placementX" : 29.368, + "placementY" : 613.284, + "placementWidth" : 74.353, + "placementHeight" : 9.682, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 74.353, + "contentHeight" : 9.682, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Contact[4]/ContactRows[2]/Contact1[1]", + "entityName" : "Contact1", + "entityKind" : "ParagraphNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Contact[4]/ContactRows[2]", + "childIndex" : 1, + "depth" : 6, + "layer" : 6, + "computedX" : 29.368, + "computedY" : 595.978, + "placementX" : 29.368, + "placementY" : 595.978, + "placementWidth" : 102.923, + "placementHeight" : 9.682, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 102.923, + "contentHeight" : 9.682, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Contact[4]/ContactRows[2]/Contact2[2]", + "entityName" : "Contact2", + "entityKind" : "ParagraphNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Contact[4]/ContactRows[2]", + "childIndex" : 2, + "depth" : 6, + "layer" : 6, + "computedX" : 29.368, + "computedY" : 578.672, + "placementX" : 29.368, + "placementY" : 578.672, + "placementWidth" : 82.058, + "placementHeight" : 9.682, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 82.058, + "contentHeight" : 9.682, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Contact[4]/ContactRows[2]/Contact3[3]", + "entityName" : "Contact3", + "entityKind" : "ParagraphNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Contact[4]/ContactRows[2]", + "childIndex" : 3, + "depth" : 6, + "layer" : 6, + "computedX" : 29.368, + "computedY" : 561.365, + "placementX" : 29.368, + "placementY" : 561.365, + "placementWidth" : 46.744, + "placementHeight" : 9.682, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 46.744, + "contentHeight" : 9.682, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Contact[4]/ContactRows[2]/Contact4[4]", + "entityName" : "Contact4", + "entityKind" : "ParagraphNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Contact[4]/ContactRows[2]", + "childIndex" : 4, + "depth" : 6, + "layer" : 6, + "computedX" : 29.368, + "computedY" : 544.059, + "placementX" : 29.368, + "placementY" : 544.059, + "placementWidth" : 47.195, + "placementHeight" : 9.682, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 47.195, + "contentHeight" : 9.682, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Education[5]", + "entityName" : "Education", + "entityKind" : "SectionNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]", + "childIndex" : 5, + "depth" : 4, + "layer" : 4, + "computedX" : 0.0, + "computedY" : 357.698, + "placementX" : 0.0, + "placementY" : 357.698, + "placementWidth" : 185.247, + "placementHeight" : 155.072, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 185.247, + "contentHeight" : 155.072, + "margin" : { + "top" : 31.289, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 24.285, + "bottom" : 0.0, + "left" : 29.368 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Education[5]/AsideHeadingEDUCATION[0]", + "entityName" : "AsideHeadingEDUCATION", + "entityKind" : "ParagraphNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Education[5]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 29.368, + "computedY" : 500.184, + "placementX" : 29.368, + "placementY" : 500.184, + "placementWidth" : 55.003, + "placementHeight" : 12.586, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 55.003, + "contentHeight" : 12.586, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Education[5]/AsideRuleEDUCATION[1]", + "entityName" : "AsideRuleEDUCATION", + "entityKind" : "LineNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Education[5]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 29.368, + "computedY" : 497.925, + "placementX" : 29.368, + "placementY" : 497.925, + "placementWidth" : 131.593, + "placementHeight" : 1.13, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 131.593, + "contentHeight" : 1.13, + "margin" : { + "top" : 1.13, + "right" : 0.0, + "bottom" : 10.392, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Education[5]/EducationEntries[2]", + "entityName" : "EducationEntries", + "entityKind" : "SectionNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Education[5]", + "childIndex" : 2, + "depth" : 5, + "layer" : 5, + "computedX" : 29.368, + "computedY" : 357.698, + "placementX" : 29.368, + "placementY" : 357.698, + "placementWidth" : 118.158, + "placementHeight" : 129.834, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 118.158, + "contentHeight" : 129.834, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Education[5]/EducationEntries[2]/EducationEntry[0]", + "entityName" : "EducationEntry", + "entityKind" : "SectionNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Education[5]/EducationEntries[2]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 29.368, + "computedY" : 425.948, + "placementX" : 29.368, + "placementY" : 425.948, + "placementWidth" : 118.158, + "placementHeight" : 61.585, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 118.158, + "contentHeight" : 61.585, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Education[5]/EducationEntries[2]/EducationEntry[0]/Degree[0]", + "entityName" : "Degree", + "entityKind" : "ParagraphNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Education[5]/EducationEntries[2]/EducationEntry[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 29.368, + "computedY" : 465.587, + "placementX" : 29.368, + "placementY" : 465.587, + "placementWidth" : 87.783, + "placementHeight" : 21.946, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 87.783, + "contentHeight" : 21.946, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Education[5]/EducationEntries[2]/EducationEntry[0]/Institution[1]", + "entityName" : "Institution", + "entityKind" : "ParagraphNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Education[5]/EducationEntries[2]/EducationEntry[0]", + "childIndex" : 1, + "depth" : 7, + "layer" : 7, + "computedX" : 29.368, + "computedY" : 443.093, + "placementX" : 29.368, + "placementY" : 443.093, + "placementWidth" : 118.158, + "placementHeight" : 18.88, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 118.158, + "contentHeight" : 18.88, + "margin" : { + "top" : 3.615, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Education[5]/EducationEntries[2]/EducationEntry[0]/Years[2]", + "entityName" : "Years", + "entityKind" : "ParagraphNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Education[5]/EducationEntries[2]/EducationEntry[0]", + "childIndex" : 2, + "depth" : 7, + "layer" : 7, + "computedX" : 29.368, + "computedY" : 425.948, + "placementX" : 29.368, + "placementY" : 425.948, + "placementWidth" : 42.494, + "placementHeight" : 10.65, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 42.494, + "contentHeight" : 10.65, + "margin" : { + "top" : 6.495, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Education[5]/EducationEntries[2]/EducationEntry[1]", + "entityName" : "EducationEntry", + "entityKind" : "SectionNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Education[5]/EducationEntries[2]", + "childIndex" : 1, + "depth" : 6, + "layer" : 6, + "computedX" : 29.368, + "computedY" : 357.698, + "placementX" : 29.368, + "placementY" : 357.698, + "placementWidth" : 102.365, + "placementHeight" : 51.419, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 102.365, + "contentHeight" : 51.419, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Education[5]/EducationEntries[2]/EducationEntry[1]/Degree[0]", + "entityName" : "Degree", + "entityKind" : "ParagraphNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Education[5]/EducationEntries[2]/EducationEntry[1]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 29.368, + "computedY" : 387.172, + "placementX" : 29.368, + "placementY" : 387.172, + "placementWidth" : 102.365, + "placementHeight" : 21.946, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 102.365, + "contentHeight" : 21.946, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Education[5]/EducationEntries[2]/EducationEntry[1]/Institution[1]", + "entityName" : "Institution", + "entityKind" : "ParagraphNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Education[5]/EducationEntries[2]/EducationEntry[1]", + "childIndex" : 1, + "depth" : 7, + "layer" : 7, + "computedX" : 29.368, + "computedY" : 374.843, + "placementX" : 29.368, + "placementY" : 374.843, + "placementWidth" : 55.739, + "placementHeight" : 8.714, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 55.739, + "contentHeight" : 8.714, + "margin" : { + "top" : 3.615, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Education[5]/EducationEntries[2]/EducationEntry[1]/Years[2]", + "entityName" : "Years", + "entityKind" : "ParagraphNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Education[5]/EducationEntries[2]/EducationEntry[1]", + "childIndex" : 2, + "depth" : 7, + "layer" : 7, + "computedX" : 29.368, + "computedY" : 357.698, + "placementX" : 29.368, + "placementY" : 357.698, + "placementWidth" : 42.627, + "placementHeight" : 10.65, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 42.627, + "contentHeight" : 10.65, + "margin" : { + "top" : 6.495, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]", + "entityName" : "Skills", + "entityKind" : "SectionNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]", + "childIndex" : 6, + "depth" : 4, + "layer" : 4, + "computedX" : 0.0, + "computedY" : 166.424, + "placementX" : 0.0, + "placementY" : 166.424, + "placementWidth" : 185.247, + "placementHeight" : 170.151, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 185.247, + "contentHeight" : 170.151, + "margin" : { + "top" : 21.123, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 24.285, + "bottom" : 0.0, + "left" : 29.368 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/AsideHeadingSKILLS[0]", + "entityName" : "AsideHeadingSKILLS", + "entityKind" : "ParagraphNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 29.368, + "computedY" : 323.989, + "placementX" : 29.368, + "placementY" : 323.989, + "placementWidth" : 33.805, + "placementHeight" : 12.586, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 33.805, + "contentHeight" : 12.586, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/AsideRuleSKILLS[1]", + "entityName" : "AsideRuleSKILLS", + "entityKind" : "LineNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 29.368, + "computedY" : 321.73, + "placementX" : 29.368, + "placementY" : 321.73, + "placementWidth" : 131.593, + "placementHeight" : 1.13, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 131.593, + "contentHeight" : 1.13, + "margin" : { + "top" : 1.13, + "right" : 0.0, + "bottom" : 10.731, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]", + "entityName" : "SkillRows", + "entityKind" : "SectionNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]", + "childIndex" : 2, + "depth" : 5, + "layer" : 5, + "computedX" : 29.368, + "computedY" : 166.424, + "placementX" : 29.368, + "placementY" : 166.424, + "placementWidth" : 131.593, + "placementHeight" : 144.575, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 131.593, + "contentHeight" : 144.575, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[0]", + "entityName" : "SkillRowLayer", + "entityKind" : "LayerStackNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 29.368, + "computedY" : 302.286, + "placementX" : 29.368, + "placementY" : 302.286, + "placementWidth" : 131.593, + "placementHeight" : 8.714, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 131.593, + "contentHeight" : 8.714, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[0]/SkillRowHolder[0]", + "entityName" : "SkillRowHolder", + "entityKind" : "SectionNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 29.368, + "computedY" : 302.286, + "placementX" : 29.368, + "placementY" : 302.286, + "placementWidth" : 131.593, + "placementHeight" : 8.714, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 131.593, + "contentHeight" : 8.714, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[0]/SkillRowHolder[0]/SkillRow[0]", + "entityName" : "SkillRow", + "entityKind" : "RowNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[0]/SkillRowHolder[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 29.368, + "computedY" : 302.286, + "placementX" : 29.368, + "placementY" : 302.286, + "placementWidth" : 131.593, + "placementHeight" : 8.714, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 131.593, + "contentHeight" : 8.714, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[0]/SkillRowHolder[0]/SkillRow[0]/SkillLabel[0]", + "entityName" : "SkillLabel", + "entityKind" : "ParagraphNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[0]/SkillRowHolder[0]/SkillRow[0]", + "childIndex" : 0, + "depth" : 9, + "layer" : 9, + "computedX" : 29.368, + "computedY" : 302.286, + "placementX" : 29.368, + "placementY" : 302.286, + "placementWidth" : 57.874, + "placementHeight" : 8.714, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 57.874, + "contentHeight" : 8.714, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[0]/SkillRowHolder[0]/SkillRow[0]/SkillMeterCell[1]", + "entityName" : "SkillMeterCell", + "entityKind" : "SectionNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[0]/SkillRowHolder[0]/SkillRow[0]", + "childIndex" : 1, + "depth" : 9, + "layer" : 9, + "computedX" : 100.53, + "computedY" : 304.948, + "placementX" : 100.53, + "placementY" : 304.948, + "placementWidth" : 60.431, + "placementHeight" : 3.389, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 60.431, + "contentHeight" : 3.389, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[0]/SkillRowHolder[0]/SkillRow[0]/SkillMeterCell[1]/SkillMeter[0]", + "entityName" : "SkillMeter", + "entityKind" : "LayerStackNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[0]/SkillRowHolder[0]/SkillRow[0]/SkillMeterCell[1]", + "childIndex" : 0, + "depth" : 10, + "layer" : 10, + "computedX" : 100.53, + "computedY" : 304.948, + "placementX" : 100.53, + "placementY" : 304.948, + "placementWidth" : 60.431, + "placementHeight" : 3.389, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 60.431, + "contentHeight" : 3.389, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[0]/SkillRowHolder[0]/SkillRow[0]/SkillMeterCell[1]/SkillMeter[0]/SkillTrack[0]", + "entityName" : "SkillTrack", + "entityKind" : "LineNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[0]/SkillRowHolder[0]/SkillRow[0]/SkillMeterCell[1]/SkillMeter[0]", + "childIndex" : 0, + "depth" : 11, + "layer" : 11, + "computedX" : 100.53, + "computedY" : 306.078, + "placementX" : 100.53, + "placementY" : 306.078, + "placementWidth" : 60.431, + "placementHeight" : 1.13, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 60.431, + "contentHeight" : 1.13, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[0]/SkillRowHolder[0]/SkillRow[0]/SkillMeterCell[1]/SkillMeter[0]/SkillTrackFilled[1]", + "entityName" : "SkillTrackFilled", + "entityKind" : "LineNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[0]/SkillRowHolder[0]/SkillRow[0]/SkillMeterCell[1]/SkillMeter[0]", + "childIndex" : 1, + "depth" : 11, + "layer" : 11, + "computedX" : 100.53, + "computedY" : 306.078, + "placementX" : 100.53, + "placementY" : 306.078, + "placementWidth" : 49.554, + "placementHeight" : 1.13, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 49.554, + "contentHeight" : 1.13, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[0]/SkillRowHolder[0]/SkillRow[0]/SkillMeterCell[1]/SkillMeter[0]/SkillKnob[2]", + "entityName" : "SkillKnob", + "entityKind" : "ShapeNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[0]/SkillRowHolder[0]/SkillRow[0]/SkillMeterCell[1]/SkillMeter[0]", + "childIndex" : 2, + "depth" : 11, + "layer" : 11, + "computedX" : 147.543, + "computedY" : 304.948, + "placementX" : 147.543, + "placementY" : 304.948, + "placementWidth" : 5.083, + "placementHeight" : 3.389, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 5.083, + "contentHeight" : 3.389, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[1]", + "entityName" : "SkillRowLayer", + "entityKind" : "LayerStackNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]", + "childIndex" : 1, + "depth" : 6, + "layer" : 6, + "computedX" : 29.368, + "computedY" : 287.19, + "placementX" : 29.368, + "placementY" : 287.19, + "placementWidth" : 131.593, + "placementHeight" : 8.714, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 131.593, + "contentHeight" : 8.714, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[1]/SkillRowHolder[0]", + "entityName" : "SkillRowHolder", + "entityKind" : "SectionNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[1]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 29.368, + "computedY" : 287.19, + "placementX" : 29.368, + "placementY" : 287.19, + "placementWidth" : 131.593, + "placementHeight" : 8.714, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 131.593, + "contentHeight" : 8.714, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[1]/SkillRowHolder[0]/SkillRow[0]", + "entityName" : "SkillRow", + "entityKind" : "RowNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[1]/SkillRowHolder[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 29.368, + "computedY" : 287.19, + "placementX" : 29.368, + "placementY" : 287.19, + "placementWidth" : 131.593, + "placementHeight" : 8.714, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 131.593, + "contentHeight" : 8.714, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[1]/SkillRowHolder[0]/SkillRow[0]/SkillLabel[0]", + "entityName" : "SkillLabel", + "entityKind" : "ParagraphNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[1]/SkillRowHolder[0]/SkillRow[0]", + "childIndex" : 0, + "depth" : 9, + "layer" : 9, + "computedX" : 29.368, + "computedY" : 287.19, + "placementX" : 29.368, + "placementY" : 287.19, + "placementWidth" : 52.899, + "placementHeight" : 8.714, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 52.899, + "contentHeight" : 8.714, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[1]/SkillRowHolder[0]/SkillRow[0]/SkillMeterCell[1]", + "entityName" : "SkillMeterCell", + "entityKind" : "SectionNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[1]/SkillRowHolder[0]/SkillRow[0]", + "childIndex" : 1, + "depth" : 9, + "layer" : 9, + "computedX" : 100.53, + "computedY" : 289.852, + "placementX" : 100.53, + "placementY" : 289.852, + "placementWidth" : 60.431, + "placementHeight" : 3.389, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 60.431, + "contentHeight" : 3.389, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[1]/SkillRowHolder[0]/SkillRow[0]/SkillMeterCell[1]/SkillMeter[0]", + "entityName" : "SkillMeter", + "entityKind" : "LayerStackNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[1]/SkillRowHolder[0]/SkillRow[0]/SkillMeterCell[1]", + "childIndex" : 0, + "depth" : 10, + "layer" : 10, + "computedX" : 100.53, + "computedY" : 289.852, + "placementX" : 100.53, + "placementY" : 289.852, + "placementWidth" : 60.431, + "placementHeight" : 3.389, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 60.431, + "contentHeight" : 3.389, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[1]/SkillRowHolder[0]/SkillRow[0]/SkillMeterCell[1]/SkillMeter[0]/SkillTrack[0]", + "entityName" : "SkillTrack", + "entityKind" : "LineNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[1]/SkillRowHolder[0]/SkillRow[0]/SkillMeterCell[1]/SkillMeter[0]", + "childIndex" : 0, + "depth" : 11, + "layer" : 11, + "computedX" : 100.53, + "computedY" : 290.982, + "placementX" : 100.53, + "placementY" : 290.982, + "placementWidth" : 60.431, + "placementHeight" : 1.13, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 60.431, + "contentHeight" : 1.13, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[1]/SkillRowHolder[0]/SkillRow[0]/SkillMeterCell[1]/SkillMeter[0]/SkillTrackFilled[1]", + "entityName" : "SkillTrackFilled", + "entityKind" : "LineNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[1]/SkillRowHolder[0]/SkillRow[0]/SkillMeterCell[1]/SkillMeter[0]", + "childIndex" : 1, + "depth" : 11, + "layer" : 11, + "computedX" : 100.53, + "computedY" : 290.982, + "placementX" : 100.53, + "placementY" : 290.982, + "placementWidth" : 50.762, + "placementHeight" : 1.13, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 50.762, + "contentHeight" : 1.13, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[1]/SkillRowHolder[0]/SkillRow[0]/SkillMeterCell[1]/SkillMeter[0]/SkillKnob[2]", + "entityName" : "SkillKnob", + "entityKind" : "ShapeNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[1]/SkillRowHolder[0]/SkillRow[0]/SkillMeterCell[1]/SkillMeter[0]", + "childIndex" : 2, + "depth" : 11, + "layer" : 11, + "computedX" : 148.751, + "computedY" : 289.852, + "placementX" : 148.751, + "placementY" : 289.852, + "placementWidth" : 5.083, + "placementHeight" : 3.389, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 5.083, + "contentHeight" : 3.389, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[2]", + "entityName" : "SkillRowLayer", + "entityKind" : "LayerStackNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]", + "childIndex" : 2, + "depth" : 6, + "layer" : 6, + "computedX" : 29.368, + "computedY" : 272.094, + "placementX" : 29.368, + "placementY" : 272.094, + "placementWidth" : 131.593, + "placementHeight" : 8.714, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 131.593, + "contentHeight" : 8.714, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[2]/SkillRowHolder[0]", + "entityName" : "SkillRowHolder", + "entityKind" : "SectionNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[2]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 29.368, + "computedY" : 272.094, + "placementX" : 29.368, + "placementY" : 272.094, + "placementWidth" : 131.593, + "placementHeight" : 8.714, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 131.593, + "contentHeight" : 8.714, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[2]/SkillRowHolder[0]/SkillRow[0]", + "entityName" : "SkillRow", + "entityKind" : "RowNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[2]/SkillRowHolder[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 29.368, + "computedY" : 272.094, + "placementX" : 29.368, + "placementY" : 272.094, + "placementWidth" : 131.593, + "placementHeight" : 8.714, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 131.593, + "contentHeight" : 8.714, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[2]/SkillRowHolder[0]/SkillRow[0]/SkillLabel[0]", + "entityName" : "SkillLabel", + "entityKind" : "ParagraphNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[2]/SkillRowHolder[0]/SkillRow[0]", + "childIndex" : 0, + "depth" : 9, + "layer" : 9, + "computedX" : 29.368, + "computedY" : 272.094, + "placementX" : 29.368, + "placementY" : 272.094, + "placementWidth" : 52.87, + "placementHeight" : 8.714, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 52.87, + "contentHeight" : 8.714, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[2]/SkillRowHolder[0]/SkillRow[0]/SkillMeterCell[1]", + "entityName" : "SkillMeterCell", + "entityKind" : "SectionNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[2]/SkillRowHolder[0]/SkillRow[0]", + "childIndex" : 1, + "depth" : 9, + "layer" : 9, + "computedX" : 100.53, + "computedY" : 274.757, + "placementX" : 100.53, + "placementY" : 274.757, + "placementWidth" : 60.431, + "placementHeight" : 3.389, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 60.431, + "contentHeight" : 3.389, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[2]/SkillRowHolder[0]/SkillRow[0]/SkillMeterCell[1]/SkillMeter[0]", + "entityName" : "SkillMeter", + "entityKind" : "LayerStackNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[2]/SkillRowHolder[0]/SkillRow[0]/SkillMeterCell[1]", + "childIndex" : 0, + "depth" : 10, + "layer" : 10, + "computedX" : 100.53, + "computedY" : 274.757, + "placementX" : 100.53, + "placementY" : 274.757, + "placementWidth" : 60.431, + "placementHeight" : 3.389, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 60.431, + "contentHeight" : 3.389, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[2]/SkillRowHolder[0]/SkillRow[0]/SkillMeterCell[1]/SkillMeter[0]/SkillTrack[0]", + "entityName" : "SkillTrack", + "entityKind" : "LineNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[2]/SkillRowHolder[0]/SkillRow[0]/SkillMeterCell[1]/SkillMeter[0]", + "childIndex" : 0, + "depth" : 11, + "layer" : 11, + "computedX" : 100.53, + "computedY" : 275.886, + "placementX" : 100.53, + "placementY" : 275.886, + "placementWidth" : 60.431, + "placementHeight" : 1.13, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 60.431, + "contentHeight" : 1.13, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[2]/SkillRowHolder[0]/SkillRow[0]/SkillMeterCell[1]/SkillMeter[0]/SkillTrackFilled[1]", + "entityName" : "SkillTrackFilled", + "entityKind" : "LineNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[2]/SkillRowHolder[0]/SkillRow[0]/SkillMeterCell[1]/SkillMeter[0]", + "childIndex" : 1, + "depth" : 11, + "layer" : 11, + "computedX" : 100.53, + "computedY" : 275.886, + "placementX" : 100.53, + "placementY" : 275.886, + "placementWidth" : 45.928, + "placementHeight" : 1.13, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 45.928, + "contentHeight" : 1.13, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[2]/SkillRowHolder[0]/SkillRow[0]/SkillMeterCell[1]/SkillMeter[0]/SkillKnob[2]", + "entityName" : "SkillKnob", + "entityKind" : "ShapeNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[2]/SkillRowHolder[0]/SkillRow[0]/SkillMeterCell[1]/SkillMeter[0]", + "childIndex" : 2, + "depth" : 11, + "layer" : 11, + "computedX" : 143.917, + "computedY" : 274.757, + "placementX" : 143.917, + "placementY" : 274.757, + "placementWidth" : 5.083, + "placementHeight" : 3.389, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 5.083, + "contentHeight" : 3.389, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[3]", + "entityName" : "SkillRowLayer", + "entityKind" : "LayerStackNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]", + "childIndex" : 3, + "depth" : 6, + "layer" : 6, + "computedX" : 29.368, + "computedY" : 256.998, + "placementX" : 29.368, + "placementY" : 256.998, + "placementWidth" : 131.593, + "placementHeight" : 8.714, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 131.593, + "contentHeight" : 8.714, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[3]/SkillRowHolder[0]", + "entityName" : "SkillRowHolder", + "entityKind" : "SectionNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[3]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 29.368, + "computedY" : 256.998, + "placementX" : 29.368, + "placementY" : 256.998, + "placementWidth" : 131.593, + "placementHeight" : 8.714, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 131.593, + "contentHeight" : 8.714, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[3]/SkillRowHolder[0]/SkillRow[0]", + "entityName" : "SkillRow", + "entityKind" : "RowNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[3]/SkillRowHolder[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 29.368, + "computedY" : 256.998, + "placementX" : 29.368, + "placementY" : 256.998, + "placementWidth" : 131.593, + "placementHeight" : 8.714, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 131.593, + "contentHeight" : 8.714, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[3]/SkillRowHolder[0]/SkillRow[0]/SkillLabel[0]", + "entityName" : "SkillLabel", + "entityKind" : "ParagraphNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[3]/SkillRowHolder[0]/SkillRow[0]", + "childIndex" : 0, + "depth" : 9, + "layer" : 9, + "computedX" : 29.368, + "computedY" : 256.998, + "placementX" : 29.368, + "placementY" : 256.998, + "placementWidth" : 60.952, + "placementHeight" : 8.714, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 60.952, + "contentHeight" : 8.714, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[3]/SkillRowHolder[0]/SkillRow[0]/SkillMeterCell[1]", + "entityName" : "SkillMeterCell", + "entityKind" : "SectionNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[3]/SkillRowHolder[0]/SkillRow[0]", + "childIndex" : 1, + "depth" : 9, + "layer" : 9, + "computedX" : 100.53, + "computedY" : 259.661, + "placementX" : 100.53, + "placementY" : 259.661, + "placementWidth" : 60.431, + "placementHeight" : 3.389, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 60.431, + "contentHeight" : 3.389, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[3]/SkillRowHolder[0]/SkillRow[0]/SkillMeterCell[1]/SkillMeter[0]", + "entityName" : "SkillMeter", + "entityKind" : "LayerStackNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[3]/SkillRowHolder[0]/SkillRow[0]/SkillMeterCell[1]", + "childIndex" : 0, + "depth" : 10, + "layer" : 10, + "computedX" : 100.53, + "computedY" : 259.661, + "placementX" : 100.53, + "placementY" : 259.661, + "placementWidth" : 60.431, + "placementHeight" : 3.389, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 60.431, + "contentHeight" : 3.389, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[3]/SkillRowHolder[0]/SkillRow[0]/SkillMeterCell[1]/SkillMeter[0]/SkillTrack[0]", + "entityName" : "SkillTrack", + "entityKind" : "LineNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[3]/SkillRowHolder[0]/SkillRow[0]/SkillMeterCell[1]/SkillMeter[0]", + "childIndex" : 0, + "depth" : 11, + "layer" : 11, + "computedX" : 100.53, + "computedY" : 260.791, + "placementX" : 100.53, + "placementY" : 260.791, + "placementWidth" : 60.431, + "placementHeight" : 1.13, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 60.431, + "contentHeight" : 1.13, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[3]/SkillRowHolder[0]/SkillRow[0]/SkillMeterCell[1]/SkillMeter[0]/SkillTrackFilled[1]", + "entityName" : "SkillTrackFilled", + "entityKind" : "LineNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[3]/SkillRowHolder[0]/SkillRow[0]/SkillMeterCell[1]/SkillMeter[0]", + "childIndex" : 1, + "depth" : 11, + "layer" : 11, + "computedX" : 100.53, + "computedY" : 260.791, + "placementX" : 100.53, + "placementY" : 260.791, + "placementWidth" : 50.762, + "placementHeight" : 1.13, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 50.762, + "contentHeight" : 1.13, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[3]/SkillRowHolder[0]/SkillRow[0]/SkillMeterCell[1]/SkillMeter[0]/SkillKnob[2]", + "entityName" : "SkillKnob", + "entityKind" : "ShapeNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[3]/SkillRowHolder[0]/SkillRow[0]/SkillMeterCell[1]/SkillMeter[0]", + "childIndex" : 2, + "depth" : 11, + "layer" : 11, + "computedX" : 148.751, + "computedY" : 259.661, + "placementX" : 148.751, + "placementY" : 259.661, + "placementWidth" : 5.083, + "placementHeight" : 3.389, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 5.083, + "contentHeight" : 3.389, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[4]", + "entityName" : "SkillRowLayer", + "entityKind" : "LayerStackNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]", + "childIndex" : 4, + "depth" : 6, + "layer" : 6, + "computedX" : 29.368, + "computedY" : 241.903, + "placementX" : 29.368, + "placementY" : 241.903, + "placementWidth" : 131.593, + "placementHeight" : 8.714, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 131.593, + "contentHeight" : 8.714, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[4]/SkillRowHolder[0]", + "entityName" : "SkillRowHolder", + "entityKind" : "SectionNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[4]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 29.368, + "computedY" : 241.903, + "placementX" : 29.368, + "placementY" : 241.903, + "placementWidth" : 131.593, + "placementHeight" : 8.714, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 131.593, + "contentHeight" : 8.714, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[4]/SkillRowHolder[0]/SkillRow[0]", + "entityName" : "SkillRow", + "entityKind" : "RowNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[4]/SkillRowHolder[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 29.368, + "computedY" : 241.903, + "placementX" : 29.368, + "placementY" : 241.903, + "placementWidth" : 131.593, + "placementHeight" : 8.714, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 131.593, + "contentHeight" : 8.714, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[4]/SkillRowHolder[0]/SkillRow[0]/SkillLabel[0]", + "entityName" : "SkillLabel", + "entityKind" : "ParagraphNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[4]/SkillRowHolder[0]/SkillRow[0]", + "childIndex" : 0, + "depth" : 9, + "layer" : 9, + "computedX" : 29.368, + "computedY" : 241.903, + "placementX" : 29.368, + "placementY" : 241.903, + "placementWidth" : 41.891, + "placementHeight" : 8.714, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 41.891, + "contentHeight" : 8.714, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[4]/SkillRowHolder[0]/SkillRow[0]/SkillMeterCell[1]", + "entityName" : "SkillMeterCell", + "entityKind" : "SectionNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[4]/SkillRowHolder[0]/SkillRow[0]", + "childIndex" : 1, + "depth" : 9, + "layer" : 9, + "computedX" : 100.53, + "computedY" : 244.565, + "placementX" : 100.53, + "placementY" : 244.565, + "placementWidth" : 60.431, + "placementHeight" : 3.389, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 60.431, + "contentHeight" : 3.389, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[4]/SkillRowHolder[0]/SkillRow[0]/SkillMeterCell[1]/SkillMeter[0]", + "entityName" : "SkillMeter", + "entityKind" : "LayerStackNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[4]/SkillRowHolder[0]/SkillRow[0]/SkillMeterCell[1]", + "childIndex" : 0, + "depth" : 10, + "layer" : 10, + "computedX" : 100.53, + "computedY" : 244.565, + "placementX" : 100.53, + "placementY" : 244.565, + "placementWidth" : 60.431, + "placementHeight" : 3.389, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 60.431, + "contentHeight" : 3.389, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[4]/SkillRowHolder[0]/SkillRow[0]/SkillMeterCell[1]/SkillMeter[0]/SkillTrack[0]", + "entityName" : "SkillTrack", + "entityKind" : "LineNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[4]/SkillRowHolder[0]/SkillRow[0]/SkillMeterCell[1]/SkillMeter[0]", + "childIndex" : 0, + "depth" : 11, + "layer" : 11, + "computedX" : 100.53, + "computedY" : 245.695, + "placementX" : 100.53, + "placementY" : 245.695, + "placementWidth" : 60.431, + "placementHeight" : 1.13, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 60.431, + "contentHeight" : 1.13, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[4]/SkillRowHolder[0]/SkillRow[0]/SkillMeterCell[1]/SkillMeter[0]/SkillTrackFilled[1]", + "entityName" : "SkillTrackFilled", + "entityKind" : "LineNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[4]/SkillRowHolder[0]/SkillRow[0]/SkillMeterCell[1]/SkillMeter[0]", + "childIndex" : 1, + "depth" : 11, + "layer" : 11, + "computedX" : 100.53, + "computedY" : 245.695, + "placementX" : 100.53, + "placementY" : 245.695, + "placementWidth" : 48.949, + "placementHeight" : 1.13, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 48.949, + "contentHeight" : 1.13, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[4]/SkillRowHolder[0]/SkillRow[0]/SkillMeterCell[1]/SkillMeter[0]/SkillKnob[2]", + "entityName" : "SkillKnob", + "entityKind" : "ShapeNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[4]/SkillRowHolder[0]/SkillRow[0]/SkillMeterCell[1]/SkillMeter[0]", + "childIndex" : 2, + "depth" : 11, + "layer" : 11, + "computedX" : 146.938, + "computedY" : 244.565, + "placementX" : 146.938, + "placementY" : 244.565, + "placementWidth" : 5.083, + "placementHeight" : 3.389, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 5.083, + "contentHeight" : 3.389, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[5]", + "entityName" : "SkillRowLayer", + "entityKind" : "LayerStackNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]", + "childIndex" : 5, + "depth" : 6, + "layer" : 6, + "computedX" : 29.368, + "computedY" : 226.807, + "placementX" : 29.368, + "placementY" : 226.807, + "placementWidth" : 131.593, + "placementHeight" : 8.714, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 131.593, + "contentHeight" : 8.714, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[5]/SkillRowHolder[0]", + "entityName" : "SkillRowHolder", + "entityKind" : "SectionNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[5]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 29.368, + "computedY" : 226.807, + "placementX" : 29.368, + "placementY" : 226.807, + "placementWidth" : 131.593, + "placementHeight" : 8.714, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 131.593, + "contentHeight" : 8.714, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[5]/SkillRowHolder[0]/SkillRow[0]", + "entityName" : "SkillRow", + "entityKind" : "RowNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[5]/SkillRowHolder[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 29.368, + "computedY" : 226.807, + "placementX" : 29.368, + "placementY" : 226.807, + "placementWidth" : 131.593, + "placementHeight" : 8.714, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 131.593, + "contentHeight" : 8.714, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[5]/SkillRowHolder[0]/SkillRow[0]/SkillLabel[0]", + "entityName" : "SkillLabel", + "entityKind" : "ParagraphNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[5]/SkillRowHolder[0]/SkillRow[0]", + "childIndex" : 0, + "depth" : 9, + "layer" : 9, + "computedX" : 29.368, + "computedY" : 226.807, + "placementX" : 29.368, + "placementY" : 226.807, + "placementWidth" : 65.135, + "placementHeight" : 8.714, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 65.135, + "contentHeight" : 8.714, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[5]/SkillRowHolder[0]/SkillRow[0]/SkillMeterCell[1]", + "entityName" : "SkillMeterCell", + "entityKind" : "SectionNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[5]/SkillRowHolder[0]/SkillRow[0]", + "childIndex" : 1, + "depth" : 9, + "layer" : 9, + "computedX" : 100.53, + "computedY" : 229.47, + "placementX" : 100.53, + "placementY" : 229.47, + "placementWidth" : 60.431, + "placementHeight" : 3.389, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 60.431, + "contentHeight" : 3.389, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[5]/SkillRowHolder[0]/SkillRow[0]/SkillMeterCell[1]/SkillMeter[0]", + "entityName" : "SkillMeter", + "entityKind" : "LayerStackNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[5]/SkillRowHolder[0]/SkillRow[0]/SkillMeterCell[1]", + "childIndex" : 0, + "depth" : 10, + "layer" : 10, + "computedX" : 100.53, + "computedY" : 229.47, + "placementX" : 100.53, + "placementY" : 229.47, + "placementWidth" : 60.431, + "placementHeight" : 3.389, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 60.431, + "contentHeight" : 3.389, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[5]/SkillRowHolder[0]/SkillRow[0]/SkillMeterCell[1]/SkillMeter[0]/SkillTrack[0]", + "entityName" : "SkillTrack", + "entityKind" : "LineNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[5]/SkillRowHolder[0]/SkillRow[0]/SkillMeterCell[1]/SkillMeter[0]", + "childIndex" : 0, + "depth" : 11, + "layer" : 11, + "computedX" : 100.53, + "computedY" : 230.599, + "placementX" : 100.53, + "placementY" : 230.599, + "placementWidth" : 60.431, + "placementHeight" : 1.13, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 60.431, + "contentHeight" : 1.13, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[5]/SkillRowHolder[0]/SkillRow[0]/SkillMeterCell[1]/SkillMeter[0]/SkillTrackFilled[1]", + "entityName" : "SkillTrackFilled", + "entityKind" : "LineNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[5]/SkillRowHolder[0]/SkillRow[0]/SkillMeterCell[1]/SkillMeter[0]", + "childIndex" : 1, + "depth" : 11, + "layer" : 11, + "computedX" : 100.53, + "computedY" : 230.599, + "placementX" : 100.53, + "placementY" : 230.599, + "placementWidth" : 41.093, + "placementHeight" : 1.13, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 41.093, + "contentHeight" : 1.13, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[5]/SkillRowHolder[0]/SkillRow[0]/SkillMeterCell[1]/SkillMeter[0]/SkillKnob[2]", + "entityName" : "SkillKnob", + "entityKind" : "ShapeNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[5]/SkillRowHolder[0]/SkillRow[0]/SkillMeterCell[1]/SkillMeter[0]", + "childIndex" : 2, + "depth" : 11, + "layer" : 11, + "computedX" : 139.082, + "computedY" : 229.47, + "placementX" : 139.082, + "placementY" : 229.47, + "placementWidth" : 5.083, + "placementHeight" : 3.389, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 5.083, + "contentHeight" : 3.389, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[6]", + "entityName" : "SkillRowLayer", + "entityKind" : "LayerStackNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]", + "childIndex" : 6, + "depth" : 6, + "layer" : 6, + "computedX" : 29.368, + "computedY" : 211.711, + "placementX" : 29.368, + "placementY" : 211.711, + "placementWidth" : 131.593, + "placementHeight" : 8.714, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 131.593, + "contentHeight" : 8.714, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[6]/SkillRowHolder[0]", + "entityName" : "SkillRowHolder", + "entityKind" : "SectionNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[6]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 29.368, + "computedY" : 211.711, + "placementX" : 29.368, + "placementY" : 211.711, + "placementWidth" : 131.593, + "placementHeight" : 8.714, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 131.593, + "contentHeight" : 8.714, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[6]/SkillRowHolder[0]/SkillRow[0]", + "entityName" : "SkillRow", + "entityKind" : "RowNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[6]/SkillRowHolder[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 29.368, + "computedY" : 211.711, + "placementX" : 29.368, + "placementY" : 211.711, + "placementWidth" : 131.593, + "placementHeight" : 8.714, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 131.593, + "contentHeight" : 8.714, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[6]/SkillRowHolder[0]/SkillRow[0]/SkillLabel[0]", + "entityName" : "SkillLabel", + "entityKind" : "ParagraphNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[6]/SkillRowHolder[0]/SkillRow[0]", + "childIndex" : 0, + "depth" : 9, + "layer" : 9, + "computedX" : 29.368, + "computedY" : 211.711, + "placementX" : 29.368, + "placementY" : 211.711, + "placementWidth" : 52.057, + "placementHeight" : 8.714, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 52.057, + "contentHeight" : 8.714, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[6]/SkillRowHolder[0]/SkillRow[0]/SkillMeterCell[1]", + "entityName" : "SkillMeterCell", + "entityKind" : "SectionNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[6]/SkillRowHolder[0]/SkillRow[0]", + "childIndex" : 1, + "depth" : 9, + "layer" : 9, + "computedX" : 100.53, + "computedY" : 214.374, + "placementX" : 100.53, + "placementY" : 214.374, + "placementWidth" : 60.431, + "placementHeight" : 3.389, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 60.431, + "contentHeight" : 3.389, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[6]/SkillRowHolder[0]/SkillRow[0]/SkillMeterCell[1]/SkillMeter[0]", + "entityName" : "SkillMeter", + "entityKind" : "LayerStackNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[6]/SkillRowHolder[0]/SkillRow[0]/SkillMeterCell[1]", + "childIndex" : 0, + "depth" : 10, + "layer" : 10, + "computedX" : 100.53, + "computedY" : 214.374, + "placementX" : 100.53, + "placementY" : 214.374, + "placementWidth" : 60.431, + "placementHeight" : 3.389, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 60.431, + "contentHeight" : 3.389, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[6]/SkillRowHolder[0]/SkillRow[0]/SkillMeterCell[1]/SkillMeter[0]/SkillTrack[0]", + "entityName" : "SkillTrack", + "entityKind" : "LineNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[6]/SkillRowHolder[0]/SkillRow[0]/SkillMeterCell[1]/SkillMeter[0]", + "childIndex" : 0, + "depth" : 11, + "layer" : 11, + "computedX" : 100.53, + "computedY" : 215.503, + "placementX" : 100.53, + "placementY" : 215.503, + "placementWidth" : 60.431, + "placementHeight" : 1.13, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 60.431, + "contentHeight" : 1.13, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[6]/SkillRowHolder[0]/SkillRow[0]/SkillMeterCell[1]/SkillMeter[0]/SkillTrackFilled[1]", + "entityName" : "SkillTrackFilled", + "entityKind" : "LineNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[6]/SkillRowHolder[0]/SkillRow[0]/SkillMeterCell[1]/SkillMeter[0]", + "childIndex" : 1, + "depth" : 11, + "layer" : 11, + "computedX" : 100.53, + "computedY" : 215.503, + "placementX" : 100.53, + "placementY" : 215.503, + "placementWidth" : 50.158, + "placementHeight" : 1.13, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 50.158, + "contentHeight" : 1.13, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[6]/SkillRowHolder[0]/SkillRow[0]/SkillMeterCell[1]/SkillMeter[0]/SkillKnob[2]", + "entityName" : "SkillKnob", + "entityKind" : "ShapeNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[6]/SkillRowHolder[0]/SkillRow[0]/SkillMeterCell[1]/SkillMeter[0]", + "childIndex" : 2, + "depth" : 11, + "layer" : 11, + "computedX" : 148.147, + "computedY" : 214.374, + "placementX" : 148.147, + "placementY" : 214.374, + "placementWidth" : 5.083, + "placementHeight" : 3.389, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 5.083, + "contentHeight" : 3.389, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[7]", + "entityName" : "SkillRowLayer", + "entityKind" : "LayerStackNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]", + "childIndex" : 7, + "depth" : 6, + "layer" : 6, + "computedX" : 29.368, + "computedY" : 196.616, + "placementX" : 29.368, + "placementY" : 196.616, + "placementWidth" : 131.593, + "placementHeight" : 8.714, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 131.593, + "contentHeight" : 8.714, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[7]/SkillRowHolder[0]", + "entityName" : "SkillRowHolder", + "entityKind" : "SectionNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[7]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 29.368, + "computedY" : 196.616, + "placementX" : 29.368, + "placementY" : 196.616, + "placementWidth" : 131.593, + "placementHeight" : 8.714, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 131.593, + "contentHeight" : 8.714, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[7]/SkillRowHolder[0]/SkillRow[0]", + "entityName" : "SkillRow", + "entityKind" : "RowNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[7]/SkillRowHolder[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 29.368, + "computedY" : 196.616, + "placementX" : 29.368, + "placementY" : 196.616, + "placementWidth" : 131.593, + "placementHeight" : 8.714, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 131.593, + "contentHeight" : 8.714, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[7]/SkillRowHolder[0]/SkillRow[0]/SkillLabel[0]", + "entityName" : "SkillLabel", + "entityKind" : "ParagraphNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[7]/SkillRowHolder[0]/SkillRow[0]", + "childIndex" : 0, + "depth" : 9, + "layer" : 9, + "computedX" : 29.368, + "computedY" : 196.616, + "placementX" : 29.368, + "placementY" : 196.616, + "placementWidth" : 32.785, + "placementHeight" : 8.714, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 32.785, + "contentHeight" : 8.714, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[7]/SkillRowHolder[0]/SkillRow[0]/SkillMeterCell[1]", + "entityName" : "SkillMeterCell", + "entityKind" : "SectionNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[7]/SkillRowHolder[0]/SkillRow[0]", + "childIndex" : 1, + "depth" : 9, + "layer" : 9, + "computedX" : 100.53, + "computedY" : 199.278, + "placementX" : 100.53, + "placementY" : 199.278, + "placementWidth" : 60.431, + "placementHeight" : 3.389, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 60.431, + "contentHeight" : 3.389, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[7]/SkillRowHolder[0]/SkillRow[0]/SkillMeterCell[1]/SkillMeter[0]", + "entityName" : "SkillMeter", + "entityKind" : "LayerStackNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[7]/SkillRowHolder[0]/SkillRow[0]/SkillMeterCell[1]", + "childIndex" : 0, + "depth" : 10, + "layer" : 10, + "computedX" : 100.53, + "computedY" : 199.278, + "placementX" : 100.53, + "placementY" : 199.278, + "placementWidth" : 60.431, + "placementHeight" : 3.389, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 60.431, + "contentHeight" : 3.389, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[7]/SkillRowHolder[0]/SkillRow[0]/SkillMeterCell[1]/SkillMeter[0]/SkillTrack[0]", + "entityName" : "SkillTrack", + "entityKind" : "LineNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[7]/SkillRowHolder[0]/SkillRow[0]/SkillMeterCell[1]/SkillMeter[0]", + "childIndex" : 0, + "depth" : 11, + "layer" : 11, + "computedX" : 100.53, + "computedY" : 200.408, + "placementX" : 100.53, + "placementY" : 200.408, + "placementWidth" : 60.431, + "placementHeight" : 1.13, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 60.431, + "contentHeight" : 1.13, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[7]/SkillRowHolder[0]/SkillRow[0]/SkillMeterCell[1]/SkillMeter[0]/SkillTrackFilled[1]", + "entityName" : "SkillTrackFilled", + "entityKind" : "LineNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[7]/SkillRowHolder[0]/SkillRow[0]/SkillMeterCell[1]/SkillMeter[0]", + "childIndex" : 1, + "depth" : 11, + "layer" : 11, + "computedX" : 100.53, + "computedY" : 200.408, + "placementX" : 100.53, + "placementY" : 200.408, + "placementWidth" : 45.928, + "placementHeight" : 1.13, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 45.928, + "contentHeight" : 1.13, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[7]/SkillRowHolder[0]/SkillRow[0]/SkillMeterCell[1]/SkillMeter[0]/SkillKnob[2]", + "entityName" : "SkillKnob", + "entityKind" : "ShapeNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[7]/SkillRowHolder[0]/SkillRow[0]/SkillMeterCell[1]/SkillMeter[0]", + "childIndex" : 2, + "depth" : 11, + "layer" : 11, + "computedX" : 143.917, + "computedY" : 199.278, + "placementX" : 143.917, + "placementY" : 199.278, + "placementWidth" : 5.083, + "placementHeight" : 3.389, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 5.083, + "contentHeight" : 3.389, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[8]", + "entityName" : "SkillRowLayer", + "entityKind" : "LayerStackNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]", + "childIndex" : 8, + "depth" : 6, + "layer" : 6, + "computedX" : 29.368, + "computedY" : 181.52, + "placementX" : 29.368, + "placementY" : 181.52, + "placementWidth" : 131.593, + "placementHeight" : 8.714, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 131.593, + "contentHeight" : 8.714, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[8]/SkillRowHolder[0]", + "entityName" : "SkillRowHolder", + "entityKind" : "SectionNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[8]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 29.368, + "computedY" : 181.52, + "placementX" : 29.368, + "placementY" : 181.52, + "placementWidth" : 131.593, + "placementHeight" : 8.714, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 131.593, + "contentHeight" : 8.714, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[8]/SkillRowHolder[0]/SkillRow[0]", + "entityName" : "SkillRow", + "entityKind" : "RowNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[8]/SkillRowHolder[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 29.368, + "computedY" : 181.52, + "placementX" : 29.368, + "placementY" : 181.52, + "placementWidth" : 131.593, + "placementHeight" : 8.714, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 131.593, + "contentHeight" : 8.714, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[8]/SkillRowHolder[0]/SkillRow[0]/SkillLabel[0]", + "entityName" : "SkillLabel", + "entityKind" : "ParagraphNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[8]/SkillRowHolder[0]/SkillRow[0]", + "childIndex" : 0, + "depth" : 9, + "layer" : 9, + "computedX" : 29.368, + "computedY" : 181.52, + "placementX" : 29.368, + "placementY" : 181.52, + "placementWidth" : 68.686, + "placementHeight" : 8.714, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 68.686, + "contentHeight" : 8.714, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[8]/SkillRowHolder[0]/SkillRow[0]/SkillMeterCell[1]", + "entityName" : "SkillMeterCell", + "entityKind" : "SectionNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[8]/SkillRowHolder[0]/SkillRow[0]", + "childIndex" : 1, + "depth" : 9, + "layer" : 9, + "computedX" : 100.53, + "computedY" : 184.182, + "placementX" : 100.53, + "placementY" : 184.182, + "placementWidth" : 60.431, + "placementHeight" : 3.389, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 60.431, + "contentHeight" : 3.389, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[8]/SkillRowHolder[0]/SkillRow[0]/SkillMeterCell[1]/SkillMeter[0]", + "entityName" : "SkillMeter", + "entityKind" : "LayerStackNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[8]/SkillRowHolder[0]/SkillRow[0]/SkillMeterCell[1]", + "childIndex" : 0, + "depth" : 10, + "layer" : 10, + "computedX" : 100.53, + "computedY" : 184.182, + "placementX" : 100.53, + "placementY" : 184.182, + "placementWidth" : 60.431, + "placementHeight" : 3.389, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 60.431, + "contentHeight" : 3.389, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[8]/SkillRowHolder[0]/SkillRow[0]/SkillMeterCell[1]/SkillMeter[0]/SkillTrack[0]", + "entityName" : "SkillTrack", + "entityKind" : "LineNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[8]/SkillRowHolder[0]/SkillRow[0]/SkillMeterCell[1]/SkillMeter[0]", + "childIndex" : 0, + "depth" : 11, + "layer" : 11, + "computedX" : 100.53, + "computedY" : 185.312, + "placementX" : 100.53, + "placementY" : 185.312, + "placementWidth" : 60.431, + "placementHeight" : 1.13, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 60.431, + "contentHeight" : 1.13, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[8]/SkillRowHolder[0]/SkillRow[0]/SkillMeterCell[1]/SkillMeter[0]/SkillTrackFilled[1]", + "entityName" : "SkillTrackFilled", + "entityKind" : "LineNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[8]/SkillRowHolder[0]/SkillRow[0]/SkillMeterCell[1]/SkillMeter[0]", + "childIndex" : 1, + "depth" : 11, + "layer" : 11, + "computedX" : 100.53, + "computedY" : 185.312, + "placementX" : 100.53, + "placementY" : 185.312, + "placementWidth" : 45.323, + "placementHeight" : 1.13, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 45.323, + "contentHeight" : 1.13, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[8]/SkillRowHolder[0]/SkillRow[0]/SkillMeterCell[1]/SkillMeter[0]/SkillKnob[2]", + "entityName" : "SkillKnob", + "entityKind" : "ShapeNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[8]/SkillRowHolder[0]/SkillRow[0]/SkillMeterCell[1]/SkillMeter[0]", + "childIndex" : 2, + "depth" : 11, + "layer" : 11, + "computedX" : 143.312, + "computedY" : 184.182, + "placementX" : 143.312, + "placementY" : 184.182, + "placementWidth" : 5.083, + "placementHeight" : 3.389, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 5.083, + "contentHeight" : 3.389, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[9]", + "entityName" : "SkillRowLayer", + "entityKind" : "LayerStackNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]", + "childIndex" : 9, + "depth" : 6, + "layer" : 6, + "computedX" : 29.368, + "computedY" : 166.424, + "placementX" : 29.368, + "placementY" : 166.424, + "placementWidth" : 131.593, + "placementHeight" : 8.714, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 131.593, + "contentHeight" : 8.714, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[9]/SkillRowHolder[0]", + "entityName" : "SkillRowHolder", + "entityKind" : "SectionNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[9]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 29.368, + "computedY" : 166.424, + "placementX" : 29.368, + "placementY" : 166.424, + "placementWidth" : 131.593, + "placementHeight" : 8.714, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 131.593, + "contentHeight" : 8.714, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[9]/SkillRowHolder[0]/SkillRow[0]", + "entityName" : "SkillRow", + "entityKind" : "RowNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[9]/SkillRowHolder[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 29.368, + "computedY" : 166.424, + "placementX" : 29.368, + "placementY" : 166.424, + "placementWidth" : 131.593, + "placementHeight" : 8.714, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 131.593, + "contentHeight" : 8.714, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[9]/SkillRowHolder[0]/SkillRow[0]/SkillLabel[0]", + "entityName" : "SkillLabel", + "entityKind" : "ParagraphNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[9]/SkillRowHolder[0]/SkillRow[0]", + "childIndex" : 0, + "depth" : 9, + "layer" : 9, + "computedX" : 29.368, + "computedY" : 166.424, + "placementX" : 29.368, + "placementY" : 166.424, + "placementWidth" : 49.886, + "placementHeight" : 8.714, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 49.886, + "contentHeight" : 8.714, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[9]/SkillRowHolder[0]/SkillRow[0]/SkillMeterCell[1]", + "entityName" : "SkillMeterCell", + "entityKind" : "SectionNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[9]/SkillRowHolder[0]/SkillRow[0]", + "childIndex" : 1, + "depth" : 9, + "layer" : 9, + "computedX" : 100.53, + "computedY" : 169.087, + "placementX" : 100.53, + "placementY" : 169.087, + "placementWidth" : 60.431, + "placementHeight" : 3.389, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 60.431, + "contentHeight" : 3.389, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[9]/SkillRowHolder[0]/SkillRow[0]/SkillMeterCell[1]/SkillMeter[0]", + "entityName" : "SkillMeter", + "entityKind" : "LayerStackNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[9]/SkillRowHolder[0]/SkillRow[0]/SkillMeterCell[1]", + "childIndex" : 0, + "depth" : 10, + "layer" : 10, + "computedX" : 100.53, + "computedY" : 169.087, + "placementX" : 100.53, + "placementY" : 169.087, + "placementWidth" : 60.431, + "placementHeight" : 3.389, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 60.431, + "contentHeight" : 3.389, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[9]/SkillRowHolder[0]/SkillRow[0]/SkillMeterCell[1]/SkillMeter[0]/SkillTrack[0]", + "entityName" : "SkillTrack", + "entityKind" : "LineNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[9]/SkillRowHolder[0]/SkillRow[0]/SkillMeterCell[1]/SkillMeter[0]", + "childIndex" : 0, + "depth" : 11, + "layer" : 11, + "computedX" : 100.53, + "computedY" : 170.216, + "placementX" : 100.53, + "placementY" : 170.216, + "placementWidth" : 60.431, + "placementHeight" : 1.13, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 60.431, + "contentHeight" : 1.13, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[9]/SkillRowHolder[0]/SkillRow[0]/SkillMeterCell[1]/SkillMeter[0]/SkillTrackFilled[1]", + "entityName" : "SkillTrackFilled", + "entityKind" : "LineNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[9]/SkillRowHolder[0]/SkillRow[0]/SkillMeterCell[1]/SkillMeter[0]", + "childIndex" : 1, + "depth" : 11, + "layer" : 11, + "computedX" : 100.53, + "computedY" : 170.216, + "placementX" : 100.53, + "placementY" : 170.216, + "placementWidth" : 58.014, + "placementHeight" : 1.13, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 58.014, + "contentHeight" : 1.13, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[9]/SkillRowHolder[0]/SkillRow[0]/SkillMeterCell[1]/SkillMeter[0]/SkillKnob[2]", + "entityName" : "SkillKnob", + "entityKind" : "ShapeNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Skills[6]/SkillRows[2]/SkillRowLayer[9]/SkillRowHolder[0]/SkillRow[0]/SkillMeterCell[1]/SkillMeter[0]", + "childIndex" : 2, + "depth" : 11, + "layer" : 11, + "computedX" : 156.003, + "computedY" : 169.087, + "placementX" : 156.003, + "placementY" : 169.087, + "placementWidth" : 5.083, + "placementHeight" : 3.389, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 5.083, + "contentHeight" : 3.389, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Languages[7]", + "entityName" : "Languages", + "entityKind" : "SectionNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]", + "childIndex" : 7, + "depth" : 4, + "layer" : 4, + "computedX" : 0.0, + "computedY" : 56.397, + "placementX" : 0.0, + "placementY" : 56.397, + "placementWidth" : 185.247, + "placementHeight" : 80.263, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 185.247, + "contentHeight" : 80.263, + "margin" : { + "top" : 29.764, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 24.285, + "bottom" : 0.0, + "left" : 29.368 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Languages[7]/AsideHeadingLANGUAGES[0]", + "entityName" : "AsideHeadingLANGUAGES", + "entityKind" : "ParagraphNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Languages[7]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 29.368, + "computedY" : 124.074, + "placementX" : 29.368, + "placementY" : 124.074, + "placementWidth" : 58.852, + "placementHeight" : 12.586, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 58.852, + "contentHeight" : 12.586, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Languages[7]/AsideRuleLANGUAGES[1]", + "entityName" : "AsideRuleLANGUAGES", + "entityKind" : "LineNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Languages[7]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 29.368, + "computedY" : 121.815, + "placementX" : 29.368, + "placementY" : 121.815, + "placementWidth" : 131.593, + "placementHeight" : 1.13, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 131.593, + "contentHeight" : 1.13, + "margin" : { + "top" : 1.13, + "right" : 0.0, + "bottom" : 13.894, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Languages[7]/LanguageRows[2]", + "entityName" : "LanguageRows", + "entityKind" : "SectionNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Languages[7]", + "childIndex" : 2, + "depth" : 5, + "layer" : 5, + "computedX" : 29.368, + "computedY" : 56.397, + "placementX" : 29.368, + "placementY" : 56.397, + "placementWidth" : 131.593, + "placementHeight" : 51.524, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 131.593, + "contentHeight" : 51.524, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Languages[7]/LanguageRows[2]/LanguageRowLayer[0]", + "entityName" : "LanguageRowLayer", + "entityKind" : "LayerStackNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Languages[7]/LanguageRows[2]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 29.368, + "computedY" : 98.239, + "placementX" : 29.368, + "placementY" : 98.239, + "placementWidth" : 131.593, + "placementHeight" : 9.682, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 131.593, + "contentHeight" : 9.682, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Languages[7]/LanguageRows[2]/LanguageRowLayer[0]/LanguageRowHolder[0]", + "entityName" : "LanguageRowHolder", + "entityKind" : "SectionNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Languages[7]/LanguageRows[2]/LanguageRowLayer[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 29.368, + "computedY" : 98.239, + "placementX" : 29.368, + "placementY" : 98.239, + "placementWidth" : 131.593, + "placementHeight" : 9.682, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 131.593, + "contentHeight" : 9.682, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Languages[7]/LanguageRows[2]/LanguageRowLayer[0]/LanguageRowHolder[0]/LanguageRow[0]", + "entityName" : "LanguageRow", + "entityKind" : "RowNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Languages[7]/LanguageRows[2]/LanguageRowLayer[0]/LanguageRowHolder[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 29.368, + "computedY" : 98.239, + "placementX" : 29.368, + "placementY" : 98.239, + "placementWidth" : 131.593, + "placementHeight" : 9.682, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 131.593, + "contentHeight" : 9.682, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Languages[7]/LanguageRows[2]/LanguageRowLayer[0]/LanguageRowHolder[0]/LanguageRow[0]/LanguageLabel[0]", + "entityName" : "LanguageLabel", + "entityKind" : "ParagraphNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Languages[7]/LanguageRows[2]/LanguageRowLayer[0]/LanguageRowHolder[0]/LanguageRow[0]", + "childIndex" : 0, + "depth" : 9, + "layer" : 9, + "computedX" : 29.368, + "computedY" : 98.239, + "placementX" : 29.368, + "placementY" : 98.239, + "placementWidth" : 25.689, + "placementHeight" : 9.682, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 25.689, + "contentHeight" : 9.682, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Languages[7]/LanguageRows[2]/LanguageRowLayer[0]/LanguageRowHolder[0]/LanguageRow[0]/LanguageDots[1]", + "entityName" : "LanguageDots", + "entityKind" : "ParagraphNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Languages[7]/LanguageRows[2]/LanguageRowLayer[0]/LanguageRowHolder[0]/LanguageRow[0]", + "childIndex" : 1, + "depth" : 9, + "layer" : 9, + "computedX" : 106.743, + "computedY" : 98.239, + "placementX" : 106.743, + "placementY" : 98.239, + "placementWidth" : 54.057, + "placementHeight" : 9.682, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 54.057, + "contentHeight" : 9.682, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Languages[7]/LanguageRows[2]/LanguageRowLayer[1]", + "entityName" : "LanguageRowLayer", + "entityKind" : "LayerStackNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Languages[7]/LanguageRows[2]", + "childIndex" : 1, + "depth" : 6, + "layer" : 6, + "computedX" : 29.368, + "computedY" : 77.318, + "placementX" : 29.368, + "placementY" : 77.318, + "placementWidth" : 131.593, + "placementHeight" : 9.682, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 131.593, + "contentHeight" : 9.682, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Languages[7]/LanguageRows[2]/LanguageRowLayer[1]/LanguageRowHolder[0]", + "entityName" : "LanguageRowHolder", + "entityKind" : "SectionNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Languages[7]/LanguageRows[2]/LanguageRowLayer[1]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 29.368, + "computedY" : 77.318, + "placementX" : 29.368, + "placementY" : 77.318, + "placementWidth" : 131.593, + "placementHeight" : 9.682, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 131.593, + "contentHeight" : 9.682, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Languages[7]/LanguageRows[2]/LanguageRowLayer[1]/LanguageRowHolder[0]/LanguageRow[0]", + "entityName" : "LanguageRow", + "entityKind" : "RowNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Languages[7]/LanguageRows[2]/LanguageRowLayer[1]/LanguageRowHolder[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 29.368, + "computedY" : 77.318, + "placementX" : 29.368, + "placementY" : 77.318, + "placementWidth" : 131.593, + "placementHeight" : 9.682, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 131.593, + "contentHeight" : 9.682, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Languages[7]/LanguageRows[2]/LanguageRowLayer[1]/LanguageRowHolder[0]/LanguageRow[0]/LanguageLabel[0]", + "entityName" : "LanguageLabel", + "entityKind" : "ParagraphNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Languages[7]/LanguageRows[2]/LanguageRowLayer[1]/LanguageRowHolder[0]/LanguageRow[0]", + "childIndex" : 0, + "depth" : 9, + "layer" : 9, + "computedX" : 29.368, + "computedY" : 77.318, + "placementX" : 29.368, + "placementY" : 77.318, + "placementWidth" : 27.924, + "placementHeight" : 9.682, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 27.924, + "contentHeight" : 9.682, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Languages[7]/LanguageRows[2]/LanguageRowLayer[1]/LanguageRowHolder[0]/LanguageRow[0]/LanguageDots[1]", + "entityName" : "LanguageDots", + "entityKind" : "ParagraphNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Languages[7]/LanguageRows[2]/LanguageRowLayer[1]/LanguageRowHolder[0]/LanguageRow[0]", + "childIndex" : 1, + "depth" : 9, + "layer" : 9, + "computedX" : 106.743, + "computedY" : 77.318, + "placementX" : 106.743, + "placementY" : 77.318, + "placementWidth" : 54.057, + "placementHeight" : 9.682, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 54.057, + "contentHeight" : 9.682, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Languages[7]/LanguageRows[2]/LanguageRowLayer[2]", + "entityName" : "LanguageRowLayer", + "entityKind" : "LayerStackNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Languages[7]/LanguageRows[2]", + "childIndex" : 2, + "depth" : 6, + "layer" : 6, + "computedX" : 29.368, + "computedY" : 56.397, + "placementX" : 29.368, + "placementY" : 56.397, + "placementWidth" : 131.593, + "placementHeight" : 9.682, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 131.593, + "contentHeight" : 9.682, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Languages[7]/LanguageRows[2]/LanguageRowLayer[2]/LanguageRowHolder[0]", + "entityName" : "LanguageRowHolder", + "entityKind" : "SectionNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Languages[7]/LanguageRows[2]/LanguageRowLayer[2]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 29.368, + "computedY" : 56.397, + "placementX" : 29.368, + "placementY" : 56.397, + "placementWidth" : 131.593, + "placementHeight" : 9.682, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 131.593, + "contentHeight" : 9.682, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Languages[7]/LanguageRows[2]/LanguageRowLayer[2]/LanguageRowHolder[0]/LanguageRow[0]", + "entityName" : "LanguageRow", + "entityKind" : "RowNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Languages[7]/LanguageRows[2]/LanguageRowLayer[2]/LanguageRowHolder[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 29.368, + "computedY" : 56.397, + "placementX" : 29.368, + "placementY" : 56.397, + "placementWidth" : 131.593, + "placementHeight" : 9.682, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 131.593, + "contentHeight" : 9.682, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Languages[7]/LanguageRows[2]/LanguageRowLayer[2]/LanguageRowHolder[0]/LanguageRow[0]/LanguageLabel[0]", + "entityName" : "LanguageLabel", + "entityKind" : "ParagraphNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Languages[7]/LanguageRows[2]/LanguageRowLayer[2]/LanguageRowHolder[0]/LanguageRow[0]", + "childIndex" : 0, + "depth" : 9, + "layer" : 9, + "computedX" : 29.368, + "computedY" : 56.397, + "placementX" : 29.368, + "placementY" : 56.397, + "placementWidth" : 24.713, + "placementHeight" : 9.682, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 24.713, + "contentHeight" : 9.682, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Languages[7]/LanguageRows[2]/LanguageRowLayer[2]/LanguageRowHolder[0]/LanguageRow[0]/LanguageDots[1]", + "entityName" : "LanguageDots", + "entityKind" : "ParagraphNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Aside[0]/Languages[7]/LanguageRows[2]/LanguageRowLayer[2]/LanguageRowHolder[0]/LanguageRow[0]", + "childIndex" : 1, + "depth" : 9, + "layer" : 9, + "computedX" : 106.743, + "computedY" : 56.397, + "placementX" : 106.743, + "placementY" : 56.397, + "placementWidth" : 54.057, + "placementHeight" : 9.682, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 54.057, + "contentHeight" : 9.682, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]", + "entityName" : "Main", + "entityKind" : "SectionNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]", + "childIndex" : 1, + "depth" : 3, + "layer" : 3, + "computedX" : 185.247, + "computedY" : 28.659, + "placementX" : 185.247, + "placementY" : 28.659, + "placementWidth" : 410.029, + "placementHeight" : 813.231, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 410.029, + "contentHeight" : 813.231, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 40.269, + "right" : 41.794, + "bottom" : 0.0, + "left" : 29.933 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Summary[0]", + "entityName" : "Summary", + "entityKind" : "SectionNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 215.18, + "computedY" : 708.231, + "placementX" : 215.18, + "placementY" : 708.231, + "placementWidth" : 338.302, + "placementHeight" : 93.39, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 338.302, + "contentHeight" : 93.39, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Summary[0]/MainHeadingPROFESSIONALSUMMARY[0]", + "entityName" : "MainHeadingPROFESSIONALSUMMARY", + "entityKind" : "ParagraphNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Summary[0]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 215.18, + "computedY" : 788.066, + "placementX" : 215.18, + "placementY" : 788.066, + "placementWidth" : 132.316, + "placementHeight" : 13.555, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 132.316, + "contentHeight" : 13.555, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Summary[0]/MainRulePROFESSIONALSUMMARY[1]", + "entityName" : "MainRulePROFESSIONALSUMMARY", + "entityKind" : "LineNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Summary[0]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 215.18, + "computedY" : 782.814, + "placementX" : 215.18, + "placementY" : 782.814, + "placementWidth" : 338.302, + "placementHeight" : 1.13, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 338.302, + "contentHeight" : 1.13, + "margin" : { + "top" : 4.123, + "right" : 0.0, + "bottom" : 12.651, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Summary[0]/SummaryText[2]", + "entityName" : "SummaryText", + "entityKind" : "ParagraphNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Summary[0]", + "childIndex" : 2, + "depth" : 5, + "layer" : 5, + "computedX" : 215.18, + "computedY" : 708.231, + "placementX" : 215.18, + "placementY" : 708.231, + "placementWidth" : 321.5, + "placementHeight" : 61.932, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 321.5, + "contentHeight" : 61.932, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Experience[1]", + "entityName" : "Experience", + "entityKind" : "SectionNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 215.18, + "computedY" : 238.191, + "placementX" : 215.18, + "placementY" : 238.191, + "placementWidth" : 338.302, + "placementHeight" : 434.459, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 338.302, + "contentHeight" : 434.459, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Experience[1]/MainHeadingEXPERIENCE[0]", + "entityName" : "MainHeadingEXPERIENCE", + "entityKind" : "ParagraphNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Experience[1]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 215.18, + "computedY" : 659.095, + "placementX" : 215.18, + "placementY" : 659.095, + "placementWidth" : 64.543, + "placementHeight" : 13.555, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 64.543, + "contentHeight" : 13.555, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Experience[1]/MainRuleEXPERIENCE[1]", + "entityName" : "MainRuleEXPERIENCE", + "entityKind" : "LineNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Experience[1]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 215.18, + "computedY" : 653.843, + "placementX" : 215.18, + "placementY" : 653.843, + "placementWidth" : 338.302, + "placementHeight" : 1.13, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 338.302, + "contentHeight" : 1.13, + "margin" : { + "top" : 4.123, + "right" : 0.0, + "bottom" : 12.99, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Experience[1]/ExperienceRail[2]", + "entityName" : "ExperienceRail", + "entityKind" : "LayerStackNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Experience[1]", + "childIndex" : 2, + "depth" : 5, + "layer" : 5, + "computedX" : 215.18, + "computedY" : 238.191, + "placementX" : 215.18, + "placementY" : 238.191, + "placementWidth" : 338.302, + "placementHeight" : 402.662, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 338.302, + "contentHeight" : 402.662, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Experience[1]/ExperienceRail[2]/ExperienceRailHolder[0]", + "entityName" : "ExperienceRailHolder", + "entityKind" : "SectionNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Experience[1]/ExperienceRail[2]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 212.639, + "computedY" : 238.191, + "placementX" : 212.639, + "placementY" : 238.191, + "placementWidth" : 340.843, + "placementHeight" : 402.662, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 340.843, + "contentHeight" : 402.662, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : -2.541 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Experience[1]/ExperienceRail[2]/ExperienceRailHolder[0]/SectionNode[0]", + "entityName" : null, + "entityKind" : "SectionNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Experience[1]/ExperienceRail[2]/ExperienceRailHolder[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 212.639, + "computedY" : 238.191, + "placementX" : 212.639, + "placementY" : 238.191, + "placementWidth" : 340.843, + "placementHeight" : 402.662, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 340.843, + "contentHeight" : 402.662, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Experience[1]/ExperienceRail[2]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]", + "entityName" : null, + "entityKind" : "LayoutAnchorNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Experience[1]/ExperienceRail[2]/ExperienceRailHolder[0]/SectionNode[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 212.639, + "computedY" : 471.202, + "placementX" : 212.639, + "placementY" : 471.202, + "placementWidth" : 340.843, + "placementHeight" : 169.651, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 340.843, + "contentHeight" : 169.651, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Experience[1]/ExperienceRail[2]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]", + "entityName" : null, + "entityKind" : "SectionNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Experience[1]/ExperienceRail[2]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]", + "childIndex" : 0, + "depth" : 9, + "layer" : 9, + "computedX" : 212.639, + "computedY" : 471.202, + "placementX" : 212.639, + "placementY" : 471.202, + "placementWidth" : 340.843, + "placementHeight" : 169.651, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 340.843, + "contentHeight" : 169.651, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 25.98, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Experience[1]/ExperienceRail[2]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]", + "entityName" : null, + "entityKind" : "HorizontalBandsNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Experience[1]/ExperienceRail[2]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]", + "childIndex" : 0, + "depth" : 10, + "layer" : 10, + "computedX" : 212.639, + "computedY" : 497.182, + "placementX" : 212.639, + "placementY" : 497.182, + "placementWidth" : 340.843, + "placementHeight" : 143.671, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 340.843, + "contentHeight" : 143.671, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Experience[1]/ExperienceRail[2]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]", + "entityName" : null, + "entityKind" : "RowNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Experience[1]/ExperienceRail[2]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]", + "childIndex" : 0, + "depth" : 11, + "layer" : 11, + "computedX" : 212.639, + "computedY" : 497.182, + "placementX" : 212.639, + "placementY" : 497.182, + "placementWidth" : 340.843, + "placementHeight" : 143.671, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 340.843, + "contentHeight" : 143.671, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Experience[1]/ExperienceRail[2]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]", + "entityName" : null, + "entityKind" : "SectionNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Experience[1]/ExperienceRail[2]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]", + "childIndex" : 0, + "depth" : 12, + "layer" : 12, + "computedX" : 212.639, + "computedY" : 634.019, + "placementX" : 212.639, + "placementY" : 634.019, + "placementWidth" : 5.083, + "placementHeight" : 6.834, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 5.083, + "contentHeight" : 6.834, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Experience[1]/ExperienceRail[2]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]", + "entityName" : null, + "entityKind" : "Align", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Experience[1]/ExperienceRail[2]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]", + "childIndex" : 0, + "depth" : 13, + "layer" : 13, + "computedX" : 212.639, + "computedY" : 634.019, + "placementX" : 212.639, + "placementY" : 634.019, + "placementWidth" : 5.083, + "placementHeight" : 6.834, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 5.083, + "contentHeight" : 6.834, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Experience[1]/ExperienceRail[2]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]/LayoutAnchorNode[0]", + "entityName" : null, + "entityKind" : "LayoutAnchorNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Experience[1]/ExperienceRail[2]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]", + "childIndex" : 0, + "depth" : 14, + "layer" : 14, + "computedX" : 212.639, + "computedY" : 634.019, + "placementX" : 212.639, + "placementY" : 634.019, + "placementWidth" : 5.083, + "placementHeight" : 6.834, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 5.083, + "contentHeight" : 6.834, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Experience[1]/ExperienceRail[2]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]/LayoutAnchorNode[0]/marker[0]", + "entityName" : "marker", + "entityKind" : "CanvasLayerNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Experience[1]/ExperienceRail[2]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]/LayoutAnchorNode[0]", + "childIndex" : 0, + "depth" : 15, + "layer" : 15, + "computedX" : 212.639, + "computedY" : 634.019, + "placementX" : 212.639, + "placementY" : 634.019, + "placementWidth" : 5.083, + "placementHeight" : 6.834, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 5.083, + "contentHeight" : 6.834, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Experience[1]/ExperienceRail[2]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]/LayoutAnchorNode[0]/marker[0]/SectionNode[0]", + "entityName" : null, + "entityKind" : "SectionNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Experience[1]/ExperienceRail[2]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]/LayoutAnchorNode[0]/marker[0]", + "childIndex" : 0, + "depth" : 16, + "layer" : 16, + "computedX" : 212.639, + "computedY" : 634.019, + "placementX" : 212.639, + "placementY" : 634.019, + "placementWidth" : 5.083, + "placementHeight" : 6.834, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 5.083, + "contentHeight" : 6.834, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Experience[1]/ExperienceRail[2]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]/LayoutAnchorNode[0]/marker[0]/SectionNode[0]/EntryMarker[0]", + "entityName" : "EntryMarker", + "entityKind" : "EllipseNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Experience[1]/ExperienceRail[2]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]/LayoutAnchorNode[0]/marker[0]/SectionNode[0]", + "childIndex" : 0, + "depth" : 17, + "layer" : 17, + "computedX" : 212.639, + "computedY" : 634.019, + "placementX" : 212.639, + "placementY" : 634.019, + "placementWidth" : 5.083, + "placementHeight" : 5.083, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 5.083, + "contentHeight" : 5.083, + "margin" : { + "top" : 1.751, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Experience[1]/ExperienceRail[2]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]", + "entityName" : null, + "entityKind" : "SectionNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Experience[1]/ExperienceRail[2]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]", + "childIndex" : 1, + "depth" : 12, + "layer" : 12, + "computedX" : 230.429, + "computedY" : 497.182, + "placementX" : 230.429, + "placementY" : 497.182, + "placementWidth" : 323.053, + "placementHeight" : 143.671, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 323.053, + "contentHeight" : 143.671, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Experience[1]/ExperienceRail[2]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]/EntryHeaderLayer[0]", + "entityName" : "EntryHeaderLayer", + "entityKind" : "LayerStackNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Experience[1]/ExperienceRail[2]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]", + "childIndex" : 0, + "depth" : 13, + "layer" : 13, + "computedX" : 230.429, + "computedY" : 629.235, + "placementX" : 230.429, + "placementY" : 629.235, + "placementWidth" : 323.053, + "placementHeight" : 11.618, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 323.053, + "contentHeight" : 11.618, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Experience[1]/ExperienceRail[2]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]/EntryHeaderLayer[0]/EntryHeaderHolder[0]", + "entityName" : "EntryHeaderHolder", + "entityKind" : "SectionNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Experience[1]/ExperienceRail[2]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]/EntryHeaderLayer[0]", + "childIndex" : 0, + "depth" : 14, + "layer" : 14, + "computedX" : 230.429, + "computedY" : 629.235, + "placementX" : 230.429, + "placementY" : 629.235, + "placementWidth" : 323.053, + "placementHeight" : 11.618, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 323.053, + "contentHeight" : 11.618, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Experience[1]/ExperienceRail[2]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]/EntryHeaderLayer[0]/EntryHeaderHolder[0]/EntryHeader[0]", + "entityName" : "EntryHeader", + "entityKind" : "RowNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Experience[1]/ExperienceRail[2]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]/EntryHeaderLayer[0]/EntryHeaderHolder[0]", + "childIndex" : 0, + "depth" : 15, + "layer" : 15, + "computedX" : 230.429, + "computedY" : 629.235, + "placementX" : 230.429, + "placementY" : 629.235, + "placementWidth" : 323.053, + "placementHeight" : 11.618, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 323.053, + "contentHeight" : 11.618, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Experience[1]/ExperienceRail[2]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]/EntryHeaderLayer[0]/EntryHeaderHolder[0]/EntryHeader[0]/EntryTitle[0]", + "entityName" : "EntryTitle", + "entityKind" : "ParagraphNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Experience[1]/ExperienceRail[2]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]/EntryHeaderLayer[0]/EntryHeaderHolder[0]/EntryHeader[0]", + "childIndex" : 0, + "depth" : 16, + "layer" : 16, + "computedX" : 230.429, + "computedY" : 629.235, + "placementX" : 230.429, + "placementY" : 629.235, + "placementWidth" : 97.468, + "placementHeight" : 11.618, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 97.468, + "contentHeight" : 11.618, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Experience[1]/ExperienceRail[2]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]/EntryHeaderLayer[0]/EntryHeaderHolder[0]/EntryHeader[0]/EntryPeriod[1]", + "entityName" : "EntryPeriod", + "entityKind" : "ParagraphNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Experience[1]/ExperienceRail[2]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]/EntryHeaderLayer[0]/EntryHeaderHolder[0]/EntryHeader[0]", + "childIndex" : 1, + "depth" : 16, + "layer" : 16, + "computedX" : 430.722, + "computedY" : 630.203, + "placementX" : 430.722, + "placementY" : 630.203, + "placementWidth" : 122.76, + "placementHeight" : 10.65, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 122.76, + "contentHeight" : 10.65, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Experience[1]/ExperienceRail[2]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]/EntrySubheaderLayer[1]", + "entityName" : "EntrySubheaderLayer", + "entityKind" : "LayerStackNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Experience[1]/ExperienceRail[2]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]", + "childIndex" : 1, + "depth" : 13, + "layer" : 13, + "computedX" : 230.429, + "computedY" : 615.535, + "placementX" : 230.429, + "placementY" : 615.535, + "placementWidth" : 323.053, + "placementHeight" : 13.7, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 323.053, + "contentHeight" : 13.7, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Experience[1]/ExperienceRail[2]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]/EntrySubheaderLayer[1]/EntrySubheaderHolder[0]", + "entityName" : "EntrySubheaderHolder", + "entityKind" : "SectionNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Experience[1]/ExperienceRail[2]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]/EntrySubheaderLayer[1]", + "childIndex" : 0, + "depth" : 14, + "layer" : 14, + "computedX" : 230.429, + "computedY" : 615.535, + "placementX" : 230.429, + "placementY" : 615.535, + "placementWidth" : 323.053, + "placementHeight" : 13.7, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 323.053, + "contentHeight" : 13.7, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Experience[1]/ExperienceRail[2]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]/EntrySubheaderLayer[1]/EntrySubheaderHolder[0]/EntrySubheader[0]", + "entityName" : "EntrySubheader", + "entityKind" : "RowNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Experience[1]/ExperienceRail[2]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]/EntrySubheaderLayer[1]/EntrySubheaderHolder[0]", + "childIndex" : 0, + "depth" : 15, + "layer" : 15, + "computedX" : 230.429, + "computedY" : 615.535, + "placementX" : 230.429, + "placementY" : 615.535, + "placementWidth" : 323.053, + "placementHeight" : 13.7, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 323.053, + "contentHeight" : 13.7, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Experience[1]/ExperienceRail[2]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]/EntrySubheaderLayer[1]/EntrySubheaderHolder[0]/EntrySubheader[0]/EntryCompany[0]", + "entityName" : "EntryCompany", + "entityKind" : "ParagraphNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Experience[1]/ExperienceRail[2]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]/EntrySubheaderLayer[1]/EntrySubheaderHolder[0]/EntrySubheader[0]", + "childIndex" : 0, + "depth" : 16, + "layer" : 16, + "computedX" : 230.429, + "computedY" : 615.535, + "placementX" : 230.429, + "placementY" : 615.535, + "placementWidth" : 72.43, + "placementHeight" : 10.65, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 72.43, + "contentHeight" : 10.65, + "margin" : { + "top" : 3.05, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Experience[1]/ExperienceRail[2]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]/EntrySubheaderLayer[1]/EntrySubheaderHolder[0]/EntrySubheader[0]/EntryLocation[1]", + "entityName" : "EntryLocation", + "entityKind" : "ParagraphNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Experience[1]/ExperienceRail[2]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]/EntrySubheaderLayer[1]/EntrySubheaderHolder[0]/EntrySubheader[0]", + "childIndex" : 1, + "depth" : 16, + "layer" : 16, + "computedX" : 430.722, + "computedY" : 615.535, + "placementX" : 430.722, + "placementY" : 615.535, + "placementWidth" : 122.76, + "placementHeight" : 10.65, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 122.76, + "contentHeight" : 10.65, + "margin" : { + "top" : 3.05, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Experience[1]/ExperienceRail[2]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]/EntryBullets[2]", + "entityName" : "EntryBullets", + "entityKind" : "ListNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Experience[1]/ExperienceRail[2]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]", + "childIndex" : 2, + "depth" : 13, + "layer" : 13, + "computedX" : 230.429, + "computedY" : 497.182, + "placementX" : 230.429, + "placementY" : 497.182, + "placementWidth" : 318.296, + "placementHeight" : 104.347, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 318.296, + "contentHeight" : 104.347, + "margin" : { + "top" : 14.006, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Experience[1]/ExperienceRail[2]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]", + "entityName" : null, + "entityKind" : "LayoutAnchorNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Experience[1]/ExperienceRail[2]/ExperienceRailHolder[0]/SectionNode[0]", + "childIndex" : 1, + "depth" : 8, + "layer" : 8, + "computedX" : 212.639, + "computedY" : 328.321, + "placementX" : 212.639, + "placementY" : 328.321, + "placementWidth" : 340.843, + "placementHeight" : 142.881, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 340.843, + "contentHeight" : 142.881, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Experience[1]/ExperienceRail[2]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]", + "entityName" : null, + "entityKind" : "SectionNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Experience[1]/ExperienceRail[2]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]", + "childIndex" : 0, + "depth" : 9, + "layer" : 9, + "computedX" : 212.639, + "computedY" : 328.321, + "placementX" : 212.639, + "placementY" : 328.321, + "placementWidth" : 340.843, + "placementHeight" : 142.881, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 340.843, + "contentHeight" : 142.881, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 25.98, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Experience[1]/ExperienceRail[2]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]", + "entityName" : null, + "entityKind" : "HorizontalBandsNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Experience[1]/ExperienceRail[2]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]", + "childIndex" : 0, + "depth" : 10, + "layer" : 10, + "computedX" : 212.639, + "computedY" : 354.301, + "placementX" : 212.639, + "placementY" : 354.301, + "placementWidth" : 340.843, + "placementHeight" : 116.901, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 340.843, + "contentHeight" : 116.901, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Experience[1]/ExperienceRail[2]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]", + "entityName" : null, + "entityKind" : "RowNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Experience[1]/ExperienceRail[2]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]", + "childIndex" : 0, + "depth" : 11, + "layer" : 11, + "computedX" : 212.639, + "computedY" : 354.301, + "placementX" : 212.639, + "placementY" : 354.301, + "placementWidth" : 340.843, + "placementHeight" : 116.901, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 340.843, + "contentHeight" : 116.901, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Experience[1]/ExperienceRail[2]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]", + "entityName" : null, + "entityKind" : "SectionNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Experience[1]/ExperienceRail[2]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]", + "childIndex" : 0, + "depth" : 12, + "layer" : 12, + "computedX" : 212.639, + "computedY" : 464.368, + "placementX" : 212.639, + "placementY" : 464.368, + "placementWidth" : 5.083, + "placementHeight" : 6.834, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 5.083, + "contentHeight" : 6.834, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Experience[1]/ExperienceRail[2]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]", + "entityName" : null, + "entityKind" : "Align", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Experience[1]/ExperienceRail[2]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]", + "childIndex" : 0, + "depth" : 13, + "layer" : 13, + "computedX" : 212.639, + "computedY" : 464.368, + "placementX" : 212.639, + "placementY" : 464.368, + "placementWidth" : 5.083, + "placementHeight" : 6.834, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 5.083, + "contentHeight" : 6.834, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Experience[1]/ExperienceRail[2]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]/LayoutAnchorNode[0]", + "entityName" : null, + "entityKind" : "LayoutAnchorNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Experience[1]/ExperienceRail[2]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]", + "childIndex" : 0, + "depth" : 14, + "layer" : 14, + "computedX" : 212.639, + "computedY" : 464.368, + "placementX" : 212.639, + "placementY" : 464.368, + "placementWidth" : 5.083, + "placementHeight" : 6.834, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 5.083, + "contentHeight" : 6.834, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Experience[1]/ExperienceRail[2]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]/LayoutAnchorNode[0]/marker[0]", + "entityName" : "marker", + "entityKind" : "CanvasLayerNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Experience[1]/ExperienceRail[2]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]/LayoutAnchorNode[0]", + "childIndex" : 0, + "depth" : 15, + "layer" : 15, + "computedX" : 212.639, + "computedY" : 464.368, + "placementX" : 212.639, + "placementY" : 464.368, + "placementWidth" : 5.083, + "placementHeight" : 6.834, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 5.083, + "contentHeight" : 6.834, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Experience[1]/ExperienceRail[2]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]/LayoutAnchorNode[0]/marker[0]/SectionNode[0]", + "entityName" : null, + "entityKind" : "SectionNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Experience[1]/ExperienceRail[2]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]/LayoutAnchorNode[0]/marker[0]", + "childIndex" : 0, + "depth" : 16, + "layer" : 16, + "computedX" : 212.639, + "computedY" : 464.368, + "placementX" : 212.639, + "placementY" : 464.368, + "placementWidth" : 5.083, + "placementHeight" : 6.834, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 5.083, + "contentHeight" : 6.834, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Experience[1]/ExperienceRail[2]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]/LayoutAnchorNode[0]/marker[0]/SectionNode[0]/EntryMarker[0]", + "entityName" : "EntryMarker", + "entityKind" : "EllipseNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Experience[1]/ExperienceRail[2]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]/LayoutAnchorNode[0]/marker[0]/SectionNode[0]", + "childIndex" : 0, + "depth" : 17, + "layer" : 17, + "computedX" : 212.639, + "computedY" : 464.368, + "placementX" : 212.639, + "placementY" : 464.368, + "placementWidth" : 5.083, + "placementHeight" : 5.083, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 5.083, + "contentHeight" : 5.083, + "margin" : { + "top" : 1.751, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Experience[1]/ExperienceRail[2]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]", + "entityName" : null, + "entityKind" : "SectionNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Experience[1]/ExperienceRail[2]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]", + "childIndex" : 1, + "depth" : 12, + "layer" : 12, + "computedX" : 230.429, + "computedY" : 354.301, + "placementX" : 230.429, + "placementY" : 354.301, + "placementWidth" : 323.053, + "placementHeight" : 116.901, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 323.053, + "contentHeight" : 116.901, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Experience[1]/ExperienceRail[2]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]/EntryHeaderLayer[0]", + "entityName" : "EntryHeaderLayer", + "entityKind" : "LayerStackNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Experience[1]/ExperienceRail[2]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]", + "childIndex" : 0, + "depth" : 13, + "layer" : 13, + "computedX" : 230.429, + "computedY" : 459.584, + "placementX" : 230.429, + "placementY" : 459.584, + "placementWidth" : 323.053, + "placementHeight" : 11.618, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 323.053, + "contentHeight" : 11.618, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Experience[1]/ExperienceRail[2]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]/EntryHeaderLayer[0]/EntryHeaderHolder[0]", + "entityName" : "EntryHeaderHolder", + "entityKind" : "SectionNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Experience[1]/ExperienceRail[2]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]/EntryHeaderLayer[0]", + "childIndex" : 0, + "depth" : 14, + "layer" : 14, + "computedX" : 230.429, + "computedY" : 459.584, + "placementX" : 230.429, + "placementY" : 459.584, + "placementWidth" : 323.053, + "placementHeight" : 11.618, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 323.053, + "contentHeight" : 11.618, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Experience[1]/ExperienceRail[2]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]/EntryHeaderLayer[0]/EntryHeaderHolder[0]/EntryHeader[0]", + "entityName" : "EntryHeader", + "entityKind" : "RowNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Experience[1]/ExperienceRail[2]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]/EntryHeaderLayer[0]/EntryHeaderHolder[0]", + "childIndex" : 0, + "depth" : 15, + "layer" : 15, + "computedX" : 230.429, + "computedY" : 459.584, + "placementX" : 230.429, + "placementY" : 459.584, + "placementWidth" : 323.053, + "placementHeight" : 11.618, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 323.053, + "contentHeight" : 11.618, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Experience[1]/ExperienceRail[2]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]/EntryHeaderLayer[0]/EntryHeaderHolder[0]/EntryHeader[0]/EntryTitle[0]", + "entityName" : "EntryTitle", + "entityKind" : "ParagraphNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Experience[1]/ExperienceRail[2]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]/EntryHeaderLayer[0]/EntryHeaderHolder[0]/EntryHeader[0]", + "childIndex" : 0, + "depth" : 16, + "layer" : 16, + "computedX" : 230.429, + "computedY" : 459.584, + "placementX" : 230.429, + "placementY" : 459.584, + "placementWidth" : 139.516, + "placementHeight" : 11.618, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 139.516, + "contentHeight" : 11.618, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Experience[1]/ExperienceRail[2]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]/EntryHeaderLayer[0]/EntryHeaderHolder[0]/EntryHeader[0]/EntryPeriod[1]", + "entityName" : "EntryPeriod", + "entityKind" : "ParagraphNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Experience[1]/ExperienceRail[2]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]/EntryHeaderLayer[0]/EntryHeaderHolder[0]/EntryHeader[0]", + "childIndex" : 1, + "depth" : 16, + "layer" : 16, + "computedX" : 430.722, + "computedY" : 460.552, + "placementX" : 430.722, + "placementY" : 460.552, + "placementWidth" : 122.76, + "placementHeight" : 10.65, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 122.76, + "contentHeight" : 10.65, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Experience[1]/ExperienceRail[2]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]/EntrySubheaderLayer[1]", + "entityName" : "EntrySubheaderLayer", + "entityKind" : "LayerStackNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Experience[1]/ExperienceRail[2]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]", + "childIndex" : 1, + "depth" : 13, + "layer" : 13, + "computedX" : 230.429, + "computedY" : 445.884, + "placementX" : 230.429, + "placementY" : 445.884, + "placementWidth" : 323.053, + "placementHeight" : 13.7, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 323.053, + "contentHeight" : 13.7, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Experience[1]/ExperienceRail[2]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]/EntrySubheaderLayer[1]/EntrySubheaderHolder[0]", + "entityName" : "EntrySubheaderHolder", + "entityKind" : "SectionNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Experience[1]/ExperienceRail[2]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]/EntrySubheaderLayer[1]", + "childIndex" : 0, + "depth" : 14, + "layer" : 14, + "computedX" : 230.429, + "computedY" : 445.884, + "placementX" : 230.429, + "placementY" : 445.884, + "placementWidth" : 323.053, + "placementHeight" : 13.7, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 323.053, + "contentHeight" : 13.7, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Experience[1]/ExperienceRail[2]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]/EntrySubheaderLayer[1]/EntrySubheaderHolder[0]/EntrySubheader[0]", + "entityName" : "EntrySubheader", + "entityKind" : "RowNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Experience[1]/ExperienceRail[2]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]/EntrySubheaderLayer[1]/EntrySubheaderHolder[0]", + "childIndex" : 0, + "depth" : 15, + "layer" : 15, + "computedX" : 230.429, + "computedY" : 445.884, + "placementX" : 230.429, + "placementY" : 445.884, + "placementWidth" : 323.053, + "placementHeight" : 13.7, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 323.053, + "contentHeight" : 13.7, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Experience[1]/ExperienceRail[2]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]/EntrySubheaderLayer[1]/EntrySubheaderHolder[0]/EntrySubheader[0]/EntryCompany[0]", + "entityName" : "EntryCompany", + "entityKind" : "ParagraphNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Experience[1]/ExperienceRail[2]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]/EntrySubheaderLayer[1]/EntrySubheaderHolder[0]/EntrySubheader[0]", + "childIndex" : 0, + "depth" : 16, + "layer" : 16, + "computedX" : 230.429, + "computedY" : 445.884, + "placementX" : 230.429, + "placementY" : 445.884, + "placementWidth" : 91.307, + "placementHeight" : 10.65, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 91.307, + "contentHeight" : 10.65, + "margin" : { + "top" : 3.05, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Experience[1]/ExperienceRail[2]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]/EntrySubheaderLayer[1]/EntrySubheaderHolder[0]/EntrySubheader[0]/EntryLocation[1]", + "entityName" : "EntryLocation", + "entityKind" : "ParagraphNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Experience[1]/ExperienceRail[2]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]/EntrySubheaderLayer[1]/EntrySubheaderHolder[0]/EntrySubheader[0]", + "childIndex" : 1, + "depth" : 16, + "layer" : 16, + "computedX" : 430.722, + "computedY" : 445.884, + "placementX" : 430.722, + "placementY" : 445.884, + "placementWidth" : 122.76, + "placementHeight" : 10.65, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 122.76, + "contentHeight" : 10.65, + "margin" : { + "top" : 3.05, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Experience[1]/ExperienceRail[2]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]/EntryBullets[2]", + "entityName" : "EntryBullets", + "entityKind" : "ListNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Experience[1]/ExperienceRail[2]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]", + "childIndex" : 2, + "depth" : 13, + "layer" : 13, + "computedX" : 230.429, + "computedY" : 354.301, + "placementX" : 230.429, + "placementY" : 354.301, + "placementWidth" : 310.574, + "placementHeight" : 77.576, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 310.574, + "contentHeight" : 77.576, + "margin" : { + "top" : 14.006, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Experience[1]/ExperienceRail[2]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]", + "entityName" : null, + "entityKind" : "LayoutAnchorNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Experience[1]/ExperienceRail[2]/ExperienceRailHolder[0]/SectionNode[0]", + "childIndex" : 2, + "depth" : 8, + "layer" : 8, + "computedX" : 212.639, + "computedY" : 238.191, + "placementX" : 212.639, + "placementY" : 238.191, + "placementWidth" : 340.843, + "placementHeight" : 90.13, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 340.843, + "contentHeight" : 90.13, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Experience[1]/ExperienceRail[2]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]", + "entityName" : null, + "entityKind" : "SectionNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Experience[1]/ExperienceRail[2]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]", + "childIndex" : 0, + "depth" : 9, + "layer" : 9, + "computedX" : 212.639, + "computedY" : 238.191, + "placementX" : 212.639, + "placementY" : 238.191, + "placementWidth" : 340.843, + "placementHeight" : 90.13, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 340.843, + "contentHeight" : 90.13, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Experience[1]/ExperienceRail[2]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]", + "entityName" : null, + "entityKind" : "HorizontalBandsNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Experience[1]/ExperienceRail[2]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]", + "childIndex" : 0, + "depth" : 10, + "layer" : 10, + "computedX" : 212.639, + "computedY" : 238.191, + "placementX" : 212.639, + "placementY" : 238.191, + "placementWidth" : 340.843, + "placementHeight" : 90.13, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 340.843, + "contentHeight" : 90.13, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Experience[1]/ExperienceRail[2]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]", + "entityName" : null, + "entityKind" : "RowNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Experience[1]/ExperienceRail[2]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]", + "childIndex" : 0, + "depth" : 11, + "layer" : 11, + "computedX" : 212.639, + "computedY" : 238.191, + "placementX" : 212.639, + "placementY" : 238.191, + "placementWidth" : 340.843, + "placementHeight" : 90.13, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 340.843, + "contentHeight" : 90.13, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Experience[1]/ExperienceRail[2]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]", + "entityName" : null, + "entityKind" : "SectionNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Experience[1]/ExperienceRail[2]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]", + "childIndex" : 0, + "depth" : 12, + "layer" : 12, + "computedX" : 212.639, + "computedY" : 321.488, + "placementX" : 212.639, + "placementY" : 321.488, + "placementWidth" : 5.083, + "placementHeight" : 6.834, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 5.083, + "contentHeight" : 6.834, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Experience[1]/ExperienceRail[2]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]", + "entityName" : null, + "entityKind" : "Align", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Experience[1]/ExperienceRail[2]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]", + "childIndex" : 0, + "depth" : 13, + "layer" : 13, + "computedX" : 212.639, + "computedY" : 321.488, + "placementX" : 212.639, + "placementY" : 321.488, + "placementWidth" : 5.083, + "placementHeight" : 6.834, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 5.083, + "contentHeight" : 6.834, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Experience[1]/ExperienceRail[2]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]/LayoutAnchorNode[0]", + "entityName" : null, + "entityKind" : "LayoutAnchorNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Experience[1]/ExperienceRail[2]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]", + "childIndex" : 0, + "depth" : 14, + "layer" : 14, + "computedX" : 212.639, + "computedY" : 321.488, + "placementX" : 212.639, + "placementY" : 321.488, + "placementWidth" : 5.083, + "placementHeight" : 6.834, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 5.083, + "contentHeight" : 6.834, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Experience[1]/ExperienceRail[2]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]/LayoutAnchorNode[0]/marker[0]", + "entityName" : "marker", + "entityKind" : "CanvasLayerNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Experience[1]/ExperienceRail[2]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]/LayoutAnchorNode[0]", + "childIndex" : 0, + "depth" : 15, + "layer" : 15, + "computedX" : 212.639, + "computedY" : 321.488, + "placementX" : 212.639, + "placementY" : 321.488, + "placementWidth" : 5.083, + "placementHeight" : 6.834, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 5.083, + "contentHeight" : 6.834, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Experience[1]/ExperienceRail[2]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]/LayoutAnchorNode[0]/marker[0]/SectionNode[0]", + "entityName" : null, + "entityKind" : "SectionNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Experience[1]/ExperienceRail[2]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]/LayoutAnchorNode[0]/marker[0]", + "childIndex" : 0, + "depth" : 16, + "layer" : 16, + "computedX" : 212.639, + "computedY" : 321.488, + "placementX" : 212.639, + "placementY" : 321.488, + "placementWidth" : 5.083, + "placementHeight" : 6.834, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 5.083, + "contentHeight" : 6.834, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Experience[1]/ExperienceRail[2]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]/LayoutAnchorNode[0]/marker[0]/SectionNode[0]/EntryMarker[0]", + "entityName" : "EntryMarker", + "entityKind" : "EllipseNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Experience[1]/ExperienceRail[2]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]/LayoutAnchorNode[0]/marker[0]/SectionNode[0]", + "childIndex" : 0, + "depth" : 17, + "layer" : 17, + "computedX" : 212.639, + "computedY" : 321.488, + "placementX" : 212.639, + "placementY" : 321.488, + "placementWidth" : 5.083, + "placementHeight" : 5.083, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 5.083, + "contentHeight" : 5.083, + "margin" : { + "top" : 1.751, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Experience[1]/ExperienceRail[2]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]", + "entityName" : null, + "entityKind" : "SectionNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Experience[1]/ExperienceRail[2]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]", + "childIndex" : 1, + "depth" : 12, + "layer" : 12, + "computedX" : 230.429, + "computedY" : 238.191, + "placementX" : 230.429, + "placementY" : 238.191, + "placementWidth" : 323.053, + "placementHeight" : 90.13, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 323.053, + "contentHeight" : 90.13, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Experience[1]/ExperienceRail[2]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]/EntryHeaderLayer[0]", + "entityName" : "EntryHeaderLayer", + "entityKind" : "LayerStackNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Experience[1]/ExperienceRail[2]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]", + "childIndex" : 0, + "depth" : 13, + "layer" : 13, + "computedX" : 230.429, + "computedY" : 316.703, + "placementX" : 230.429, + "placementY" : 316.703, + "placementWidth" : 323.053, + "placementHeight" : 11.618, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 323.053, + "contentHeight" : 11.618, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Experience[1]/ExperienceRail[2]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]/EntryHeaderLayer[0]/EntryHeaderHolder[0]", + "entityName" : "EntryHeaderHolder", + "entityKind" : "SectionNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Experience[1]/ExperienceRail[2]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]/EntryHeaderLayer[0]", + "childIndex" : 0, + "depth" : 14, + "layer" : 14, + "computedX" : 230.429, + "computedY" : 316.703, + "placementX" : 230.429, + "placementY" : 316.703, + "placementWidth" : 323.053, + "placementHeight" : 11.618, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 323.053, + "contentHeight" : 11.618, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Experience[1]/ExperienceRail[2]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]/EntryHeaderLayer[0]/EntryHeaderHolder[0]/EntryHeader[0]", + "entityName" : "EntryHeader", + "entityKind" : "RowNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Experience[1]/ExperienceRail[2]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]/EntryHeaderLayer[0]/EntryHeaderHolder[0]", + "childIndex" : 0, + "depth" : 15, + "layer" : 15, + "computedX" : 230.429, + "computedY" : 316.703, + "placementX" : 230.429, + "placementY" : 316.703, + "placementWidth" : 323.053, + "placementHeight" : 11.618, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 323.053, + "contentHeight" : 11.618, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Experience[1]/ExperienceRail[2]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]/EntryHeaderLayer[0]/EntryHeaderHolder[0]/EntryHeader[0]/EntryTitle[0]", + "entityName" : "EntryTitle", + "entityKind" : "ParagraphNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Experience[1]/ExperienceRail[2]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]/EntryHeaderLayer[0]/EntryHeaderHolder[0]/EntryHeader[0]", + "childIndex" : 0, + "depth" : 16, + "layer" : 16, + "computedX" : 230.429, + "computedY" : 316.703, + "placementX" : 230.429, + "placementY" : 316.703, + "placementWidth" : 116.531, + "placementHeight" : 11.618, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 116.531, + "contentHeight" : 11.618, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Experience[1]/ExperienceRail[2]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]/EntryHeaderLayer[0]/EntryHeaderHolder[0]/EntryHeader[0]/EntryPeriod[1]", + "entityName" : "EntryPeriod", + "entityKind" : "ParagraphNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Experience[1]/ExperienceRail[2]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]/EntryHeaderLayer[0]/EntryHeaderHolder[0]/EntryHeader[0]", + "childIndex" : 1, + "depth" : 16, + "layer" : 16, + "computedX" : 430.722, + "computedY" : 317.671, + "placementX" : 430.722, + "placementY" : 317.671, + "placementWidth" : 122.76, + "placementHeight" : 10.65, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 122.76, + "contentHeight" : 10.65, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Experience[1]/ExperienceRail[2]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]/EntrySubheaderLayer[1]", + "entityName" : "EntrySubheaderLayer", + "entityKind" : "LayerStackNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Experience[1]/ExperienceRail[2]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]", + "childIndex" : 1, + "depth" : 13, + "layer" : 13, + "computedX" : 230.429, + "computedY" : 303.003, + "placementX" : 230.429, + "placementY" : 303.003, + "placementWidth" : 323.053, + "placementHeight" : 13.7, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 323.053, + "contentHeight" : 13.7, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Experience[1]/ExperienceRail[2]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]/EntrySubheaderLayer[1]/EntrySubheaderHolder[0]", + "entityName" : "EntrySubheaderHolder", + "entityKind" : "SectionNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Experience[1]/ExperienceRail[2]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]/EntrySubheaderLayer[1]", + "childIndex" : 0, + "depth" : 14, + "layer" : 14, + "computedX" : 230.429, + "computedY" : 303.003, + "placementX" : 230.429, + "placementY" : 303.003, + "placementWidth" : 323.053, + "placementHeight" : 13.7, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 323.053, + "contentHeight" : 13.7, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Experience[1]/ExperienceRail[2]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]/EntrySubheaderLayer[1]/EntrySubheaderHolder[0]/EntrySubheader[0]", + "entityName" : "EntrySubheader", + "entityKind" : "RowNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Experience[1]/ExperienceRail[2]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]/EntrySubheaderLayer[1]/EntrySubheaderHolder[0]", + "childIndex" : 0, + "depth" : 15, + "layer" : 15, + "computedX" : 230.429, + "computedY" : 303.003, + "placementX" : 230.429, + "placementY" : 303.003, + "placementWidth" : 323.053, + "placementHeight" : 13.7, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 323.053, + "contentHeight" : 13.7, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Experience[1]/ExperienceRail[2]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]/EntrySubheaderLayer[1]/EntrySubheaderHolder[0]/EntrySubheader[0]/EntryCompany[0]", + "entityName" : "EntryCompany", + "entityKind" : "ParagraphNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Experience[1]/ExperienceRail[2]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]/EntrySubheaderLayer[1]/EntrySubheaderHolder[0]/EntrySubheader[0]", + "childIndex" : 0, + "depth" : 16, + "layer" : 16, + "computedX" : 230.429, + "computedY" : 303.003, + "placementX" : 230.429, + "placementY" : 303.003, + "placementWidth" : 93.845, + "placementHeight" : 10.65, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 93.845, + "contentHeight" : 10.65, + "margin" : { + "top" : 3.05, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Experience[1]/ExperienceRail[2]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]/EntrySubheaderLayer[1]/EntrySubheaderHolder[0]/EntrySubheader[0]/EntryLocation[1]", + "entityName" : "EntryLocation", + "entityKind" : "ParagraphNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Experience[1]/ExperienceRail[2]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]/EntrySubheaderLayer[1]/EntrySubheaderHolder[0]/EntrySubheader[0]", + "childIndex" : 1, + "depth" : 16, + "layer" : 16, + "computedX" : 430.722, + "computedY" : 303.003, + "placementX" : 430.722, + "placementY" : 303.003, + "placementWidth" : 122.76, + "placementHeight" : 10.65, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 122.76, + "contentHeight" : 10.65, + "margin" : { + "top" : 3.05, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Experience[1]/ExperienceRail[2]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]/EntryBullets[2]", + "entityName" : "EntryBullets", + "entityKind" : "ListNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Experience[1]/ExperienceRail[2]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]", + "childIndex" : 2, + "depth" : 13, + "layer" : 13, + "computedX" : 230.429, + "computedY" : 238.191, + "placementX" : 230.429, + "placementY" : 238.191, + "placementWidth" : 302.152, + "placementHeight" : 50.806, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 302.152, + "contentHeight" : 50.806, + "margin" : { + "top" : 14.006, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Achievements[2]", + "entityName" : "Achievements", + "entityKind" : "SectionNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]", + "childIndex" : 2, + "depth" : 4, + "layer" : 4, + "computedX" : 215.18, + "computedY" : 130.133, + "placementX" : 215.18, + "placementY" : 130.133, + "placementWidth" : 338.302, + "placementHeight" : 72.477, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 338.302, + "contentHeight" : 72.477, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Achievements[2]/MainHeadingACHIEVEMENTS[0]", + "entityName" : "MainHeadingACHIEVEMENTS", + "entityKind" : "ParagraphNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Achievements[2]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 215.18, + "computedY" : 189.055, + "placementX" : 215.18, + "placementY" : 189.055, + "placementWidth" : 80.244, + "placementHeight" : 13.555, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 80.244, + "contentHeight" : 13.555, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Achievements[2]/MainRuleACHIEVEMENTS[1]", + "entityName" : "MainRuleACHIEVEMENTS", + "entityKind" : "LineNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Achievements[2]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 215.18, + "computedY" : 183.932, + "placementX" : 215.18, + "placementY" : 183.932, + "placementWidth" : 338.302, + "placementHeight" : 1.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 338.302, + "contentHeight" : 1.0, + "margin" : { + "top" : 4.123, + "right" : 0.0, + "bottom" : 18.299, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Achievements[2]/AchievementCardsLayer[2]", + "entityName" : "AchievementCardsLayer", + "entityKind" : "LayerStackNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Achievements[2]", + "childIndex" : 2, + "depth" : 5, + "layer" : 5, + "computedX" : 215.18, + "computedY" : 130.133, + "placementX" : 215.18, + "placementY" : 130.133, + "placementWidth" : 338.302, + "placementHeight" : 35.5, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 338.302, + "contentHeight" : 35.5, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Achievements[2]/AchievementCardsLayer[2]/AchievementCardsHolder[0]", + "entityName" : "AchievementCardsHolder", + "entityKind" : "SectionNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Achievements[2]/AchievementCardsLayer[2]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 215.18, + "computedY" : 130.133, + "placementX" : 215.18, + "placementY" : 130.133, + "placementWidth" : 338.302, + "placementHeight" : 35.5, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 338.302, + "contentHeight" : 35.5, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Achievements[2]/AchievementCardsLayer[2]/AchievementCardsHolder[0]/AchievementCards[0]", + "entityName" : "AchievementCards", + "entityKind" : "RowNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Achievements[2]/AchievementCardsLayer[2]/AchievementCardsHolder[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 215.18, + "computedY" : 130.133, + "placementX" : 215.18, + "placementY" : 130.133, + "placementWidth" : 338.302, + "placementHeight" : 35.5, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 338.302, + "contentHeight" : 35.5, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Achievements[2]/AchievementCardsLayer[2]/AchievementCardsHolder[0]/AchievementCards[0]/AchievementCard[0]", + "entityName" : "AchievementCard", + "entityKind" : "SectionNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Achievements[2]/AchievementCardsLayer[2]/AchievementCardsHolder[0]/AchievementCards[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 215.18, + "computedY" : 130.133, + "placementX" : 215.18, + "placementY" : 130.133, + "placementWidth" : 112.767, + "placementHeight" : 35.5, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 112.767, + "contentHeight" : 35.5, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Achievements[2]/AchievementCardsLayer[2]/AchievementCardsHolder[0]/AchievementCards[0]/AchievementCard[0]/AchievementCardRowLayer[0]", + "entityName" : "AchievementCardRowLayer", + "entityKind" : "LayerStackNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Achievements[2]/AchievementCardsLayer[2]/AchievementCardsHolder[0]/AchievementCards[0]/AchievementCard[0]", + "childIndex" : 0, + "depth" : 9, + "layer" : 9, + "computedX" : 215.18, + "computedY" : 130.133, + "placementX" : 215.18, + "placementY" : 130.133, + "placementWidth" : 112.767, + "placementHeight" : 35.5, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 112.767, + "contentHeight" : 35.5, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Achievements[2]/AchievementCardsLayer[2]/AchievementCardsHolder[0]/AchievementCards[0]/AchievementCard[0]/AchievementCardRowLayer[0]/AchievementCardRowHolder[0]", + "entityName" : "AchievementCardRowHolder", + "entityKind" : "SectionNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Achievements[2]/AchievementCardsLayer[2]/AchievementCardsHolder[0]/AchievementCards[0]/AchievementCard[0]/AchievementCardRowLayer[0]", + "childIndex" : 0, + "depth" : 10, + "layer" : 10, + "computedX" : 215.18, + "computedY" : 130.133, + "placementX" : 215.18, + "placementY" : 130.133, + "placementWidth" : 112.767, + "placementHeight" : 35.5, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 112.767, + "contentHeight" : 35.5, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Achievements[2]/AchievementCardsLayer[2]/AchievementCardsHolder[0]/AchievementCards[0]/AchievementCard[0]/AchievementCardRowLayer[0]/AchievementCardRowHolder[0]/AchievementCardRow[0]", + "entityName" : "AchievementCardRow", + "entityKind" : "RowNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Achievements[2]/AchievementCardsLayer[2]/AchievementCardsHolder[0]/AchievementCards[0]/AchievementCard[0]/AchievementCardRowLayer[0]/AchievementCardRowHolder[0]", + "childIndex" : 0, + "depth" : 11, + "layer" : 11, + "computedX" : 215.18, + "computedY" : 130.133, + "placementX" : 215.18, + "placementY" : 130.133, + "placementWidth" : 112.767, + "placementHeight" : 35.5, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 112.767, + "contentHeight" : 35.5, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Achievements[2]/AchievementCardsLayer[2]/AchievementCardsHolder[0]/AchievementCards[0]/AchievementCard[0]/AchievementCardRowLayer[0]/AchievementCardRowHolder[0]/AchievementCardRow[0]/AchievementDiscCell[0]", + "entityName" : "AchievementDiscCell", + "entityKind" : "SectionNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Achievements[2]/AchievementCardsLayer[2]/AchievementCardsHolder[0]/AchievementCards[0]/AchievementCard[0]/AchievementCardRowLayer[0]/AchievementCardRowHolder[0]/AchievementCardRow[0]", + "childIndex" : 0, + "depth" : 12, + "layer" : 12, + "computedX" : 215.18, + "computedY" : 132.634, + "placementX" : 215.18, + "placementY" : 132.634, + "placementWidth" : 30.498, + "placementHeight" : 30.498, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 30.498, + "contentHeight" : 30.498, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Achievements[2]/AchievementCardsLayer[2]/AchievementCardsHolder[0]/AchievementCards[0]/AchievementCard[0]/AchievementCardRowLayer[0]/AchievementCardRowHolder[0]/AchievementCardRow[0]/AchievementDiscCell[0]/AchievementDisc[0]", + "entityName" : "AchievementDisc", + "entityKind" : "ShapeContainerNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Achievements[2]/AchievementCardsLayer[2]/AchievementCardsHolder[0]/AchievementCards[0]/AchievementCard[0]/AchievementCardRowLayer[0]/AchievementCardRowHolder[0]/AchievementCardRow[0]/AchievementDiscCell[0]", + "childIndex" : 0, + "depth" : 13, + "layer" : 13, + "computedX" : 215.18, + "computedY" : 132.634, + "placementX" : 215.18, + "placementY" : 132.634, + "placementWidth" : 30.498, + "placementHeight" : 30.498, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 30.498, + "contentHeight" : 30.498, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Achievements[2]/AchievementCardsLayer[2]/AchievementCardsHolder[0]/AchievementCards[0]/AchievementCard[0]/AchievementCardRowLayer[0]/AchievementCardRowHolder[0]/AchievementCardRow[0]/AchievementDiscCell[0]/AchievementDisc[0]/SvgIcon[0]", + "entityName" : "SvgIcon", + "entityKind" : "LayerStackNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Achievements[2]/AchievementCardsLayer[2]/AchievementCardsHolder[0]/AchievementCards[0]/AchievementCard[0]/AchievementCardRowLayer[0]/AchievementCardRowHolder[0]/AchievementCardRow[0]/AchievementDiscCell[0]/AchievementDisc[0]", + "childIndex" : 0, + "depth" : 14, + "layer" : 14, + "computedX" : 222.522, + "computedY" : 139.977, + "placementX" : 222.522, + "placementY" : 139.977, + "placementWidth" : 15.814, + "placementHeight" : 15.814, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 15.814, + "contentHeight" : 15.814, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Achievements[2]/AchievementCardsLayer[2]/AchievementCardsHolder[0]/AchievementCards[0]/AchievementCard[0]/AchievementCardRowLayer[0]/AchievementCardRowHolder[0]/AchievementCardRow[0]/AchievementDiscCell[0]/AchievementDisc[0]/SvgIcon[0]/SvgLayer0[0]", + "entityName" : "SvgLayer0", + "entityKind" : "Path", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Achievements[2]/AchievementCardsLayer[2]/AchievementCardsHolder[0]/AchievementCards[0]/AchievementCard[0]/AchievementCardRowLayer[0]/AchievementCardRowHolder[0]/AchievementCardRow[0]/AchievementDiscCell[0]/AchievementDisc[0]/SvgIcon[0]", + "childIndex" : 0, + "depth" : 15, + "layer" : 15, + "computedX" : 222.522, + "computedY" : 139.977, + "placementX" : 222.522, + "placementY" : 139.977, + "placementWidth" : 15.814, + "placementHeight" : 15.814, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 15.814, + "contentHeight" : 15.814, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Achievements[2]/AchievementCardsLayer[2]/AchievementCardsHolder[0]/AchievementCards[0]/AchievementCard[0]/AchievementCardRowLayer[0]/AchievementCardRowHolder[0]/AchievementCardRow[0]/AchievementText[1]", + "entityName" : "AchievementText", + "entityKind" : "ParagraphNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Achievements[2]/AchievementCardsLayer[2]/AchievementCardsHolder[0]/AchievementCards[0]/AchievementCard[0]/AchievementCardRowLayer[0]/AchievementCardRowHolder[0]/AchievementCardRow[0]", + "childIndex" : 1, + "depth" : 12, + "layer" : 12, + "computedX" : 253.02, + "computedY" : 130.133, + "placementX" : 253.02, + "placementY" : 130.133, + "placementWidth" : 71.4, + "placementHeight" : 35.5, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 71.4, + "contentHeight" : 35.5, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Achievements[2]/AchievementCardsLayer[2]/AchievementCardsHolder[0]/AchievementCards[0]/AchievementCard[1]", + "entityName" : "AchievementCard", + "entityKind" : "SectionNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Achievements[2]/AchievementCardsLayer[2]/AchievementCardsHolder[0]/AchievementCards[0]", + "childIndex" : 1, + "depth" : 8, + "layer" : 8, + "computedX" : 327.948, + "computedY" : 130.133, + "placementX" : 327.948, + "placementY" : 130.133, + "placementWidth" : 112.767, + "placementHeight" : 35.5, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 112.767, + "contentHeight" : 35.5, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Achievements[2]/AchievementCardsLayer[2]/AchievementCardsHolder[0]/AchievementCards[0]/AchievementCard[1]/AchievementCardRowLayer[0]", + "entityName" : "AchievementCardRowLayer", + "entityKind" : "LayerStackNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Achievements[2]/AchievementCardsLayer[2]/AchievementCardsHolder[0]/AchievementCards[0]/AchievementCard[1]", + "childIndex" : 0, + "depth" : 9, + "layer" : 9, + "computedX" : 327.948, + "computedY" : 130.133, + "placementX" : 327.948, + "placementY" : 130.133, + "placementWidth" : 112.767, + "placementHeight" : 35.5, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 112.767, + "contentHeight" : 35.5, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Achievements[2]/AchievementCardsLayer[2]/AchievementCardsHolder[0]/AchievementCards[0]/AchievementCard[1]/AchievementCardRowLayer[0]/AchievementCardRowHolder[0]", + "entityName" : "AchievementCardRowHolder", + "entityKind" : "SectionNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Achievements[2]/AchievementCardsLayer[2]/AchievementCardsHolder[0]/AchievementCards[0]/AchievementCard[1]/AchievementCardRowLayer[0]", + "childIndex" : 0, + "depth" : 10, + "layer" : 10, + "computedX" : 327.948, + "computedY" : 130.133, + "placementX" : 327.948, + "placementY" : 130.133, + "placementWidth" : 112.767, + "placementHeight" : 35.5, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 112.767, + "contentHeight" : 35.5, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Achievements[2]/AchievementCardsLayer[2]/AchievementCardsHolder[0]/AchievementCards[0]/AchievementCard[1]/AchievementCardRowLayer[0]/AchievementCardRowHolder[0]/AchievementCardRow[0]", + "entityName" : "AchievementCardRow", + "entityKind" : "RowNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Achievements[2]/AchievementCardsLayer[2]/AchievementCardsHolder[0]/AchievementCards[0]/AchievementCard[1]/AchievementCardRowLayer[0]/AchievementCardRowHolder[0]", + "childIndex" : 0, + "depth" : 11, + "layer" : 11, + "computedX" : 327.948, + "computedY" : 130.133, + "placementX" : 327.948, + "placementY" : 130.133, + "placementWidth" : 112.767, + "placementHeight" : 35.5, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 112.767, + "contentHeight" : 35.5, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Achievements[2]/AchievementCardsLayer[2]/AchievementCardsHolder[0]/AchievementCards[0]/AchievementCard[1]/AchievementCardRowLayer[0]/AchievementCardRowHolder[0]/AchievementCardRow[0]/AchievementDiscCell[0]", + "entityName" : "AchievementDiscCell", + "entityKind" : "SectionNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Achievements[2]/AchievementCardsLayer[2]/AchievementCardsHolder[0]/AchievementCards[0]/AchievementCard[1]/AchievementCardRowLayer[0]/AchievementCardRowHolder[0]/AchievementCardRow[0]", + "childIndex" : 0, + "depth" : 12, + "layer" : 12, + "computedX" : 327.948, + "computedY" : 132.634, + "placementX" : 327.948, + "placementY" : 132.634, + "placementWidth" : 30.498, + "placementHeight" : 30.498, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 30.498, + "contentHeight" : 30.498, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Achievements[2]/AchievementCardsLayer[2]/AchievementCardsHolder[0]/AchievementCards[0]/AchievementCard[1]/AchievementCardRowLayer[0]/AchievementCardRowHolder[0]/AchievementCardRow[0]/AchievementDiscCell[0]/AchievementDisc[0]", + "entityName" : "AchievementDisc", + "entityKind" : "ShapeContainerNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Achievements[2]/AchievementCardsLayer[2]/AchievementCardsHolder[0]/AchievementCards[0]/AchievementCard[1]/AchievementCardRowLayer[0]/AchievementCardRowHolder[0]/AchievementCardRow[0]/AchievementDiscCell[0]", + "childIndex" : 0, + "depth" : 13, + "layer" : 13, + "computedX" : 327.948, + "computedY" : 132.634, + "placementX" : 327.948, + "placementY" : 132.634, + "placementWidth" : 30.498, + "placementHeight" : 30.498, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 30.498, + "contentHeight" : 30.498, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Achievements[2]/AchievementCardsLayer[2]/AchievementCardsHolder[0]/AchievementCards[0]/AchievementCard[1]/AchievementCardRowLayer[0]/AchievementCardRowHolder[0]/AchievementCardRow[0]/AchievementDiscCell[0]/AchievementDisc[0]/SvgIcon[0]", + "entityName" : "SvgIcon", + "entityKind" : "LayerStackNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Achievements[2]/AchievementCardsLayer[2]/AchievementCardsHolder[0]/AchievementCards[0]/AchievementCard[1]/AchievementCardRowLayer[0]/AchievementCardRowHolder[0]/AchievementCardRow[0]/AchievementDiscCell[0]/AchievementDisc[0]", + "childIndex" : 0, + "depth" : 14, + "layer" : 14, + "computedX" : 335.29, + "computedY" : 139.977, + "placementX" : 335.29, + "placementY" : 139.977, + "placementWidth" : 15.814, + "placementHeight" : 15.814, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 15.814, + "contentHeight" : 15.814, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Achievements[2]/AchievementCardsLayer[2]/AchievementCardsHolder[0]/AchievementCards[0]/AchievementCard[1]/AchievementCardRowLayer[0]/AchievementCardRowHolder[0]/AchievementCardRow[0]/AchievementDiscCell[0]/AchievementDisc[0]/SvgIcon[0]/SvgLayer0[0]", + "entityName" : "SvgLayer0", + "entityKind" : "Path", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Achievements[2]/AchievementCardsLayer[2]/AchievementCardsHolder[0]/AchievementCards[0]/AchievementCard[1]/AchievementCardRowLayer[0]/AchievementCardRowHolder[0]/AchievementCardRow[0]/AchievementDiscCell[0]/AchievementDisc[0]/SvgIcon[0]", + "childIndex" : 0, + "depth" : 15, + "layer" : 15, + "computedX" : 335.29, + "computedY" : 139.977, + "placementX" : 335.29, + "placementY" : 139.977, + "placementWidth" : 15.814, + "placementHeight" : 15.814, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 15.814, + "contentHeight" : 15.814, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Achievements[2]/AchievementCardsLayer[2]/AchievementCardsHolder[0]/AchievementCards[0]/AchievementCard[1]/AchievementCardRowLayer[0]/AchievementCardRowHolder[0]/AchievementCardRow[0]/AchievementDiscCell[0]/AchievementDisc[0]/SvgIcon[0]/SvgLayer1[1]", + "entityName" : "SvgLayer1", + "entityKind" : "Path", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Achievements[2]/AchievementCardsLayer[2]/AchievementCardsHolder[0]/AchievementCards[0]/AchievementCard[1]/AchievementCardRowLayer[0]/AchievementCardRowHolder[0]/AchievementCardRow[0]/AchievementDiscCell[0]/AchievementDisc[0]/SvgIcon[0]", + "childIndex" : 1, + "depth" : 15, + "layer" : 15, + "computedX" : 335.29, + "computedY" : 139.977, + "placementX" : 335.29, + "placementY" : 139.977, + "placementWidth" : 15.814, + "placementHeight" : 15.814, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 15.814, + "contentHeight" : 15.814, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Achievements[2]/AchievementCardsLayer[2]/AchievementCardsHolder[0]/AchievementCards[0]/AchievementCard[1]/AchievementCardRowLayer[0]/AchievementCardRowHolder[0]/AchievementCardRow[0]/AchievementText[1]", + "entityName" : "AchievementText", + "entityKind" : "ParagraphNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Achievements[2]/AchievementCardsLayer[2]/AchievementCardsHolder[0]/AchievementCards[0]/AchievementCard[1]/AchievementCardRowLayer[0]/AchievementCardRowHolder[0]/AchievementCardRow[0]", + "childIndex" : 1, + "depth" : 12, + "layer" : 12, + "computedX" : 365.788, + "computedY" : 130.133, + "placementX" : 365.788, + "placementY" : 130.133, + "placementWidth" : 71.018, + "placementHeight" : 35.5, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 71.018, + "contentHeight" : 35.5, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Achievements[2]/AchievementCardsLayer[2]/AchievementCardsHolder[0]/AchievementCards[0]/AchievementCard[2]", + "entityName" : "AchievementCard", + "entityKind" : "SectionNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Achievements[2]/AchievementCardsLayer[2]/AchievementCardsHolder[0]/AchievementCards[0]", + "childIndex" : 2, + "depth" : 8, + "layer" : 8, + "computedX" : 440.715, + "computedY" : 135.136, + "placementX" : 440.715, + "placementY" : 135.136, + "placementWidth" : 112.767, + "placementHeight" : 30.498, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 112.767, + "contentHeight" : 30.498, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Achievements[2]/AchievementCardsLayer[2]/AchievementCardsHolder[0]/AchievementCards[0]/AchievementCard[2]/AchievementCardRowLayer[0]", + "entityName" : "AchievementCardRowLayer", + "entityKind" : "LayerStackNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Achievements[2]/AchievementCardsLayer[2]/AchievementCardsHolder[0]/AchievementCards[0]/AchievementCard[2]", + "childIndex" : 0, + "depth" : 9, + "layer" : 9, + "computedX" : 440.715, + "computedY" : 135.136, + "placementX" : 440.715, + "placementY" : 135.136, + "placementWidth" : 112.767, + "placementHeight" : 30.498, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 112.767, + "contentHeight" : 30.498, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Achievements[2]/AchievementCardsLayer[2]/AchievementCardsHolder[0]/AchievementCards[0]/AchievementCard[2]/AchievementCardRowLayer[0]/AchievementCardRowHolder[0]", + "entityName" : "AchievementCardRowHolder", + "entityKind" : "SectionNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Achievements[2]/AchievementCardsLayer[2]/AchievementCardsHolder[0]/AchievementCards[0]/AchievementCard[2]/AchievementCardRowLayer[0]", + "childIndex" : 0, + "depth" : 10, + "layer" : 10, + "computedX" : 440.715, + "computedY" : 135.136, + "placementX" : 440.715, + "placementY" : 135.136, + "placementWidth" : 112.767, + "placementHeight" : 30.498, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 112.767, + "contentHeight" : 30.498, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Achievements[2]/AchievementCardsLayer[2]/AchievementCardsHolder[0]/AchievementCards[0]/AchievementCard[2]/AchievementCardRowLayer[0]/AchievementCardRowHolder[0]/AchievementCardRow[0]", + "entityName" : "AchievementCardRow", + "entityKind" : "RowNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Achievements[2]/AchievementCardsLayer[2]/AchievementCardsHolder[0]/AchievementCards[0]/AchievementCard[2]/AchievementCardRowLayer[0]/AchievementCardRowHolder[0]", + "childIndex" : 0, + "depth" : 11, + "layer" : 11, + "computedX" : 440.715, + "computedY" : 135.136, + "placementX" : 440.715, + "placementY" : 135.136, + "placementWidth" : 112.767, + "placementHeight" : 30.498, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 112.767, + "contentHeight" : 30.498, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Achievements[2]/AchievementCardsLayer[2]/AchievementCardsHolder[0]/AchievementCards[0]/AchievementCard[2]/AchievementCardRowLayer[0]/AchievementCardRowHolder[0]/AchievementCardRow[0]/AchievementDiscCell[0]", + "entityName" : "AchievementDiscCell", + "entityKind" : "SectionNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Achievements[2]/AchievementCardsLayer[2]/AchievementCardsHolder[0]/AchievementCards[0]/AchievementCard[2]/AchievementCardRowLayer[0]/AchievementCardRowHolder[0]/AchievementCardRow[0]", + "childIndex" : 0, + "depth" : 12, + "layer" : 12, + "computedX" : 440.715, + "computedY" : 135.136, + "placementX" : 440.715, + "placementY" : 135.136, + "placementWidth" : 30.498, + "placementHeight" : 30.498, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 30.498, + "contentHeight" : 30.498, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Achievements[2]/AchievementCardsLayer[2]/AchievementCardsHolder[0]/AchievementCards[0]/AchievementCard[2]/AchievementCardRowLayer[0]/AchievementCardRowHolder[0]/AchievementCardRow[0]/AchievementDiscCell[0]/AchievementDisc[0]", + "entityName" : "AchievementDisc", + "entityKind" : "ShapeContainerNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Achievements[2]/AchievementCardsLayer[2]/AchievementCardsHolder[0]/AchievementCards[0]/AchievementCard[2]/AchievementCardRowLayer[0]/AchievementCardRowHolder[0]/AchievementCardRow[0]/AchievementDiscCell[0]", + "childIndex" : 0, + "depth" : 13, + "layer" : 13, + "computedX" : 440.715, + "computedY" : 135.136, + "placementX" : 440.715, + "placementY" : 135.136, + "placementWidth" : 30.498, + "placementHeight" : 30.498, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 30.498, + "contentHeight" : 30.498, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Achievements[2]/AchievementCardsLayer[2]/AchievementCardsHolder[0]/AchievementCards[0]/AchievementCard[2]/AchievementCardRowLayer[0]/AchievementCardRowHolder[0]/AchievementCardRow[0]/AchievementDiscCell[0]/AchievementDisc[0]/SvgIcon[0]", + "entityName" : "SvgIcon", + "entityKind" : "LayerStackNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Achievements[2]/AchievementCardsLayer[2]/AchievementCardsHolder[0]/AchievementCards[0]/AchievementCard[2]/AchievementCardRowLayer[0]/AchievementCardRowHolder[0]/AchievementCardRow[0]/AchievementDiscCell[0]/AchievementDisc[0]", + "childIndex" : 0, + "depth" : 14, + "layer" : 14, + "computedX" : 448.057, + "computedY" : 142.478, + "placementX" : 448.057, + "placementY" : 142.478, + "placementWidth" : 15.814, + "placementHeight" : 15.814, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 15.814, + "contentHeight" : 15.814, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Achievements[2]/AchievementCardsLayer[2]/AchievementCardsHolder[0]/AchievementCards[0]/AchievementCard[2]/AchievementCardRowLayer[0]/AchievementCardRowHolder[0]/AchievementCardRow[0]/AchievementDiscCell[0]/AchievementDisc[0]/SvgIcon[0]/SvgLayer0[0]", + "entityName" : "SvgLayer0", + "entityKind" : "Path", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Achievements[2]/AchievementCardsLayer[2]/AchievementCardsHolder[0]/AchievementCards[0]/AchievementCard[2]/AchievementCardRowLayer[0]/AchievementCardRowHolder[0]/AchievementCardRow[0]/AchievementDiscCell[0]/AchievementDisc[0]/SvgIcon[0]", + "childIndex" : 0, + "depth" : 15, + "layer" : 15, + "computedX" : 448.057, + "computedY" : 142.478, + "placementX" : 448.057, + "placementY" : 142.478, + "placementWidth" : 15.814, + "placementHeight" : 15.814, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 15.814, + "contentHeight" : 15.814, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Achievements[2]/AchievementCardsLayer[2]/AchievementCardsHolder[0]/AchievementCards[0]/AchievementCard[2]/AchievementCardRowLayer[0]/AchievementCardRowHolder[0]/AchievementCardRow[0]/AchievementDiscCell[0]/AchievementDisc[0]/SvgIcon[0]/SvgLayer1[1]", + "entityName" : "SvgLayer1", + "entityKind" : "Path", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Achievements[2]/AchievementCardsLayer[2]/AchievementCardsHolder[0]/AchievementCards[0]/AchievementCard[2]/AchievementCardRowLayer[0]/AchievementCardRowHolder[0]/AchievementCardRow[0]/AchievementDiscCell[0]/AchievementDisc[0]/SvgIcon[0]", + "childIndex" : 1, + "depth" : 15, + "layer" : 15, + "computedX" : 448.057, + "computedY" : 142.478, + "placementX" : 448.057, + "placementY" : 142.478, + "placementWidth" : 15.814, + "placementHeight" : 15.814, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 15.814, + "contentHeight" : 15.814, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Achievements[2]/AchievementCardsLayer[2]/AchievementCardsHolder[0]/AchievementCards[0]/AchievementCard[2]/AchievementCardRowLayer[0]/AchievementCardRowHolder[0]/AchievementCardRow[0]/AchievementText[1]", + "entityName" : "AchievementText", + "entityKind" : "ParagraphNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Achievements[2]/AchievementCardsLayer[2]/AchievementCardsHolder[0]/AchievementCards[0]/AchievementCard[2]/AchievementCardRowLayer[0]/AchievementCardRowHolder[0]/AchievementCardRow[0]", + "childIndex" : 1, + "depth" : 12, + "layer" : 12, + "computedX" : 478.555, + "computedY" : 138.847, + "placementX" : 478.555, + "placementY" : 138.847, + "placementWidth" : 72.092, + "placementHeight" : 23.075, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 72.092, + "contentHeight" : 23.075, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Certifications[3]", + "entityName" : "Certifications", + "entityKind" : "SectionNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]", + "childIndex" : 3, + "depth" : 4, + "layer" : 4, + "computedX" : 215.18, + "computedY" : 28.659, + "placementX" : 215.18, + "placementY" : 28.659, + "placementWidth" : 338.302, + "placementHeight" : 73.236, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 338.302, + "contentHeight" : 73.236, + "margin" : { + "top" : -7.342, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Certifications[3]/MainHeadingCERTIFICATIONS[0]", + "entityName" : "MainHeadingCERTIFICATIONS", + "entityKind" : "ParagraphNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Certifications[3]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 215.18, + "computedY" : 88.34, + "placementX" : 215.18, + "placementY" : 88.34, + "placementWidth" : 84.547, + "placementHeight" : 13.555, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 84.547, + "contentHeight" : 13.555, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Certifications[3]/MainRuleCERTIFICATIONS[1]", + "entityName" : "MainRuleCERTIFICATIONS", + "entityKind" : "LineNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Certifications[3]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 215.18, + "computedY" : 83.087, + "placementX" : 215.18, + "placementY" : 83.087, + "placementWidth" : 338.302, + "placementHeight" : 1.13, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 338.302, + "contentHeight" : 1.13, + "margin" : { + "top" : 4.123, + "right" : 0.0, + "bottom" : 16.04, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Certifications[3]/CertificationColumnsLayer[2]", + "entityName" : "CertificationColumnsLayer", + "entityKind" : "LayerStackNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Certifications[3]", + "childIndex" : 2, + "depth" : 5, + "layer" : 5, + "computedX" : 215.18, + "computedY" : 28.659, + "placementX" : 215.18, + "placementY" : 28.659, + "placementWidth" : 338.302, + "placementHeight" : 38.389, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 338.302, + "contentHeight" : 38.389, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Certifications[3]/CertificationColumnsLayer[2]/CertificationColumnsHolder[0]", + "entityName" : "CertificationColumnsHolder", + "entityKind" : "SectionNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Certifications[3]/CertificationColumnsLayer[2]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 215.18, + "computedY" : 28.659, + "placementX" : 215.18, + "placementY" : 28.659, + "placementWidth" : 338.302, + "placementHeight" : 38.389, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 338.302, + "contentHeight" : 38.389, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Certifications[3]/CertificationColumnsLayer[2]/CertificationColumnsHolder[0]/CertificationColumns[0]", + "entityName" : "CertificationColumns", + "entityKind" : "RowNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Certifications[3]/CertificationColumnsLayer[2]/CertificationColumnsHolder[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 215.18, + "computedY" : 28.659, + "placementX" : 215.18, + "placementY" : 28.659, + "placementWidth" : 338.302, + "placementHeight" : 38.389, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 338.302, + "contentHeight" : 38.389, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Certifications[3]/CertificationColumnsLayer[2]/CertificationColumnsHolder[0]/CertificationColumns[0]/CertificationColumn[0]", + "entityName" : "CertificationColumn", + "entityKind" : "SectionNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Certifications[3]/CertificationColumnsLayer[2]/CertificationColumnsHolder[0]/CertificationColumns[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 215.18, + "computedY" : 28.659, + "placementX" : 215.18, + "placementY" : 28.659, + "placementWidth" : 111.363, + "placementHeight" : 38.389, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 111.363, + "contentHeight" : 38.389, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Certifications[3]/CertificationColumnsLayer[2]/CertificationColumnsHolder[0]/CertificationColumns[0]/CertificationColumn[0]/CertificationTitle[0]", + "entityName" : "CertificationTitle", + "entityKind" : "ParagraphNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Certifications[3]/CertificationColumnsLayer[2]/CertificationColumnsHolder[0]/CertificationColumns[0]/CertificationColumn[0]", + "childIndex" : 0, + "depth" : 9, + "layer" : 9, + "computedX" : 215.18, + "computedY" : 56.397, + "placementX" : 215.18, + "placementY" : 56.397, + "placementWidth" : 111.363, + "placementHeight" : 10.65, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 111.363, + "contentHeight" : 10.65, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Certifications[3]/CertificationColumnsLayer[2]/CertificationColumnsHolder[0]/CertificationColumns[0]/CertificationColumn[0]/CertificationIssuer[1]", + "entityName" : "CertificationIssuer", + "entityKind" : "ParagraphNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Certifications[3]/CertificationColumnsLayer[2]/CertificationColumnsHolder[0]/CertificationColumns[0]/CertificationColumn[0]", + "childIndex" : 1, + "depth" : 9, + "layer" : 9, + "computedX" : 227.605, + "computedY" : 42.528, + "placementX" : 227.605, + "placementY" : 42.528, + "placementWidth" : 25.844, + "placementHeight" : 10.65, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 25.844, + "contentHeight" : 10.65, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 12.425 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Certifications[3]/CertificationColumnsLayer[2]/CertificationColumnsHolder[0]/CertificationColumns[0]/CertificationColumn[0]/CertificationYear[2]", + "entityName" : "CertificationYear", + "entityKind" : "ParagraphNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Certifications[3]/CertificationColumnsLayer[2]/CertificationColumnsHolder[0]/CertificationColumns[0]/CertificationColumn[0]", + "childIndex" : 2, + "depth" : 9, + "layer" : 9, + "computedX" : 227.605, + "computedY" : 28.659, + "placementX" : 227.605, + "placementY" : 28.659, + "placementWidth" : 19.543, + "placementHeight" : 10.65, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 19.543, + "contentHeight" : 10.65, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 12.425 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Certifications[3]/CertificationColumnsLayer[2]/CertificationColumnsHolder[0]/CertificationColumns[0]/CertificationColumn[1]", + "entityName" : "CertificationColumn", + "entityKind" : "SectionNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Certifications[3]/CertificationColumnsLayer[2]/CertificationColumnsHolder[0]/CertificationColumns[0]", + "childIndex" : 1, + "depth" : 8, + "layer" : 8, + "computedX" : 384.331, + "computedY" : 28.659, + "placementX" : 384.331, + "placementY" : 28.659, + "placementWidth" : 167.58, + "placementHeight" : 38.389, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 167.58, + "contentHeight" : 38.389, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 13.555 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Certifications[3]/CertificationColumnsLayer[2]/CertificationColumnsHolder[0]/CertificationColumns[0]/CertificationColumn[1]/CertificationTitle[0]", + "entityName" : "CertificationTitle", + "entityKind" : "ParagraphNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Certifications[3]/CertificationColumnsLayer[2]/CertificationColumnsHolder[0]/CertificationColumns[0]/CertificationColumn[1]", + "childIndex" : 0, + "depth" : 9, + "layer" : 9, + "computedX" : 397.886, + "computedY" : 56.397, + "placementX" : 397.886, + "placementY" : 56.397, + "placementWidth" : 154.025, + "placementHeight" : 10.65, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 154.025, + "contentHeight" : 10.65, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Certifications[3]/CertificationColumnsLayer[2]/CertificationColumnsHolder[0]/CertificationColumns[0]/CertificationColumn[1]/CertificationIssuer[1]", + "entityName" : "CertificationIssuer", + "entityKind" : "ParagraphNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Certifications[3]/CertificationColumnsLayer[2]/CertificationColumnsHolder[0]/CertificationColumns[0]/CertificationColumn[1]", + "childIndex" : 1, + "depth" : 9, + "layer" : 9, + "computedX" : 410.311, + "computedY" : 42.528, + "placementX" : 410.311, + "placementY" : 42.528, + "placementWidth" : 69.039, + "placementHeight" : 10.65, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 69.039, + "contentHeight" : 10.65, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 12.425 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Certifications[3]/CertificationColumnsLayer[2]/CertificationColumnsHolder[0]/CertificationColumns[0]/CertificationColumn[1]/CertificationYear[2]", + "entityName" : "CertificationYear", + "entityKind" : "ParagraphNode", + "parentPath" : "MidnightNavyCv[0]/PageGrid[0]/Main[1]/Certifications[3]/CertificationColumnsLayer[2]/CertificationColumnsHolder[0]/CertificationColumns[0]/CertificationColumn[1]", + "childIndex" : 2, + "depth" : 9, + "layer" : 9, + "computedX" : 410.311, + "computedY" : 28.659, + "placementX" : 410.311, + "placementY" : 28.659, + "placementWidth" : 17.635, + "placementHeight" : 10.65, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 17.635, + "contentHeight" : 10.65, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 12.425 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + } ] +} diff --git a/qa/src/test/resources/layout-snapshots/canonical-templates/cv-v2/navy_sidebar_layout.json b/qa/src/test/resources/layout-snapshots/canonical-templates/cv-v2/navy_sidebar_layout.json new file mode 100644 index 000000000..f467275c7 --- /dev/null +++ b/qa/src/test/resources/layout-snapshots/canonical-templates/cv-v2/navy_sidebar_layout.json @@ -0,0 +1,3557 @@ +{ + "formatVersion" : "2.0", + "canvas" : { + "pageWidth" : 595.276, + "pageHeight" : 841.89, + "innerWidth" : 595.276, + "innerHeight" : 841.89, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, + "totalPages" : 1, + "nodes" : [ { + "path" : "NavySidebarCv[0]", + "entityName" : "NavySidebarCv", + "entityKind" : "ContainerNode", + "parentPath" : null, + "childIndex" : 0, + "depth" : 1, + "layer" : 1, + "computedX" : 0.0, + "computedY" : 1.125, + "placementX" : 0.0, + "placementY" : 1.125, + "placementWidth" : 595.276, + "placementHeight" : 840.765, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 595.276, + "contentHeight" : 840.765, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "NavySidebarCv[0]/PageGrid[0]", + "entityName" : "PageGrid", + "entityKind" : "RowNode", + "parentPath" : "NavySidebarCv[0]", + "childIndex" : 0, + "depth" : 2, + "layer" : 2, + "computedX" : 0.0, + "computedY" : 1.125, + "placementX" : 0.0, + "placementY" : 1.125, + "placementWidth" : 595.276, + "placementHeight" : 840.765, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 595.276, + "contentHeight" : 840.765, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "NavySidebarCv[0]/PageGrid[0]/Sidebar[0]", + "entityName" : "Sidebar", + "entityKind" : "SectionNode", + "parentPath" : "NavySidebarCv[0]/PageGrid[0]", + "childIndex" : 0, + "depth" : 3, + "layer" : 3, + "computedX" : 0.0, + "computedY" : 48.043, + "placementX" : 0.0, + "placementY" : 48.043, + "placementWidth" : 176.797, + "placementHeight" : 793.847, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 176.797, + "contentHeight" : 793.847, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 25.0, + "right" : 26.5, + "bottom" : 36.5, + "left" : 26.5 + } + }, { + "path" : "NavySidebarCv[0]/PageGrid[0]/Sidebar[0]/Avatar[0]", + "entityName" : "Avatar", + "entityKind" : "SectionNode", + "parentPath" : "NavySidebarCv[0]/PageGrid[0]/Sidebar[0]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 26.5, + "computedY" : 667.893, + "placementX" : 26.5, + "placementY" : 667.893, + "placementWidth" : 123.797, + "placementHeight" : 148.997, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 123.797, + "contentHeight" : 148.997, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 22.0, + "left" : 0.0 + } + }, { + "path" : "NavySidebarCv[0]/PageGrid[0]/Sidebar[0]/Avatar[0]/AvatarRing[0]", + "entityName" : "AvatarRing", + "entityKind" : "ShapeContainerNode", + "parentPath" : "NavySidebarCv[0]/PageGrid[0]/Sidebar[0]/Avatar[0]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 26.5, + "computedY" : 689.893, + "placementX" : 26.5, + "placementY" : 689.893, + "placementWidth" : 126.997, + "placementHeight" : 126.997, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 126.997, + "contentHeight" : 126.997, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "NavySidebarCv[0]/PageGrid[0]/Sidebar[0]/Avatar[0]/AvatarRing[0]/AvatarCircle[0]", + "entityName" : "AvatarCircle", + "entityKind" : "ShapeContainerNode", + "parentPath" : "NavySidebarCv[0]/PageGrid[0]/Sidebar[0]/Avatar[0]/AvatarRing[0]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 28.1, + "computedY" : 691.493, + "placementX" : 28.1, + "placementY" : 691.493, + "placementWidth" : 123.797, + "placementHeight" : 123.797, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 123.797, + "contentHeight" : 123.797, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "NavySidebarCv[0]/PageGrid[0]/Sidebar[0]/Avatar[0]/AvatarRing[0]/AvatarCircle[0]/AvatarPhoto[0]", + "entityName" : "AvatarPhoto", + "entityKind" : "ImageNode", + "parentPath" : "NavySidebarCv[0]/PageGrid[0]/Sidebar[0]/Avatar[0]/AvatarRing[0]/AvatarCircle[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 28.1, + "computedY" : 691.493, + "placementX" : 28.1, + "placementY" : 691.493, + "placementWidth" : 123.797, + "placementHeight" : 123.797, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 123.797, + "contentHeight" : 123.797, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "NavySidebarCv[0]/PageGrid[0]/Sidebar[0]/Contact[1]", + "entityName" : "Contact", + "entityKind" : "SectionNode", + "parentPath" : "NavySidebarCv[0]/PageGrid[0]/Sidebar[0]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 26.5, + "computedY" : 550.093, + "placementX" : 26.5, + "placementY" : 550.093, + "placementWidth" : 123.797, + "placementHeight" : 117.8, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 123.797, + "contentHeight" : 117.8, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "NavySidebarCv[0]/PageGrid[0]/Sidebar[0]/Contact[1]/SidebarHeading_Contact[0]", + "entityName" : "SidebarHeading_Contact", + "entityKind" : "ParagraphNode", + "parentPath" : "NavySidebarCv[0]/PageGrid[0]/Sidebar[0]/Contact[1]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 26.5, + "computedY" : 653.493, + "placementX" : 26.5, + "placementY" : 653.493, + "placementWidth" : 69.473, + "placementHeight" : 14.4, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 69.473, + "contentHeight" : 14.4, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 7.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "NavySidebarCv[0]/PageGrid[0]/Sidebar[0]/Contact[1]/SidebarRule_Contact[1]", + "entityName" : "SidebarRule_Contact", + "entityKind" : "LineNode", + "parentPath" : "NavySidebarCv[0]/PageGrid[0]/Sidebar[0]/Contact[1]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 26.5, + "computedY" : 645.493, + "placementX" : 26.5, + "placementY" : 645.493, + "placementWidth" : 123.797, + "placementHeight" : 1.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 123.797, + "contentHeight" : 1.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 10.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "NavySidebarCv[0]/PageGrid[0]/Sidebar[0]/Contact[1]/Contact_phone[2]", + "entityName" : "Contact_phone", + "entityKind" : "ParagraphNode", + "parentPath" : "NavySidebarCv[0]/PageGrid[0]/Sidebar[0]/Contact[1]", + "childIndex" : 2, + "depth" : 5, + "layer" : 5, + "computedX" : 26.5, + "computedY" : 624.693, + "placementX" : 26.5, + "placementY" : 624.693, + "placementWidth" : 92.314, + "placementHeight" : 10.8, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 92.314, + "contentHeight" : 10.8, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 10.5, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "NavySidebarCv[0]/PageGrid[0]/Sidebar[0]/Contact[1]/Contact_email[3]", + "entityName" : "Contact_email", + "entityKind" : "ParagraphNode", + "parentPath" : "NavySidebarCv[0]/PageGrid[0]/Sidebar[0]/Contact[1]", + "childIndex" : 3, + "depth" : 5, + "layer" : 5, + "computedX" : 26.5, + "computedY" : 603.393, + "placementX" : 26.5, + "placementY" : 603.393, + "placementWidth" : 105.31, + "placementHeight" : 10.8, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 105.31, + "contentHeight" : 10.8, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 10.5, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "NavySidebarCv[0]/PageGrid[0]/Sidebar[0]/Contact[1]/Contact_location[4]", + "entityName" : "Contact_location", + "entityKind" : "ParagraphNode", + "parentPath" : "NavySidebarCv[0]/PageGrid[0]/Sidebar[0]/Contact[1]", + "childIndex" : 4, + "depth" : 5, + "layer" : 5, + "computedX" : 26.5, + "computedY" : 581.893, + "placementX" : 26.5, + "placementY" : 581.893, + "placementWidth" : 96.275, + "placementHeight" : 11.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 96.275, + "contentHeight" : 11.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 10.5, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "NavySidebarCv[0]/PageGrid[0]/Sidebar[0]/Contact[1]/Contact_linkedin[5]", + "entityName" : "Contact_linkedin", + "entityKind" : "ParagraphNode", + "parentPath" : "NavySidebarCv[0]/PageGrid[0]/Sidebar[0]/Contact[1]", + "childIndex" : 5, + "depth" : 5, + "layer" : 5, + "computedX" : 26.5, + "computedY" : 560.593, + "placementX" : 26.5, + "placementY" : 560.593, + "placementWidth" : 122.217, + "placementHeight" : 10.8, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 122.217, + "contentHeight" : 10.8, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 10.5, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "NavySidebarCv[0]/PageGrid[0]/Sidebar[0]/Education[2]", + "entityName" : "Education", + "entityKind" : "SectionNode", + "parentPath" : "NavySidebarCv[0]/PageGrid[0]/Sidebar[0]", + "childIndex" : 2, + "depth" : 4, + "layer" : 4, + "computedX" : 26.5, + "computedY" : 348.993, + "placementX" : 26.5, + "placementY" : 348.993, + "placementWidth" : 123.797, + "placementHeight" : 201.1, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 123.797, + "contentHeight" : 201.1, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "NavySidebarCv[0]/PageGrid[0]/Sidebar[0]/Education[2]/SidebarHeading_Education[0]", + "entityName" : "SidebarHeading_Education", + "entityKind" : "ParagraphNode", + "parentPath" : "NavySidebarCv[0]/PageGrid[0]/Sidebar[0]/Education[2]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 26.5, + "computedY" : 517.693, + "placementX" : 26.5, + "placementY" : 517.693, + "placementWidth" : 86.807, + "placementHeight" : 14.4, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 86.807, + "contentHeight" : 14.4, + "margin" : { + "top" : 18.0, + "right" : 0.0, + "bottom" : 7.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "NavySidebarCv[0]/PageGrid[0]/Sidebar[0]/Education[2]/SidebarRule_Education[1]", + "entityName" : "SidebarRule_Education", + "entityKind" : "LineNode", + "parentPath" : "NavySidebarCv[0]/PageGrid[0]/Sidebar[0]/Education[2]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 26.5, + "computedY" : 509.693, + "placementX" : 26.5, + "placementY" : 509.693, + "placementWidth" : 123.797, + "placementHeight" : 1.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 123.797, + "contentHeight" : 1.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 10.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "NavySidebarCv[0]/PageGrid[0]/Sidebar[0]/Education[2]/Degree_MasterofScienceinMarketing[2]", + "entityName" : "Degree_MasterofScienceinMarketing", + "entityKind" : "ParagraphNode", + "parentPath" : "NavySidebarCv[0]/PageGrid[0]/Sidebar[0]/Education[2]", + "childIndex" : 2, + "depth" : 5, + "layer" : 5, + "computedX" : 26.5, + "computedY" : 476.743, + "placementX" : 26.5, + "placementY" : 476.743, + "placementWidth" : 101.421, + "placementHeight" : 22.95, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 101.421, + "contentHeight" : 22.95, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 4.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "NavySidebarCv[0]/PageGrid[0]/Sidebar[0]/Education[2]/Institution_MasterofScienceinMarketing[3]", + "entityName" : "Institution_MasterofScienceinMarketing", + "entityKind" : "ParagraphNode", + "parentPath" : "NavySidebarCv[0]/PageGrid[0]/Sidebar[0]/Education[2]", + "childIndex" : 3, + "depth" : 5, + "layer" : 5, + "computedX" : 26.5, + "computedY" : 461.943, + "placementX" : 26.5, + "placementY" : 461.943, + "placementWidth" : 82.287, + "placementHeight" : 10.8, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 82.287, + "contentHeight" : 10.8, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 1.5, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "NavySidebarCv[0]/PageGrid[0]/Sidebar[0]/Education[2]/Campus_MasterofScienceinMarketing[4]", + "entityName" : "Campus_MasterofScienceinMarketing", + "entityKind" : "ParagraphNode", + "parentPath" : "NavySidebarCv[0]/PageGrid[0]/Sidebar[0]/Education[2]", + "childIndex" : 4, + "depth" : 5, + "layer" : 5, + "computedX" : 26.5, + "computedY" : 449.643, + "placementX" : 26.5, + "placementY" : 449.643, + "placementWidth" : 56.412, + "placementHeight" : 10.8, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 56.412, + "contentHeight" : 10.8, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 1.5, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "NavySidebarCv[0]/PageGrid[0]/Sidebar[0]/Education[2]/Years_MasterofScienceinMarketing[5]", + "entityName" : "Years_MasterofScienceinMarketing", + "entityKind" : "ParagraphNode", + "parentPath" : "NavySidebarCv[0]/PageGrid[0]/Sidebar[0]/Education[2]", + "childIndex" : 5, + "depth" : 5, + "layer" : 5, + "computedX" : 26.5, + "computedY" : 437.343, + "placementX" : 26.5, + "placementY" : 437.343, + "placementWidth" : 49.716, + "placementHeight" : 10.8, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 49.716, + "contentHeight" : 10.8, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 13.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "NavySidebarCv[0]/PageGrid[0]/Sidebar[0]/Education[2]/Degree_BachelorofBusinessAdministration[6]", + "entityName" : "Degree_BachelorofBusinessAdministration", + "entityKind" : "ParagraphNode", + "parentPath" : "NavySidebarCv[0]/PageGrid[0]/Sidebar[0]/Education[2]", + "childIndex" : 6, + "depth" : 5, + "layer" : 5, + "computedX" : 26.5, + "computedY" : 401.393, + "placementX" : 26.5, + "placementY" : 401.393, + "placementWidth" : 106.965, + "placementHeight" : 22.95, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 106.965, + "contentHeight" : 22.95, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 4.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "NavySidebarCv[0]/PageGrid[0]/Sidebar[0]/Education[2]/Institution_BachelorofBusinessAdministration[7]", + "entityName" : "Institution_BachelorofBusinessAdministration", + "entityKind" : "ParagraphNode", + "parentPath" : "NavySidebarCv[0]/PageGrid[0]/Sidebar[0]/Education[2]", + "childIndex" : 7, + "depth" : 5, + "layer" : 5, + "computedX" : 26.5, + "computedY" : 386.593, + "placementX" : 26.5, + "placementY" : 386.593, + "placementWidth" : 91.242, + "placementHeight" : 10.8, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 91.242, + "contentHeight" : 10.8, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 1.5, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "NavySidebarCv[0]/PageGrid[0]/Sidebar[0]/Education[2]/Campus_BachelorofBusinessAdministration[8]", + "entityName" : "Campus_BachelorofBusinessAdministration", + "entityKind" : "ParagraphNode", + "parentPath" : "NavySidebarCv[0]/PageGrid[0]/Sidebar[0]/Education[2]", + "childIndex" : 8, + "depth" : 5, + "layer" : 5, + "computedX" : 26.5, + "computedY" : 374.293, + "placementX" : 26.5, + "placementY" : 374.293, + "placementWidth" : 63.702, + "placementHeight" : 10.8, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 63.702, + "contentHeight" : 10.8, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 1.5, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "NavySidebarCv[0]/PageGrid[0]/Sidebar[0]/Education[2]/Years_BachelorofBusinessAdministration[9]", + "entityName" : "Years_BachelorofBusinessAdministration", + "entityKind" : "ParagraphNode", + "parentPath" : "NavySidebarCv[0]/PageGrid[0]/Sidebar[0]/Education[2]", + "childIndex" : 9, + "depth" : 5, + "layer" : 5, + "computedX" : 26.5, + "computedY" : 361.993, + "placementX" : 26.5, + "placementY" : 361.993, + "placementWidth" : 49.716, + "placementHeight" : 10.8, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 49.716, + "contentHeight" : 10.8, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 13.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "NavySidebarCv[0]/PageGrid[0]/Sidebar[0]/Skills[3]", + "entityName" : "Skills", + "entityKind" : "SectionNode", + "parentPath" : "NavySidebarCv[0]/PageGrid[0]/Sidebar[0]", + "childIndex" : 3, + "depth" : 4, + "layer" : 4, + "computedX" : 26.5, + "computedY" : 195.393, + "placementX" : 26.5, + "placementY" : 195.393, + "placementWidth" : 123.797, + "placementHeight" : 153.6, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 123.797, + "contentHeight" : 153.6, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "NavySidebarCv[0]/PageGrid[0]/Sidebar[0]/Skills[3]/SidebarHeading_Skills[0]", + "entityName" : "SidebarHeading_Skills", + "entityKind" : "ParagraphNode", + "parentPath" : "NavySidebarCv[0]/PageGrid[0]/Sidebar[0]/Skills[3]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 26.5, + "computedY" : 316.593, + "placementX" : 26.5, + "placementY" : 316.593, + "placementWidth" : 47.366, + "placementHeight" : 14.4, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 47.366, + "contentHeight" : 14.4, + "margin" : { + "top" : 18.0, + "right" : 0.0, + "bottom" : 7.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "NavySidebarCv[0]/PageGrid[0]/Sidebar[0]/Skills[3]/SidebarRule_Skills[1]", + "entityName" : "SidebarRule_Skills", + "entityKind" : "LineNode", + "parentPath" : "NavySidebarCv[0]/PageGrid[0]/Sidebar[0]/Skills[3]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 26.5, + "computedY" : 308.593, + "placementX" : 26.5, + "placementY" : 308.593, + "placementWidth" : 123.797, + "placementHeight" : 1.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 123.797, + "contentHeight" : 1.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 10.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "NavySidebarCv[0]/PageGrid[0]/Sidebar[0]/Skills[3]/SkillsList[2]", + "entityName" : "SkillsList", + "entityKind" : "ListNode", + "parentPath" : "NavySidebarCv[0]/PageGrid[0]/Sidebar[0]/Skills[3]", + "childIndex" : 2, + "depth" : 5, + "layer" : 5, + "computedX" : 26.5, + "computedY" : 195.393, + "placementX" : 26.5, + "placementY" : 195.393, + "placementWidth" : 95.517, + "placementHeight" : 103.2, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 95.517, + "contentHeight" : 103.2, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "NavySidebarCv[0]/PageGrid[0]/Sidebar[0]/Languages[4]", + "entityName" : "Languages", + "entityKind" : "SectionNode", + "parentPath" : "NavySidebarCv[0]/PageGrid[0]/Sidebar[0]", + "childIndex" : 4, + "depth" : 4, + "layer" : 4, + "computedX" : 26.5, + "computedY" : 84.543, + "placementX" : 26.5, + "placementY" : 84.543, + "placementWidth" : 123.797, + "placementHeight" : 110.85, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 123.797, + "contentHeight" : 110.85, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "NavySidebarCv[0]/PageGrid[0]/Sidebar[0]/Languages[4]/SidebarHeading_Languages[0]", + "entityName" : "SidebarHeading_Languages", + "entityKind" : "ParagraphNode", + "parentPath" : "NavySidebarCv[0]/PageGrid[0]/Sidebar[0]/Languages[4]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 26.5, + "computedY" : 162.993, + "placementX" : 26.5, + "placementY" : 162.993, + "placementWidth" : 87.863, + "placementHeight" : 14.4, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 87.863, + "contentHeight" : 14.4, + "margin" : { + "top" : 18.0, + "right" : 0.0, + "bottom" : 7.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "NavySidebarCv[0]/PageGrid[0]/Sidebar[0]/Languages[4]/SidebarRule_Languages[1]", + "entityName" : "SidebarRule_Languages", + "entityKind" : "LineNode", + "parentPath" : "NavySidebarCv[0]/PageGrid[0]/Sidebar[0]/Languages[4]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 26.5, + "computedY" : 148.993, + "placementX" : 26.5, + "placementY" : 148.993, + "placementWidth" : 123.797, + "placementHeight" : 1.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 123.797, + "contentHeight" : 1.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 10.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "NavySidebarCv[0]/PageGrid[0]/Sidebar[0]/Languages[4]/LanguageRow_English[2]", + "entityName" : "LanguageRow_English", + "entityKind" : "ShapeContainerNode", + "parentPath" : "NavySidebarCv[0]/PageGrid[0]/Sidebar[0]/Languages[4]", + "childIndex" : 2, + "depth" : 5, + "layer" : 5, + "computedX" : 26.5, + "computedY" : 120.843, + "placementX" : 26.5, + "placementY" : 120.843, + "placementWidth" : 123.797, + "placementHeight" : 12.15, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 123.797, + "contentHeight" : 12.15, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "NavySidebarCv[0]/PageGrid[0]/Sidebar[0]/Languages[4]/LanguageRow_English[2]/Language_English[0]", + "entityName" : "Language_English", + "entityKind" : "ParagraphNode", + "parentPath" : "NavySidebarCv[0]/PageGrid[0]/Sidebar[0]/Languages[4]/LanguageRow_English[2]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 26.5, + "computedY" : 121.518, + "placementX" : 26.5, + "placementY" : 121.518, + "placementWidth" : 35.115, + "placementHeight" : 10.8, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 35.115, + "contentHeight" : 10.8, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "NavySidebarCv[0]/PageGrid[0]/Sidebar[0]/Languages[4]/LanguageRow_English[2]/Proficiency_English[1]", + "entityName" : "Proficiency_English", + "entityKind" : "ParagraphNode", + "parentPath" : "NavySidebarCv[0]/PageGrid[0]/Sidebar[0]/Languages[4]/LanguageRow_English[2]", + "childIndex" : 1, + "depth" : 6, + "layer" : 6, + "computedX" : 77.257, + "computedY" : 121.518, + "placementX" : 77.257, + "placementY" : 121.518, + "placementWidth" : 38.268, + "placementHeight" : 10.8, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 38.268, + "contentHeight" : 10.8, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "NavySidebarCv[0]/PageGrid[0]/Sidebar[0]/Languages[4]/LanguageRow_Spanish[3]", + "entityName" : "LanguageRow_Spanish", + "entityKind" : "ShapeContainerNode", + "parentPath" : "NavySidebarCv[0]/PageGrid[0]/Sidebar[0]/Languages[4]", + "childIndex" : 3, + "depth" : 5, + "layer" : 5, + "computedX" : 26.5, + "computedY" : 102.693, + "placementX" : 26.5, + "placementY" : 102.693, + "placementWidth" : 123.797, + "placementHeight" : 12.15, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 123.797, + "contentHeight" : 12.15, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "NavySidebarCv[0]/PageGrid[0]/Sidebar[0]/Languages[4]/LanguageRow_Spanish[3]/Language_Spanish[0]", + "entityName" : "Language_Spanish", + "entityKind" : "ParagraphNode", + "parentPath" : "NavySidebarCv[0]/PageGrid[0]/Sidebar[0]/Languages[4]/LanguageRow_Spanish[3]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 26.5, + "computedY" : 103.368, + "placementX" : 26.5, + "placementY" : 103.368, + "placementWidth" : 37.518, + "placementHeight" : 10.8, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 37.518, + "contentHeight" : 10.8, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "NavySidebarCv[0]/PageGrid[0]/Sidebar[0]/Languages[4]/LanguageRow_Spanish[3]/Proficiency_Spanish[1]", + "entityName" : "Proficiency_Spanish", + "entityKind" : "ParagraphNode", + "parentPath" : "NavySidebarCv[0]/PageGrid[0]/Sidebar[0]/Languages[4]/LanguageRow_Spanish[3]", + "childIndex" : 1, + "depth" : 6, + "layer" : 6, + "computedX" : 77.257, + "computedY" : 103.368, + "placementX" : 77.257, + "placementY" : 103.368, + "placementWidth" : 51.498, + "placementHeight" : 10.8, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 51.498, + "contentHeight" : 10.8, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "NavySidebarCv[0]/PageGrid[0]/Sidebar[0]/Languages[4]/LanguageRow_French[4]", + "entityName" : "LanguageRow_French", + "entityKind" : "ShapeContainerNode", + "parentPath" : "NavySidebarCv[0]/PageGrid[0]/Sidebar[0]/Languages[4]", + "childIndex" : 4, + "depth" : 5, + "layer" : 5, + "computedX" : 26.5, + "computedY" : 84.543, + "placementX" : 26.5, + "placementY" : 84.543, + "placementWidth" : 123.797, + "placementHeight" : 12.15, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 123.797, + "contentHeight" : 12.15, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "NavySidebarCv[0]/PageGrid[0]/Sidebar[0]/Languages[4]/LanguageRow_French[4]/Language_French[0]", + "entityName" : "Language_French", + "entityKind" : "ParagraphNode", + "parentPath" : "NavySidebarCv[0]/PageGrid[0]/Sidebar[0]/Languages[4]/LanguageRow_French[4]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 26.5, + "computedY" : 85.218, + "placementX" : 26.5, + "placementY" : 85.218, + "placementWidth" : 34.476, + "placementHeight" : 10.8, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 34.476, + "contentHeight" : 10.8, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "NavySidebarCv[0]/PageGrid[0]/Sidebar[0]/Languages[4]/LanguageRow_French[4]/Proficiency_French[1]", + "entityName" : "Proficiency_French", + "entityKind" : "ParagraphNode", + "parentPath" : "NavySidebarCv[0]/PageGrid[0]/Sidebar[0]/Languages[4]/LanguageRow_French[4]", + "childIndex" : 1, + "depth" : 6, + "layer" : 6, + "computedX" : 77.257, + "computedY" : 85.218, + "placementX" : 77.257, + "placementY" : 85.218, + "placementWidth" : 62.748, + "placementHeight" : 10.8, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 62.748, + "contentHeight" : 10.8, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "NavySidebarCv[0]/PageGrid[0]/Main[1]", + "entityName" : "Main", + "entityKind" : "SectionNode", + "parentPath" : "NavySidebarCv[0]/PageGrid[0]", + "childIndex" : 1, + "depth" : 3, + "layer" : 3, + "computedX" : 176.797, + "computedY" : 1.125, + "placementX" : 176.797, + "placementY" : 1.125, + "placementWidth" : 418.479, + "placementHeight" : 840.765, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 418.479, + "contentHeight" : 840.765, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 30.0, + "right" : 36.5, + "bottom" : 36.5, + "left" : 25.4 + } + }, { + "path" : "NavySidebarCv[0]/PageGrid[0]/Main[1]/Identity[0]", + "entityName" : "Identity", + "entityKind" : "SectionNode", + "parentPath" : "NavySidebarCv[0]/PageGrid[0]/Main[1]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 202.197, + "computedY" : 737.69, + "placementX" : 202.197, + "placementY" : 737.69, + "placementWidth" : 255.99, + "placementHeight" : 74.2, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 255.99, + "contentHeight" : 74.2, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "NavySidebarCv[0]/PageGrid[0]/Main[1]/Identity[0]/Name[0]", + "entityName" : "Name", + "entityKind" : "ParagraphNode", + "parentPath" : "NavySidebarCv[0]/PageGrid[0]/Main[1]/Identity[0]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 202.197, + "computedY" : 775.89, + "placementX" : 202.197, + "placementY" : 775.89, + "placementWidth" : 255.99, + "placementHeight" : 36.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 255.99, + "contentHeight" : 36.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 6.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "NavySidebarCv[0]/PageGrid[0]/Main[1]/Identity[0]/Role[1]", + "entityName" : "Role", + "entityKind" : "ParagraphNode", + "parentPath" : "NavySidebarCv[0]/PageGrid[0]/Main[1]/Identity[0]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 202.197, + "computedY" : 753.69, + "placementX" : 202.197, + "placementY" : 753.69, + "placementWidth" : 187.31, + "placementHeight" : 16.2, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 187.31, + "contentHeight" : 16.2, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 16.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "NavySidebarCv[0]/PageGrid[0]/Main[1]/Summary[1]", + "entityName" : "Summary", + "entityKind" : "SectionNode", + "parentPath" : "NavySidebarCv[0]/PageGrid[0]/Main[1]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 202.197, + "computedY" : 683.45, + "placementX" : 202.197, + "placementY" : 683.45, + "placementWidth" : 346.217, + "placementHeight" : 54.24, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 346.217, + "contentHeight" : 54.24, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "NavySidebarCv[0]/PageGrid[0]/Main[1]/Summary[1]/Summary[0]", + "entityName" : "Summary", + "entityKind" : "ParagraphNode", + "parentPath" : "NavySidebarCv[0]/PageGrid[0]/Main[1]/Summary[1]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 202.197, + "computedY" : 683.45, + "placementX" : 202.197, + "placementY" : 683.45, + "placementWidth" : 346.217, + "placementHeight" : 54.24, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 346.217, + "contentHeight" : 54.24, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "NavySidebarCv[0]/PageGrid[0]/Main[1]/SectionRule[2]", + "entityName" : "SectionRule", + "entityKind" : "LineNode", + "parentPath" : "NavySidebarCv[0]/PageGrid[0]/Main[1]", + "childIndex" : 2, + "depth" : 4, + "layer" : 4, + "computedX" : 202.197, + "computedY" : 666.45, + "placementX" : 202.197, + "placementY" : 666.45, + "placementWidth" : 356.579, + "placementHeight" : 1.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 356.579, + "contentHeight" : 1.0, + "margin" : { + "top" : 16.0, + "right" : 0.0, + "bottom" : 15.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "NavySidebarCv[0]/PageGrid[0]/Main[1]/Experience[3]", + "entityName" : "Experience", + "entityKind" : "SectionNode", + "parentPath" : "NavySidebarCv[0]/PageGrid[0]/Main[1]", + "childIndex" : 3, + "depth" : 4, + "layer" : 4, + "computedX" : 202.197, + "computedY" : 258.175, + "placementX" : 202.197, + "placementY" : 258.175, + "placementWidth" : 356.579, + "placementHeight" : 393.275, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 356.579, + "contentHeight" : 393.275, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "NavySidebarCv[0]/PageGrid[0]/Main[1]/Experience[3]/SectionHeader_Experience[0]", + "entityName" : "SectionHeader_Experience", + "entityKind" : "ShapeContainerNode", + "parentPath" : "NavySidebarCv[0]/PageGrid[0]/Main[1]/Experience[3]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 202.197, + "computedY" : 628.85, + "placementX" : 202.197, + "placementY" : 628.85, + "placementWidth" : 356.579, + "placementHeight" : 22.6, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 356.579, + "contentHeight" : 22.6, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "NavySidebarCv[0]/PageGrid[0]/Main[1]/Experience[3]/SectionHeader_Experience[0]/Badge_Experience[0]", + "entityName" : "Badge_Experience", + "entityKind" : "ShapeContainerNode", + "parentPath" : "NavySidebarCv[0]/PageGrid[0]/Main[1]/Experience[3]/SectionHeader_Experience[0]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 202.197, + "computedY" : 628.85, + "placementX" : 202.197, + "placementY" : 628.85, + "placementWidth" : 22.6, + "placementHeight" : 22.6, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 22.6, + "contentHeight" : 22.6, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "NavySidebarCv[0]/PageGrid[0]/Main[1]/Experience[3]/SectionHeader_Experience[0]/Badge_Experience[0]/BadgeGlyph_Experience[0]", + "entityName" : "BadgeGlyph_Experience", + "entityKind" : "ImageNode", + "parentPath" : "NavySidebarCv[0]/PageGrid[0]/Main[1]/Experience[3]/SectionHeader_Experience[0]/Badge_Experience[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 207.847, + "computedY" : 634.5, + "placementX" : 207.847, + "placementY" : 634.5, + "placementWidth" : 11.3, + "placementHeight" : 11.3, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 11.3, + "contentHeight" : 11.3, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "NavySidebarCv[0]/PageGrid[0]/Main[1]/Experience[3]/SectionHeader_Experience[0]/SectionTitle_Experience[1]", + "entityName" : "SectionTitle_Experience", + "entityKind" : "ParagraphNode", + "parentPath" : "NavySidebarCv[0]/PageGrid[0]/Main[1]/Experience[3]/SectionHeader_Experience[0]", + "childIndex" : 1, + "depth" : 6, + "layer" : 6, + "computedX" : 232.097, + "computedY" : 632.95, + "placementX" : 232.097, + "placementY" : 632.95, + "placementWidth" : 89.402, + "placementHeight" : 14.4, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 89.402, + "contentHeight" : 14.4, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "NavySidebarCv[0]/PageGrid[0]/Main[1]/Experience[3]/ExperienceRail[1]", + "entityName" : "ExperienceRail", + "entityKind" : "LayerStackNode", + "parentPath" : "NavySidebarCv[0]/PageGrid[0]/Main[1]/Experience[3]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 202.197, + "computedY" : 258.175, + "placementX" : 202.197, + "placementY" : 258.175, + "placementWidth" : 356.579, + "placementHeight" : 370.675, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 356.579, + "contentHeight" : 370.675, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "NavySidebarCv[0]/PageGrid[0]/Main[1]/Experience[3]/ExperienceRail[1]/ExperienceRailHolder[0]", + "entityName" : "ExperienceRailHolder", + "entityKind" : "SectionNode", + "parentPath" : "NavySidebarCv[0]/PageGrid[0]/Main[1]/Experience[3]/ExperienceRail[1]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 209.697, + "computedY" : 258.175, + "placementX" : 209.697, + "placementY" : 258.175, + "placementWidth" : 349.079, + "placementHeight" : 355.312, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 349.079, + "contentHeight" : 355.312, + "margin" : { + "top" : 15.363, + "right" : 0.0, + "bottom" : 0.0, + "left" : 7.5 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "NavySidebarCv[0]/PageGrid[0]/Main[1]/Experience[3]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]", + "entityName" : null, + "entityKind" : "SectionNode", + "parentPath" : "NavySidebarCv[0]/PageGrid[0]/Main[1]/Experience[3]/ExperienceRail[1]/ExperienceRailHolder[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 209.697, + "computedY" : 258.175, + "placementX" : 209.697, + "placementY" : 258.175, + "placementWidth" : 349.079, + "placementHeight" : 355.312, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 349.079, + "contentHeight" : 355.312, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "NavySidebarCv[0]/PageGrid[0]/Main[1]/Experience[3]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]", + "entityName" : null, + "entityKind" : "LayoutAnchorNode", + "parentPath" : "NavySidebarCv[0]/PageGrid[0]/Main[1]/Experience[3]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 209.697, + "computedY" : 474.112, + "placementX" : 209.697, + "placementY" : 474.112, + "placementWidth" : 349.079, + "placementHeight" : 139.375, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 349.079, + "contentHeight" : 139.375, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "NavySidebarCv[0]/PageGrid[0]/Main[1]/Experience[3]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]", + "entityName" : null, + "entityKind" : "SectionNode", + "parentPath" : "NavySidebarCv[0]/PageGrid[0]/Main[1]/Experience[3]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]", + "childIndex" : 0, + "depth" : 9, + "layer" : 9, + "computedX" : 209.697, + "computedY" : 474.112, + "placementX" : 209.697, + "placementY" : 474.112, + "placementWidth" : 349.079, + "placementHeight" : 139.375, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 349.079, + "contentHeight" : 139.375, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 26.363, + "left" : 0.0 + } + }, { + "path" : "NavySidebarCv[0]/PageGrid[0]/Main[1]/Experience[3]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]", + "entityName" : null, + "entityKind" : "HorizontalBandsNode", + "parentPath" : "NavySidebarCv[0]/PageGrid[0]/Main[1]/Experience[3]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]", + "childIndex" : 0, + "depth" : 10, + "layer" : 10, + "computedX" : 209.697, + "computedY" : 500.475, + "placementX" : 209.697, + "placementY" : 500.475, + "placementWidth" : 349.079, + "placementHeight" : 113.012, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 349.079, + "contentHeight" : 113.012, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "NavySidebarCv[0]/PageGrid[0]/Main[1]/Experience[3]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]", + "entityName" : null, + "entityKind" : "RowNode", + "parentPath" : "NavySidebarCv[0]/PageGrid[0]/Main[1]/Experience[3]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]", + "childIndex" : 0, + "depth" : 11, + "layer" : 11, + "computedX" : 209.697, + "computedY" : 500.475, + "placementX" : 209.697, + "placementY" : 500.475, + "placementWidth" : 349.079, + "placementHeight" : 113.012, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 349.079, + "contentHeight" : 113.012, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "NavySidebarCv[0]/PageGrid[0]/Main[1]/Experience[3]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]", + "entityName" : null, + "entityKind" : "SectionNode", + "parentPath" : "NavySidebarCv[0]/PageGrid[0]/Main[1]/Experience[3]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]", + "childIndex" : 0, + "depth" : 12, + "layer" : 12, + "computedX" : 209.697, + "computedY" : 606.687, + "placementX" : 209.697, + "placementY" : 606.687, + "placementWidth" : 6.8, + "placementHeight" : 6.8, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 6.8, + "contentHeight" : 6.8, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "NavySidebarCv[0]/PageGrid[0]/Main[1]/Experience[3]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]", + "entityName" : null, + "entityKind" : "Align", + "parentPath" : "NavySidebarCv[0]/PageGrid[0]/Main[1]/Experience[3]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]", + "childIndex" : 0, + "depth" : 13, + "layer" : 13, + "computedX" : 209.697, + "computedY" : 606.687, + "placementX" : 209.697, + "placementY" : 606.687, + "placementWidth" : 6.8, + "placementHeight" : 6.8, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 6.8, + "contentHeight" : 6.8, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "NavySidebarCv[0]/PageGrid[0]/Main[1]/Experience[3]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]/LayoutAnchorNode[0]", + "entityName" : null, + "entityKind" : "LayoutAnchorNode", + "parentPath" : "NavySidebarCv[0]/PageGrid[0]/Main[1]/Experience[3]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]", + "childIndex" : 0, + "depth" : 14, + "layer" : 14, + "computedX" : 209.697, + "computedY" : 606.687, + "placementX" : 209.697, + "placementY" : 606.687, + "placementWidth" : 6.8, + "placementHeight" : 6.8, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 6.8, + "contentHeight" : 6.8, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "NavySidebarCv[0]/PageGrid[0]/Main[1]/Experience[3]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]/LayoutAnchorNode[0]/marker[0]", + "entityName" : "marker", + "entityKind" : "CanvasLayerNode", + "parentPath" : "NavySidebarCv[0]/PageGrid[0]/Main[1]/Experience[3]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]/LayoutAnchorNode[0]", + "childIndex" : 0, + "depth" : 15, + "layer" : 15, + "computedX" : 209.697, + "computedY" : 606.687, + "placementX" : 209.697, + "placementY" : 606.687, + "placementWidth" : 6.8, + "placementHeight" : 6.8, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 6.8, + "contentHeight" : 6.8, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "NavySidebarCv[0]/PageGrid[0]/Main[1]/Experience[3]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]/LayoutAnchorNode[0]/marker[0]/SectionNode[0]", + "entityName" : null, + "entityKind" : "SectionNode", + "parentPath" : "NavySidebarCv[0]/PageGrid[0]/Main[1]/Experience[3]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]/LayoutAnchorNode[0]/marker[0]", + "childIndex" : 0, + "depth" : 16, + "layer" : 16, + "computedX" : 209.697, + "computedY" : 606.687, + "placementX" : 209.697, + "placementY" : 606.687, + "placementWidth" : 6.8, + "placementHeight" : 6.8, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 6.8, + "contentHeight" : 6.8, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "NavySidebarCv[0]/PageGrid[0]/Main[1]/Experience[3]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]/LayoutAnchorNode[0]/marker[0]/SectionNode[0]/Marker_MarketingManager[0]", + "entityName" : "Marker_MarketingManager", + "entityKind" : "EllipseNode", + "parentPath" : "NavySidebarCv[0]/PageGrid[0]/Main[1]/Experience[3]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]/LayoutAnchorNode[0]/marker[0]/SectionNode[0]", + "childIndex" : 0, + "depth" : 17, + "layer" : 17, + "computedX" : 209.697, + "computedY" : 606.687, + "placementX" : 209.697, + "placementY" : 606.687, + "placementWidth" : 6.8, + "placementHeight" : 6.8, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 6.8, + "contentHeight" : 6.8, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "NavySidebarCv[0]/PageGrid[0]/Main[1]/Experience[3]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]", + "entityName" : null, + "entityKind" : "SectionNode", + "parentPath" : "NavySidebarCv[0]/PageGrid[0]/Main[1]/Experience[3]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]", + "childIndex" : 1, + "depth" : 12, + "layer" : 12, + "computedX" : 232.097, + "computedY" : 500.475, + "placementX" : 232.097, + "placementY" : 500.475, + "placementWidth" : 326.679, + "placementHeight" : 113.012, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 326.679, + "contentHeight" : 113.012, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "NavySidebarCv[0]/PageGrid[0]/Main[1]/Experience[3]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]/EntryHead_MarketingManager[0]", + "entityName" : "EntryHead_MarketingManager", + "entityKind" : "ShapeContainerNode", + "parentPath" : "NavySidebarCv[0]/PageGrid[0]/Main[1]/Experience[3]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]", + "childIndex" : 0, + "depth" : 13, + "layer" : 13, + "computedX" : 232.097, + "computedY" : 606.687, + "placementX" : 232.097, + "placementY" : 606.687, + "placementWidth" : 326.679, + "placementHeight" : 6.8, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 326.679, + "contentHeight" : 6.8, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "NavySidebarCv[0]/PageGrid[0]/Main[1]/Experience[3]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]/EntryHead_MarketingManager[0]/JobTitle_MarketingManager[0]", + "entityName" : "JobTitle_MarketingManager", + "entityKind" : "ParagraphNode", + "parentPath" : "NavySidebarCv[0]/PageGrid[0]/Main[1]/Experience[3]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]/EntryHead_MarketingManager[0]", + "childIndex" : 0, + "depth" : 14, + "layer" : 14, + "computedX" : 232.097, + "computedY" : 604.05, + "placementX" : 232.097, + "placementY" : 604.05, + "placementWidth" : 129.157, + "placementHeight" : 13.8, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 129.157, + "contentHeight" : 13.8, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "NavySidebarCv[0]/PageGrid[0]/Main[1]/Experience[3]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]/EntryHead_MarketingManager[0]/JobDates_MarketingManager[1]", + "entityName" : "JobDates_MarketingManager", + "entityKind" : "ParagraphNode", + "parentPath" : "NavySidebarCv[0]/PageGrid[0]/Main[1]/Experience[3]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]/EntryHead_MarketingManager[0]", + "childIndex" : 1, + "depth" : 14, + "layer" : 14, + "computedX" : 232.097, + "computedY" : 605.362, + "placementX" : 232.097, + "placementY" : 605.362, + "placementWidth" : 326.679, + "placementHeight" : 10.8, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 326.679, + "contentHeight" : 10.8, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "NavySidebarCv[0]/PageGrid[0]/Main[1]/Experience[3]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]/Employer_MarketingManager[1]", + "entityName" : "Employer_MarketingManager", + "entityKind" : "ParagraphNode", + "parentPath" : "NavySidebarCv[0]/PageGrid[0]/Main[1]/Experience[3]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]", + "childIndex" : 1, + "depth" : 13, + "layer" : 13, + "computedX" : 232.097, + "computedY" : 588.525, + "placementX" : 232.097, + "placementY" : 588.525, + "placementWidth" : 146.997, + "placementHeight" : 10.8, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 146.997, + "contentHeight" : 10.8, + "margin" : { + "top" : 7.363, + "right" : 0.0, + "bottom" : 7.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "NavySidebarCv[0]/PageGrid[0]/Main[1]/Experience[3]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]/Highlights_MarketingManager[2]", + "entityName" : "Highlights_MarketingManager", + "entityKind" : "ListNode", + "parentPath" : "NavySidebarCv[0]/PageGrid[0]/Main[1]/Experience[3]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]", + "childIndex" : 2, + "depth" : 13, + "layer" : 13, + "computedX" : 232.097, + "computedY" : 500.475, + "placementX" : 232.097, + "placementY" : 500.475, + "placementWidth" : 313.803, + "placementHeight" : 81.05, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 313.803, + "contentHeight" : 81.05, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "NavySidebarCv[0]/PageGrid[0]/Main[1]/Experience[3]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]", + "entityName" : null, + "entityKind" : "LayoutAnchorNode", + "parentPath" : "NavySidebarCv[0]/PageGrid[0]/Main[1]/Experience[3]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]", + "childIndex" : 1, + "depth" : 8, + "layer" : 8, + "computedX" : 209.697, + "computedY" : 334.737, + "placementX" : 209.697, + "placementY" : 334.737, + "placementWidth" : 349.079, + "placementHeight" : 139.375, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 349.079, + "contentHeight" : 139.375, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "NavySidebarCv[0]/PageGrid[0]/Main[1]/Experience[3]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]", + "entityName" : null, + "entityKind" : "SectionNode", + "parentPath" : "NavySidebarCv[0]/PageGrid[0]/Main[1]/Experience[3]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]", + "childIndex" : 0, + "depth" : 9, + "layer" : 9, + "computedX" : 209.697, + "computedY" : 334.737, + "placementX" : 209.697, + "placementY" : 334.737, + "placementWidth" : 349.079, + "placementHeight" : 139.375, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 349.079, + "contentHeight" : 139.375, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 26.363, + "left" : 0.0 + } + }, { + "path" : "NavySidebarCv[0]/PageGrid[0]/Main[1]/Experience[3]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]", + "entityName" : null, + "entityKind" : "HorizontalBandsNode", + "parentPath" : "NavySidebarCv[0]/PageGrid[0]/Main[1]/Experience[3]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]", + "childIndex" : 0, + "depth" : 10, + "layer" : 10, + "computedX" : 209.697, + "computedY" : 361.1, + "placementX" : 209.697, + "placementY" : 361.1, + "placementWidth" : 349.079, + "placementHeight" : 113.012, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 349.079, + "contentHeight" : 113.012, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "NavySidebarCv[0]/PageGrid[0]/Main[1]/Experience[3]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]", + "entityName" : null, + "entityKind" : "RowNode", + "parentPath" : "NavySidebarCv[0]/PageGrid[0]/Main[1]/Experience[3]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]", + "childIndex" : 0, + "depth" : 11, + "layer" : 11, + "computedX" : 209.697, + "computedY" : 361.1, + "placementX" : 209.697, + "placementY" : 361.1, + "placementWidth" : 349.079, + "placementHeight" : 113.012, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 349.079, + "contentHeight" : 113.012, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "NavySidebarCv[0]/PageGrid[0]/Main[1]/Experience[3]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]", + "entityName" : null, + "entityKind" : "SectionNode", + "parentPath" : "NavySidebarCv[0]/PageGrid[0]/Main[1]/Experience[3]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]", + "childIndex" : 0, + "depth" : 12, + "layer" : 12, + "computedX" : 209.697, + "computedY" : 467.312, + "placementX" : 209.697, + "placementY" : 467.312, + "placementWidth" : 6.8, + "placementHeight" : 6.8, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 6.8, + "contentHeight" : 6.8, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "NavySidebarCv[0]/PageGrid[0]/Main[1]/Experience[3]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]", + "entityName" : null, + "entityKind" : "Align", + "parentPath" : "NavySidebarCv[0]/PageGrid[0]/Main[1]/Experience[3]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]", + "childIndex" : 0, + "depth" : 13, + "layer" : 13, + "computedX" : 209.697, + "computedY" : 467.312, + "placementX" : 209.697, + "placementY" : 467.312, + "placementWidth" : 6.8, + "placementHeight" : 6.8, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 6.8, + "contentHeight" : 6.8, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "NavySidebarCv[0]/PageGrid[0]/Main[1]/Experience[3]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]/LayoutAnchorNode[0]", + "entityName" : null, + "entityKind" : "LayoutAnchorNode", + "parentPath" : "NavySidebarCv[0]/PageGrid[0]/Main[1]/Experience[3]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]", + "childIndex" : 0, + "depth" : 14, + "layer" : 14, + "computedX" : 209.697, + "computedY" : 467.312, + "placementX" : 209.697, + "placementY" : 467.312, + "placementWidth" : 6.8, + "placementHeight" : 6.8, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 6.8, + "contentHeight" : 6.8, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "NavySidebarCv[0]/PageGrid[0]/Main[1]/Experience[3]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]/LayoutAnchorNode[0]/marker[0]", + "entityName" : "marker", + "entityKind" : "CanvasLayerNode", + "parentPath" : "NavySidebarCv[0]/PageGrid[0]/Main[1]/Experience[3]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]/LayoutAnchorNode[0]", + "childIndex" : 0, + "depth" : 15, + "layer" : 15, + "computedX" : 209.697, + "computedY" : 467.312, + "placementX" : 209.697, + "placementY" : 467.312, + "placementWidth" : 6.8, + "placementHeight" : 6.8, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 6.8, + "contentHeight" : 6.8, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "NavySidebarCv[0]/PageGrid[0]/Main[1]/Experience[3]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]/LayoutAnchorNode[0]/marker[0]/SectionNode[0]", + "entityName" : null, + "entityKind" : "SectionNode", + "parentPath" : "NavySidebarCv[0]/PageGrid[0]/Main[1]/Experience[3]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]/LayoutAnchorNode[0]/marker[0]", + "childIndex" : 0, + "depth" : 16, + "layer" : 16, + "computedX" : 209.697, + "computedY" : 467.312, + "placementX" : 209.697, + "placementY" : 467.312, + "placementWidth" : 6.8, + "placementHeight" : 6.8, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 6.8, + "contentHeight" : 6.8, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "NavySidebarCv[0]/PageGrid[0]/Main[1]/Experience[3]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]/LayoutAnchorNode[0]/marker[0]/SectionNode[0]/Marker_MarketingSpecialist[0]", + "entityName" : "Marker_MarketingSpecialist", + "entityKind" : "EllipseNode", + "parentPath" : "NavySidebarCv[0]/PageGrid[0]/Main[1]/Experience[3]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]/LayoutAnchorNode[0]/marker[0]/SectionNode[0]", + "childIndex" : 0, + "depth" : 17, + "layer" : 17, + "computedX" : 209.697, + "computedY" : 467.312, + "placementX" : 209.697, + "placementY" : 467.312, + "placementWidth" : 6.8, + "placementHeight" : 6.8, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 6.8, + "contentHeight" : 6.8, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "NavySidebarCv[0]/PageGrid[0]/Main[1]/Experience[3]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]", + "entityName" : null, + "entityKind" : "SectionNode", + "parentPath" : "NavySidebarCv[0]/PageGrid[0]/Main[1]/Experience[3]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]", + "childIndex" : 1, + "depth" : 12, + "layer" : 12, + "computedX" : 232.097, + "computedY" : 361.1, + "placementX" : 232.097, + "placementY" : 361.1, + "placementWidth" : 326.679, + "placementHeight" : 113.012, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 326.679, + "contentHeight" : 113.012, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "NavySidebarCv[0]/PageGrid[0]/Main[1]/Experience[3]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]/EntryHead_MarketingSpecialist[0]", + "entityName" : "EntryHead_MarketingSpecialist", + "entityKind" : "ShapeContainerNode", + "parentPath" : "NavySidebarCv[0]/PageGrid[0]/Main[1]/Experience[3]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]", + "childIndex" : 0, + "depth" : 13, + "layer" : 13, + "computedX" : 232.097, + "computedY" : 467.312, + "placementX" : 232.097, + "placementY" : 467.312, + "placementWidth" : 326.679, + "placementHeight" : 6.8, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 326.679, + "contentHeight" : 6.8, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "NavySidebarCv[0]/PageGrid[0]/Main[1]/Experience[3]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]/EntryHead_MarketingSpecialist[0]/JobTitle_MarketingSpecialist[0]", + "entityName" : "JobTitle_MarketingSpecialist", + "entityKind" : "ParagraphNode", + "parentPath" : "NavySidebarCv[0]/PageGrid[0]/Main[1]/Experience[3]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]/EntryHead_MarketingSpecialist[0]", + "childIndex" : 0, + "depth" : 14, + "layer" : 14, + "computedX" : 232.097, + "computedY" : 464.675, + "placementX" : 232.097, + "placementY" : 464.675, + "placementWidth" : 132.756, + "placementHeight" : 13.8, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 132.756, + "contentHeight" : 13.8, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "NavySidebarCv[0]/PageGrid[0]/Main[1]/Experience[3]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]/EntryHead_MarketingSpecialist[0]/JobDates_MarketingSpecialist[1]", + "entityName" : "JobDates_MarketingSpecialist", + "entityKind" : "ParagraphNode", + "parentPath" : "NavySidebarCv[0]/PageGrid[0]/Main[1]/Experience[3]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]/EntryHead_MarketingSpecialist[0]", + "childIndex" : 1, + "depth" : 14, + "layer" : 14, + "computedX" : 232.097, + "computedY" : 465.987, + "placementX" : 232.097, + "placementY" : 465.987, + "placementWidth" : 326.679, + "placementHeight" : 10.8, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 326.679, + "contentHeight" : 10.8, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "NavySidebarCv[0]/PageGrid[0]/Main[1]/Experience[3]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]/Employer_MarketingSpecialist[1]", + "entityName" : "Employer_MarketingSpecialist", + "entityKind" : "ParagraphNode", + "parentPath" : "NavySidebarCv[0]/PageGrid[0]/Main[1]/Experience[3]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]", + "childIndex" : 1, + "depth" : 13, + "layer" : 13, + "computedX" : 232.097, + "computedY" : 449.15, + "placementX" : 232.097, + "placementY" : 449.15, + "placementWidth" : 146.997, + "placementHeight" : 10.8, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 146.997, + "contentHeight" : 10.8, + "margin" : { + "top" : 7.363, + "right" : 0.0, + "bottom" : 7.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "NavySidebarCv[0]/PageGrid[0]/Main[1]/Experience[3]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]/Highlights_MarketingSpecialist[2]", + "entityName" : "Highlights_MarketingSpecialist", + "entityKind" : "ListNode", + "parentPath" : "NavySidebarCv[0]/PageGrid[0]/Main[1]/Experience[3]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]", + "childIndex" : 2, + "depth" : 13, + "layer" : 13, + "computedX" : 232.097, + "computedY" : 361.1, + "placementX" : 232.097, + "placementY" : 361.1, + "placementWidth" : 324.765, + "placementHeight" : 81.05, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 324.765, + "contentHeight" : 81.05, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "NavySidebarCv[0]/PageGrid[0]/Main[1]/Experience[3]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]", + "entityName" : null, + "entityKind" : "LayoutAnchorNode", + "parentPath" : "NavySidebarCv[0]/PageGrid[0]/Main[1]/Experience[3]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]", + "childIndex" : 2, + "depth" : 8, + "layer" : 8, + "computedX" : 209.697, + "computedY" : 258.175, + "placementX" : 209.697, + "placementY" : 258.175, + "placementWidth" : 349.079, + "placementHeight" : 76.562, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 349.079, + "contentHeight" : 76.562, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "NavySidebarCv[0]/PageGrid[0]/Main[1]/Experience[3]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]", + "entityName" : null, + "entityKind" : "SectionNode", + "parentPath" : "NavySidebarCv[0]/PageGrid[0]/Main[1]/Experience[3]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]", + "childIndex" : 0, + "depth" : 9, + "layer" : 9, + "computedX" : 209.697, + "computedY" : 258.175, + "placementX" : 209.697, + "placementY" : 258.175, + "placementWidth" : 349.079, + "placementHeight" : 76.562, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 349.079, + "contentHeight" : 76.562, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "NavySidebarCv[0]/PageGrid[0]/Main[1]/Experience[3]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]", + "entityName" : null, + "entityKind" : "HorizontalBandsNode", + "parentPath" : "NavySidebarCv[0]/PageGrid[0]/Main[1]/Experience[3]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]", + "childIndex" : 0, + "depth" : 10, + "layer" : 10, + "computedX" : 209.697, + "computedY" : 258.175, + "placementX" : 209.697, + "placementY" : 258.175, + "placementWidth" : 349.079, + "placementHeight" : 76.562, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 349.079, + "contentHeight" : 76.562, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "NavySidebarCv[0]/PageGrid[0]/Main[1]/Experience[3]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]", + "entityName" : null, + "entityKind" : "RowNode", + "parentPath" : "NavySidebarCv[0]/PageGrid[0]/Main[1]/Experience[3]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]", + "childIndex" : 0, + "depth" : 11, + "layer" : 11, + "computedX" : 209.697, + "computedY" : 258.175, + "placementX" : 209.697, + "placementY" : 258.175, + "placementWidth" : 349.079, + "placementHeight" : 76.562, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 349.079, + "contentHeight" : 76.562, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "NavySidebarCv[0]/PageGrid[0]/Main[1]/Experience[3]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]", + "entityName" : null, + "entityKind" : "SectionNode", + "parentPath" : "NavySidebarCv[0]/PageGrid[0]/Main[1]/Experience[3]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]", + "childIndex" : 0, + "depth" : 12, + "layer" : 12, + "computedX" : 209.697, + "computedY" : 327.937, + "placementX" : 209.697, + "placementY" : 327.937, + "placementWidth" : 6.8, + "placementHeight" : 6.8, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 6.8, + "contentHeight" : 6.8, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "NavySidebarCv[0]/PageGrid[0]/Main[1]/Experience[3]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]", + "entityName" : null, + "entityKind" : "Align", + "parentPath" : "NavySidebarCv[0]/PageGrid[0]/Main[1]/Experience[3]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]", + "childIndex" : 0, + "depth" : 13, + "layer" : 13, + "computedX" : 209.697, + "computedY" : 327.937, + "placementX" : 209.697, + "placementY" : 327.937, + "placementWidth" : 6.8, + "placementHeight" : 6.8, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 6.8, + "contentHeight" : 6.8, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "NavySidebarCv[0]/PageGrid[0]/Main[1]/Experience[3]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]/LayoutAnchorNode[0]", + "entityName" : null, + "entityKind" : "LayoutAnchorNode", + "parentPath" : "NavySidebarCv[0]/PageGrid[0]/Main[1]/Experience[3]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]", + "childIndex" : 0, + "depth" : 14, + "layer" : 14, + "computedX" : 209.697, + "computedY" : 327.937, + "placementX" : 209.697, + "placementY" : 327.937, + "placementWidth" : 6.8, + "placementHeight" : 6.8, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 6.8, + "contentHeight" : 6.8, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "NavySidebarCv[0]/PageGrid[0]/Main[1]/Experience[3]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]/LayoutAnchorNode[0]/marker[0]", + "entityName" : "marker", + "entityKind" : "CanvasLayerNode", + "parentPath" : "NavySidebarCv[0]/PageGrid[0]/Main[1]/Experience[3]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]/LayoutAnchorNode[0]", + "childIndex" : 0, + "depth" : 15, + "layer" : 15, + "computedX" : 209.697, + "computedY" : 327.937, + "placementX" : 209.697, + "placementY" : 327.937, + "placementWidth" : 6.8, + "placementHeight" : 6.8, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 6.8, + "contentHeight" : 6.8, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "NavySidebarCv[0]/PageGrid[0]/Main[1]/Experience[3]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]/LayoutAnchorNode[0]/marker[0]/SectionNode[0]", + "entityName" : null, + "entityKind" : "SectionNode", + "parentPath" : "NavySidebarCv[0]/PageGrid[0]/Main[1]/Experience[3]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]/LayoutAnchorNode[0]/marker[0]", + "childIndex" : 0, + "depth" : 16, + "layer" : 16, + "computedX" : 209.697, + "computedY" : 327.937, + "placementX" : 209.697, + "placementY" : 327.937, + "placementWidth" : 6.8, + "placementHeight" : 6.8, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 6.8, + "contentHeight" : 6.8, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "NavySidebarCv[0]/PageGrid[0]/Main[1]/Experience[3]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]/LayoutAnchorNode[0]/marker[0]/SectionNode[0]/Marker_MarketingIntern[0]", + "entityName" : "Marker_MarketingIntern", + "entityKind" : "EllipseNode", + "parentPath" : "NavySidebarCv[0]/PageGrid[0]/Main[1]/Experience[3]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]/LayoutAnchorNode[0]/marker[0]/SectionNode[0]", + "childIndex" : 0, + "depth" : 17, + "layer" : 17, + "computedX" : 209.697, + "computedY" : 327.937, + "placementX" : 209.697, + "placementY" : 327.937, + "placementWidth" : 6.8, + "placementHeight" : 6.8, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 6.8, + "contentHeight" : 6.8, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "NavySidebarCv[0]/PageGrid[0]/Main[1]/Experience[3]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]", + "entityName" : null, + "entityKind" : "SectionNode", + "parentPath" : "NavySidebarCv[0]/PageGrid[0]/Main[1]/Experience[3]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]", + "childIndex" : 1, + "depth" : 12, + "layer" : 12, + "computedX" : 232.097, + "computedY" : 258.175, + "placementX" : 232.097, + "placementY" : 258.175, + "placementWidth" : 326.679, + "placementHeight" : 76.562, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 326.679, + "contentHeight" : 76.562, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "NavySidebarCv[0]/PageGrid[0]/Main[1]/Experience[3]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]/EntryHead_MarketingIntern[0]", + "entityName" : "EntryHead_MarketingIntern", + "entityKind" : "ShapeContainerNode", + "parentPath" : "NavySidebarCv[0]/PageGrid[0]/Main[1]/Experience[3]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]", + "childIndex" : 0, + "depth" : 13, + "layer" : 13, + "computedX" : 232.097, + "computedY" : 327.937, + "placementX" : 232.097, + "placementY" : 327.937, + "placementWidth" : 326.679, + "placementHeight" : 6.8, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 326.679, + "contentHeight" : 6.8, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "NavySidebarCv[0]/PageGrid[0]/Main[1]/Experience[3]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]/EntryHead_MarketingIntern[0]/JobTitle_MarketingIntern[0]", + "entityName" : "JobTitle_MarketingIntern", + "entityKind" : "ParagraphNode", + "parentPath" : "NavySidebarCv[0]/PageGrid[0]/Main[1]/Experience[3]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]/EntryHead_MarketingIntern[0]", + "childIndex" : 0, + "depth" : 14, + "layer" : 14, + "computedX" : 232.097, + "computedY" : 325.3, + "placementX" : 232.097, + "placementY" : 325.3, + "placementWidth" : 113.137, + "placementHeight" : 13.8, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 113.137, + "contentHeight" : 13.8, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "NavySidebarCv[0]/PageGrid[0]/Main[1]/Experience[3]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]/EntryHead_MarketingIntern[0]/JobDates_MarketingIntern[1]", + "entityName" : "JobDates_MarketingIntern", + "entityKind" : "ParagraphNode", + "parentPath" : "NavySidebarCv[0]/PageGrid[0]/Main[1]/Experience[3]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]/EntryHead_MarketingIntern[0]", + "childIndex" : 1, + "depth" : 14, + "layer" : 14, + "computedX" : 232.097, + "computedY" : 326.612, + "placementX" : 232.097, + "placementY" : 326.612, + "placementWidth" : 326.679, + "placementHeight" : 10.8, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 326.679, + "contentHeight" : 10.8, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "NavySidebarCv[0]/PageGrid[0]/Main[1]/Experience[3]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]/Employer_MarketingIntern[1]", + "entityName" : "Employer_MarketingIntern", + "entityKind" : "ParagraphNode", + "parentPath" : "NavySidebarCv[0]/PageGrid[0]/Main[1]/Experience[3]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]", + "childIndex" : 1, + "depth" : 13, + "layer" : 13, + "computedX" : 232.097, + "computedY" : 309.775, + "placementX" : 232.097, + "placementY" : 309.775, + "placementWidth" : 146.997, + "placementHeight" : 10.8, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 146.997, + "contentHeight" : 10.8, + "margin" : { + "top" : 7.363, + "right" : 0.0, + "bottom" : 7.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "NavySidebarCv[0]/PageGrid[0]/Main[1]/Experience[3]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]/Highlights_MarketingIntern[2]", + "entityName" : "Highlights_MarketingIntern", + "entityKind" : "ListNode", + "parentPath" : "NavySidebarCv[0]/PageGrid[0]/Main[1]/Experience[3]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]", + "childIndex" : 2, + "depth" : 13, + "layer" : 13, + "computedX" : 232.097, + "computedY" : 258.175, + "placementX" : 232.097, + "placementY" : 258.175, + "placementWidth" : 274.005, + "placementHeight" : 44.6, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 274.005, + "contentHeight" : 44.6, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "NavySidebarCv[0]/PageGrid[0]/Main[1]/SectionRule[4]", + "entityName" : "SectionRule", + "entityKind" : "LineNode", + "parentPath" : "NavySidebarCv[0]/PageGrid[0]/Main[1]", + "childIndex" : 4, + "depth" : 4, + "layer" : 4, + "computedX" : 202.197, + "computedY" : 241.175, + "placementX" : 202.197, + "placementY" : 241.175, + "placementWidth" : 356.579, + "placementHeight" : 1.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 356.579, + "contentHeight" : 1.0, + "margin" : { + "top" : 16.0, + "right" : 0.0, + "bottom" : 11.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "NavySidebarCv[0]/PageGrid[0]/Main[1]/Achievements[5]", + "entityName" : "Achievements", + "entityKind" : "SectionNode", + "parentPath" : "NavySidebarCv[0]/PageGrid[0]/Main[1]", + "childIndex" : 5, + "depth" : 4, + "layer" : 4, + "computedX" : 202.197, + "computedY" : 141.825, + "placementX" : 202.197, + "placementY" : 141.825, + "placementWidth" : 356.579, + "placementHeight" : 88.35, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 356.579, + "contentHeight" : 88.35, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "NavySidebarCv[0]/PageGrid[0]/Main[1]/Achievements[5]/SectionHeader_Achievements[0]", + "entityName" : "SectionHeader_Achievements", + "entityKind" : "ShapeContainerNode", + "parentPath" : "NavySidebarCv[0]/PageGrid[0]/Main[1]/Achievements[5]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 202.197, + "computedY" : 207.575, + "placementX" : 202.197, + "placementY" : 207.575, + "placementWidth" : 356.579, + "placementHeight" : 22.6, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 356.579, + "contentHeight" : 22.6, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "NavySidebarCv[0]/PageGrid[0]/Main[1]/Achievements[5]/SectionHeader_Achievements[0]/Badge_Achievements[0]", + "entityName" : "Badge_Achievements", + "entityKind" : "ShapeContainerNode", + "parentPath" : "NavySidebarCv[0]/PageGrid[0]/Main[1]/Achievements[5]/SectionHeader_Achievements[0]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 202.197, + "computedY" : 207.575, + "placementX" : 202.197, + "placementY" : 207.575, + "placementWidth" : 22.6, + "placementHeight" : 22.6, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 22.6, + "contentHeight" : 22.6, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "NavySidebarCv[0]/PageGrid[0]/Main[1]/Achievements[5]/SectionHeader_Achievements[0]/Badge_Achievements[0]/BadgeGlyph_Achievements[0]", + "entityName" : "BadgeGlyph_Achievements", + "entityKind" : "ImageNode", + "parentPath" : "NavySidebarCv[0]/PageGrid[0]/Main[1]/Achievements[5]/SectionHeader_Achievements[0]/Badge_Achievements[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 207.847, + "computedY" : 213.225, + "placementX" : 207.847, + "placementY" : 213.225, + "placementWidth" : 11.3, + "placementHeight" : 11.3, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 11.3, + "contentHeight" : 11.3, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "NavySidebarCv[0]/PageGrid[0]/Main[1]/Achievements[5]/SectionHeader_Achievements[0]/SectionTitle_Achievements[1]", + "entityName" : "SectionTitle_Achievements", + "entityKind" : "ParagraphNode", + "parentPath" : "NavySidebarCv[0]/PageGrid[0]/Main[1]/Achievements[5]/SectionHeader_Achievements[0]", + "childIndex" : 1, + "depth" : 6, + "layer" : 6, + "computedX" : 232.097, + "computedY" : 211.675, + "placementX" : 232.097, + "placementY" : 211.675, + "placementWidth" : 114.332, + "placementHeight" : 14.4, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 114.332, + "contentHeight" : 14.4, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "NavySidebarCv[0]/PageGrid[0]/Main[1]/Achievements[5]/AchievementsList[1]", + "entityName" : "AchievementsList", + "entityKind" : "ListNode", + "parentPath" : "NavySidebarCv[0]/PageGrid[0]/Main[1]/Achievements[5]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 232.097, + "computedY" : 141.825, + "placementX" : 232.097, + "placementY" : 141.825, + "placementWidth" : 315.126, + "placementHeight" : 54.75, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 315.126, + "contentHeight" : 54.75, + "margin" : { + "top" : 11.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 29.9 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "NavySidebarCv[0]/PageGrid[0]/Main[1]/SectionRule[6]", + "entityName" : "SectionRule", + "entityKind" : "LineNode", + "parentPath" : "NavySidebarCv[0]/PageGrid[0]/Main[1]", + "childIndex" : 6, + "depth" : 4, + "layer" : 4, + "computedX" : 202.197, + "computedY" : 124.825, + "placementX" : 202.197, + "placementY" : 124.825, + "placementWidth" : 356.579, + "placementHeight" : 1.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 356.579, + "contentHeight" : 1.0, + "margin" : { + "top" : 16.0, + "right" : 0.0, + "bottom" : 11.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "NavySidebarCv[0]/PageGrid[0]/Main[1]/Certifications[7]", + "entityName" : "Certifications", + "entityKind" : "SectionNode", + "parentPath" : "NavySidebarCv[0]/PageGrid[0]/Main[1]", + "childIndex" : 7, + "depth" : 4, + "layer" : 4, + "computedX" : 202.197, + "computedY" : 37.625, + "placementX" : 202.197, + "placementY" : 37.625, + "placementWidth" : 356.579, + "placementHeight" : 76.2, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 356.579, + "contentHeight" : 76.2, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "NavySidebarCv[0]/PageGrid[0]/Main[1]/Certifications[7]/SectionHeader_Certifications[0]", + "entityName" : "SectionHeader_Certifications", + "entityKind" : "ShapeContainerNode", + "parentPath" : "NavySidebarCv[0]/PageGrid[0]/Main[1]/Certifications[7]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 202.197, + "computedY" : 91.225, + "placementX" : 202.197, + "placementY" : 91.225, + "placementWidth" : 356.579, + "placementHeight" : 22.6, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 356.579, + "contentHeight" : 22.6, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "NavySidebarCv[0]/PageGrid[0]/Main[1]/Certifications[7]/SectionHeader_Certifications[0]/Badge_Certifications[0]", + "entityName" : "Badge_Certifications", + "entityKind" : "ShapeContainerNode", + "parentPath" : "NavySidebarCv[0]/PageGrid[0]/Main[1]/Certifications[7]/SectionHeader_Certifications[0]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 202.197, + "computedY" : 91.225, + "placementX" : 202.197, + "placementY" : 91.225, + "placementWidth" : 22.6, + "placementHeight" : 22.6, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 22.6, + "contentHeight" : 22.6, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "NavySidebarCv[0]/PageGrid[0]/Main[1]/Certifications[7]/SectionHeader_Certifications[0]/Badge_Certifications[0]/BadgeGlyph_Certifications[0]", + "entityName" : "BadgeGlyph_Certifications", + "entityKind" : "ImageNode", + "parentPath" : "NavySidebarCv[0]/PageGrid[0]/Main[1]/Certifications[7]/SectionHeader_Certifications[0]/Badge_Certifications[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 207.847, + "computedY" : 96.875, + "placementX" : 207.847, + "placementY" : 96.875, + "placementWidth" : 11.3, + "placementHeight" : 11.3, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 11.3, + "contentHeight" : 11.3, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "NavySidebarCv[0]/PageGrid[0]/Main[1]/Certifications[7]/SectionHeader_Certifications[0]/SectionTitle_Certifications[1]", + "entityName" : "SectionTitle_Certifications", + "entityKind" : "ParagraphNode", + "parentPath" : "NavySidebarCv[0]/PageGrid[0]/Main[1]/Certifications[7]/SectionHeader_Certifications[0]", + "childIndex" : 1, + "depth" : 6, + "layer" : 6, + "computedX" : 232.097, + "computedY" : 95.325, + "placementX" : 232.097, + "placementY" : 95.325, + "placementWidth" : 122.041, + "placementHeight" : 14.4, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 122.041, + "contentHeight" : 14.4, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "NavySidebarCv[0]/PageGrid[0]/Main[1]/Certifications[7]/CertificationsList[1]", + "entityName" : "CertificationsList", + "entityKind" : "ListNode", + "parentPath" : "NavySidebarCv[0]/PageGrid[0]/Main[1]/Certifications[7]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 232.097, + "computedY" : 37.625, + "placementX" : 232.097, + "placementY" : 37.625, + "placementWidth" : 197.037, + "placementHeight" : 42.6, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 197.037, + "contentHeight" : 42.6, + "margin" : { + "top" : 11.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 29.9 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + } ] +} diff --git a/qa/src/test/resources/layout-snapshots/canonical-templates/cv-v2/orange_ops_layout.json b/qa/src/test/resources/layout-snapshots/canonical-templates/cv-v2/orange_ops_layout.json new file mode 100644 index 000000000..3401abdf0 --- /dev/null +++ b/qa/src/test/resources/layout-snapshots/canonical-templates/cv-v2/orange_ops_layout.json @@ -0,0 +1,3197 @@ +{ + "formatVersion" : "2.0", + "canvas" : { + "pageWidth" : 595.276, + "pageHeight" : 841.89, + "innerWidth" : 595.276, + "innerHeight" : 824.452, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 17.438, + "left" : 0.0 + } + }, + "totalPages" : 1, + "nodes" : [ { + "path" : "OrangeOpsCv[0]", + "entityName" : "OrangeOpsCv", + "entityKind" : "ContainerNode", + "parentPath" : null, + "childIndex" : 0, + "depth" : 1, + "layer" : 1, + "computedX" : 0.0, + "computedY" : 19.603, + "placementX" : 0.0, + "placementY" : 19.603, + "placementWidth" : 595.276, + "placementHeight" : 822.287, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 595.276, + "contentHeight" : 822.287, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "OrangeOpsCv[0]/Masthead[0]", + "entityName" : "Masthead", + "entityKind" : "SectionNode", + "parentPath" : "OrangeOpsCv[0]", + "childIndex" : 0, + "depth" : 2, + "layer" : 2, + "computedX" : 0.0, + "computedY" : 767.821, + "placementX" : 0.0, + "placementY" : 767.821, + "placementWidth" : 422.259, + "placementHeight" : 74.068, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 422.259, + "contentHeight" : 74.068, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 23.134, + "bottom" : 0.0, + "left" : 23.134 + } + }, { + "path" : "OrangeOpsCv[0]/Masthead[0]/Name[0]", + "entityName" : "Name", + "entityKind" : "ParagraphNode", + "parentPath" : "OrangeOpsCv[0]/Masthead[0]", + "childIndex" : 0, + "depth" : 3, + "layer" : 3, + "computedX" : 23.134, + "computedY" : 763.804, + "placementX" : 23.134, + "placementY" : 763.804, + "placementWidth" : 375.991, + "placementHeight" : 78.086, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 375.991, + "contentHeight" : 78.086, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : -4.017, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "OrangeOpsCv[0]/RoleBar[1]", + "entityName" : "RoleBar", + "entityKind" : "LayerStackNode", + "parentPath" : "OrangeOpsCv[0]", + "childIndex" : 1, + "depth" : 2, + "layer" : 2, + "computedX" : 0.0, + "computedY" : 745.816, + "placementX" : 0.0, + "placementY" : 745.816, + "placementWidth" : 316.54, + "placementHeight" : 22.005, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 316.54, + "contentHeight" : 22.005, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 7.938, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "OrangeOpsCv[0]/RoleBar[1]/RoleSlashes[0]", + "entityName" : "RoleSlashes", + "entityKind" : "Path", + "parentPath" : "OrangeOpsCv[0]/RoleBar[1]", + "childIndex" : 0, + "depth" : 3, + "layer" : 3, + "computedX" : 307.512, + "computedY" : 745.816, + "placementX" : 307.512, + "placementY" : 745.816, + "placementWidth" : 65.452, + "placementHeight" : 22.005, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 65.452, + "contentHeight" : 22.005, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "OrangeOpsCv[0]/RoleBar[1]/RoleBarPlate[1]", + "entityName" : "RoleBarPlate", + "entityKind" : "ShapeContainerNode", + "parentPath" : "OrangeOpsCv[0]/RoleBar[1]", + "childIndex" : 1, + "depth" : 3, + "layer" : 3, + "computedX" : 0.0, + "computedY" : 745.816, + "placementX" : 0.0, + "placementY" : 745.816, + "placementWidth" : 316.54, + "placementHeight" : 22.005, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 316.54, + "contentHeight" : 22.005, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "OrangeOpsCv[0]/RoleBar[1]/RoleBarPlate[1]/RoleText[0]", + "entityName" : "RoleText", + "entityKind" : "ParagraphNode", + "parentPath" : "OrangeOpsCv[0]/RoleBar[1]/RoleBarPlate[1]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 23.134, + "computedY" : 750.204, + "placementX" : 23.134, + "placementY" : 750.204, + "placementWidth" : 213.686, + "placementHeight" : 13.23, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 213.686, + "contentHeight" : 13.23, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "OrangeOpsCv[0]/ContactStrip[2]", + "entityName" : "ContactStrip", + "entityKind" : "RowNode", + "parentPath" : "OrangeOpsCv[0]", + "childIndex" : 2, + "depth" : 2, + "layer" : 2, + "computedX" : 0.0, + "computedY" : 714.391, + "placementX" : 0.0, + "placementY" : 714.391, + "placementWidth" : 595.276, + "placementHeight" : 23.487, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 595.276, + "contentHeight" : 23.487, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 23.134, + "bottom" : 12.202, + "left" : 23.134 + } + }, { + "path" : "OrangeOpsCv[0]/ContactStrip[2]/Contact0[0]", + "entityName" : "Contact0", + "entityKind" : "ParagraphNode", + "parentPath" : "OrangeOpsCv[0]/ContactStrip[2]", + "childIndex" : 0, + "depth" : 3, + "layer" : 3, + "computedX" : 23.134, + "computedY" : 727.315, + "placementX" : 23.134, + "placementY" : 727.315, + "placementWidth" : 78.725, + "placementHeight" : 9.84, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 78.725, + "contentHeight" : 9.84, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "OrangeOpsCv[0]/ContactStrip[2]/ContactSeparator1[1]", + "entityName" : "ContactSeparator1", + "entityKind" : "SectionNode", + "parentPath" : "OrangeOpsCv[0]/ContactStrip[2]", + "childIndex" : 1, + "depth" : 3, + "layer" : 3, + "computedX" : 101.859, + "computedY" : 726.593, + "placementX" : 101.859, + "placementY" : 726.593, + "placementWidth" : 61.762, + "placementHeight" : 11.285, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 61.762, + "contentHeight" : 11.285, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "OrangeOpsCv[0]/ContactStrip[2]/ContactSeparator1[1]/Align[0]", + "entityName" : null, + "entityKind" : "Align", + "parentPath" : "OrangeOpsCv[0]/ContactStrip[2]/ContactSeparator1[1]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 101.859, + "computedY" : 726.593, + "placementX" : 101.859, + "placementY" : 726.593, + "placementWidth" : 61.762, + "placementHeight" : 11.285, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 61.762, + "contentHeight" : 11.285, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "OrangeOpsCv[0]/ContactStrip[2]/ContactSeparator1[1]/Align[0]/ContactSeparator1Line[0]", + "entityName" : "ContactSeparator1Line", + "entityKind" : "LineNode", + "parentPath" : "OrangeOpsCv[0]/ContactStrip[2]/ContactSeparator1[1]/Align[0]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 132.24, + "computedY" : 726.593, + "placementX" : 132.24, + "placementY" : 726.593, + "placementWidth" : 1.0, + "placementHeight" : 11.285, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 1.0, + "contentHeight" : 11.285, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "OrangeOpsCv[0]/ContactStrip[2]/Contact1[2]", + "entityName" : "Contact1", + "entityKind" : "ParagraphNode", + "parentPath" : "OrangeOpsCv[0]/ContactStrip[2]", + "childIndex" : 2, + "depth" : 3, + "layer" : 3, + "computedX" : 163.621, + "computedY" : 727.315, + "placementX" : 163.621, + "placementY" : 727.315, + "placementWidth" : 111.951, + "placementHeight" : 9.84, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 111.951, + "contentHeight" : 9.84, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "OrangeOpsCv[0]/ContactStrip[2]/ContactSeparator2[3]", + "entityName" : "ContactSeparator2", + "entityKind" : "SectionNode", + "parentPath" : "OrangeOpsCv[0]/ContactStrip[2]", + "childIndex" : 3, + "depth" : 3, + "layer" : 3, + "computedX" : 275.572, + "computedY" : 726.593, + "placementX" : 275.572, + "placementY" : 726.593, + "placementWidth" : 61.762, + "placementHeight" : 11.285, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 61.762, + "contentHeight" : 11.285, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "OrangeOpsCv[0]/ContactStrip[2]/ContactSeparator2[3]/Align[0]", + "entityName" : null, + "entityKind" : "Align", + "parentPath" : "OrangeOpsCv[0]/ContactStrip[2]/ContactSeparator2[3]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 275.572, + "computedY" : 726.593, + "placementX" : 275.572, + "placementY" : 726.593, + "placementWidth" : 61.762, + "placementHeight" : 11.285, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 61.762, + "contentHeight" : 11.285, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "OrangeOpsCv[0]/ContactStrip[2]/ContactSeparator2[3]/Align[0]/ContactSeparator2Line[0]", + "entityName" : "ContactSeparator2Line", + "entityKind" : "LineNode", + "parentPath" : "OrangeOpsCv[0]/ContactStrip[2]/ContactSeparator2[3]/Align[0]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 305.953, + "computedY" : 726.593, + "placementX" : 305.953, + "placementY" : 726.593, + "placementWidth" : 1.0, + "placementHeight" : 11.285, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 1.0, + "contentHeight" : 11.285, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "OrangeOpsCv[0]/ContactStrip[2]/Contact2[4]", + "entityName" : "Contact2", + "entityKind" : "ParagraphNode", + "parentPath" : "OrangeOpsCv[0]/ContactStrip[2]", + "childIndex" : 4, + "depth" : 3, + "layer" : 3, + "computedX" : 337.334, + "computedY" : 727.315, + "placementX" : 337.334, + "placementY" : 727.315, + "placementWidth" : 129.63, + "placementHeight" : 9.84, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 129.63, + "contentHeight" : 9.84, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "OrangeOpsCv[0]/ContactStrip[2]/ContactSeparator3[5]", + "entityName" : "ContactSeparator3", + "entityKind" : "SectionNode", + "parentPath" : "OrangeOpsCv[0]/ContactStrip[2]", + "childIndex" : 5, + "depth" : 3, + "layer" : 3, + "computedX" : 466.964, + "computedY" : 726.593, + "placementX" : 466.964, + "placementY" : 726.593, + "placementWidth" : 61.762, + "placementHeight" : 11.285, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 61.762, + "contentHeight" : 11.285, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "OrangeOpsCv[0]/ContactStrip[2]/ContactSeparator3[5]/Align[0]", + "entityName" : null, + "entityKind" : "Align", + "parentPath" : "OrangeOpsCv[0]/ContactStrip[2]/ContactSeparator3[5]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 466.964, + "computedY" : 726.593, + "placementX" : 466.964, + "placementY" : 726.593, + "placementWidth" : 61.762, + "placementHeight" : 11.285, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 61.762, + "contentHeight" : 11.285, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "OrangeOpsCv[0]/ContactStrip[2]/ContactSeparator3[5]/Align[0]/ContactSeparator3Line[0]", + "entityName" : "ContactSeparator3Line", + "entityKind" : "LineNode", + "parentPath" : "OrangeOpsCv[0]/ContactStrip[2]/ContactSeparator3[5]/Align[0]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 497.345, + "computedY" : 726.593, + "placementX" : 497.345, + "placementY" : 726.593, + "placementWidth" : 1.0, + "placementHeight" : 11.285, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 1.0, + "contentHeight" : 11.285, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "OrangeOpsCv[0]/ContactStrip[2]/Contact3[6]", + "entityName" : "Contact3", + "entityKind" : "ParagraphNode", + "parentPath" : "OrangeOpsCv[0]/ContactStrip[2]", + "childIndex" : 6, + "depth" : 3, + "layer" : 3, + "computedX" : 528.726, + "computedY" : 727.315, + "placementX" : 528.726, + "placementY" : 727.315, + "placementWidth" : 43.416, + "placementHeight" : 9.84, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 43.416, + "contentHeight" : 9.84, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "OrangeOpsCv[0]/Body[3]", + "entityName" : "Body", + "entityKind" : "RowNode", + "parentPath" : "OrangeOpsCv[0]", + "childIndex" : 3, + "depth" : 2, + "layer" : 2, + "computedX" : 0.0, + "computedY" : 19.603, + "placementX" : 0.0, + "placementY" : 19.603, + "placementWidth" : 595.276, + "placementHeight" : 694.788, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 595.276, + "contentHeight" : 694.788, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "OrangeOpsCv[0]/Body[3]/Aside[0]", + "entityName" : "Aside", + "entityKind" : "SectionNode", + "parentPath" : "OrangeOpsCv[0]/Body[3]", + "childIndex" : 0, + "depth" : 3, + "layer" : 3, + "computedX" : 0.0, + "computedY" : 43.159, + "placementX" : 0.0, + "placementY" : 43.159, + "placementWidth" : 215.258, + "placementHeight" : 671.232, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 215.258, + "contentHeight" : 671.232, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 17.774, + "bottom" : 0.0, + "left" : 23.134 + } + }, { + "path" : "OrangeOpsCv[0]/Body[3]/Aside[0]/Skills[0]", + "entityName" : "Skills", + "entityKind" : "SectionNode", + "parentPath" : "OrangeOpsCv[0]/Body[3]/Aside[0]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 23.134, + "computedY" : 554.212, + "placementX" : 23.134, + "placementY" : 554.212, + "placementWidth" : 144.83, + "placementHeight" : 160.179, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 144.83, + "contentHeight" : 160.179, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "OrangeOpsCv[0]/Body[3]/Aside[0]/Skills[0]/SkillsHeading[0]", + "entityName" : "SkillsHeading", + "entityKind" : "TableNode", + "parentPath" : "OrangeOpsCv[0]/Body[3]/Aside[0]/Skills[0]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 23.134, + "computedY" : 694.431, + "placementX" : 23.134, + "placementY" : 694.431, + "placementWidth" : 50.432, + "placementHeight" : 19.96, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 50.432, + "contentHeight" : 19.96, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "OrangeOpsCv[0]/Body[3]/Aside[0]/Skills[0]/SkillNames[1]", + "entityName" : "SkillNames", + "entityKind" : "ListNode", + "parentPath" : "OrangeOpsCv[0]/Body[3]/Aside[0]/Skills[0]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 23.134, + "computedY" : 554.212, + "placementX" : 23.134, + "placementY" : 554.212, + "placementWidth" : 144.83, + "placementHeight" : 133.973, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 144.83, + "contentHeight" : 133.973, + "margin" : { + "top" : 3.672, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "OrangeOpsCv[0]/Body[3]/Aside[0]/AfterSkillsRule[1]", + "entityName" : "AfterSkillsRule", + "entityKind" : "LineNode", + "parentPath" : "OrangeOpsCv[0]/Body[3]/Aside[0]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 23.134, + "computedY" : 540.288, + "placementX" : 23.134, + "placementY" : 540.288, + "placementWidth" : 174.351, + "placementHeight" : 1.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 174.351, + "contentHeight" : 1.0, + "margin" : { + "top" : 12.924, + "right" : 0.0, + "bottom" : 7.124, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "OrangeOpsCv[0]/Body[3]/Aside[0]/Achievements[2]", + "entityName" : "Achievements", + "entityKind" : "SectionNode", + "parentPath" : "OrangeOpsCv[0]/Body[3]/Aside[0]", + "childIndex" : 2, + "depth" : 4, + "layer" : 4, + "computedX" : 23.134, + "computedY" : 279.011, + "placementX" : 23.134, + "placementY" : 279.011, + "placementWidth" : 174.235, + "placementHeight" : 254.154, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 174.235, + "contentHeight" : 254.154, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "OrangeOpsCv[0]/Body[3]/Aside[0]/Achievements[2]/AchievementsHeading[0]", + "entityName" : "AchievementsHeading", + "entityKind" : "TableNode", + "parentPath" : "OrangeOpsCv[0]/Body[3]/Aside[0]/Achievements[2]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 23.134, + "computedY" : 513.205, + "placementX" : 23.134, + "placementY" : 513.205, + "placementWidth" : 68.131, + "placementHeight" : 19.96, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 68.131, + "contentHeight" : 19.96, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "OrangeOpsCv[0]/Body[3]/Aside[0]/Achievements[2]/AchievementCard0[1]", + "entityName" : "AchievementCard0", + "entityKind" : "LayerStackNode", + "parentPath" : "OrangeOpsCv[0]/Body[3]/Aside[0]/Achievements[2]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 23.134, + "computedY" : 453.087, + "placementX" : 23.134, + "placementY" : 453.087, + "placementWidth" : 171.555, + "placementHeight" : 53.872, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 171.555, + "contentHeight" : 53.872, + "margin" : { + "top" : 6.246, + "right" : 0.0, + "bottom" : 7.539, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "OrangeOpsCv[0]/Body[3]/Aside[0]/Achievements[2]/AchievementCard0[1]/AchievementBadge0Cell[0]", + "entityName" : "AchievementBadge0Cell", + "entityKind" : "SectionNode", + "parentPath" : "OrangeOpsCv[0]/Body[3]/Aside[0]/Achievements[2]/AchievementCard0[1]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 23.134, + "computedY" : 474.233, + "placementX" : 23.134, + "placementY" : 474.233, + "placementWidth" : 32.726, + "placementHeight" : 32.726, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 32.726, + "contentHeight" : 32.726, + "margin" : { + "top" : 0.0, + "right" : 132.597, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "OrangeOpsCv[0]/Body[3]/Aside[0]/Achievements[2]/AchievementCard0[1]/AchievementBadge0Cell[0]/AchievementBadge0[0]", + "entityName" : "AchievementBadge0", + "entityKind" : "ParagraphNode", + "parentPath" : "OrangeOpsCv[0]/Body[3]/Aside[0]/Achievements[2]/AchievementCard0[1]/AchievementBadge0Cell[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 23.134, + "computedY" : 474.233, + "placementX" : 23.134, + "placementY" : 474.233, + "placementWidth" : 32.726, + "placementHeight" : 32.726, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 32.726, + "contentHeight" : 32.726, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "OrangeOpsCv[0]/Body[3]/Aside[0]/Achievements[2]/AchievementCard0[1]/AchievementText0[1]", + "entityName" : "AchievementText0", + "entityKind" : "SectionNode", + "parentPath" : "OrangeOpsCv[0]/Body[3]/Aside[0]/Achievements[2]/AchievementCard0[1]", + "childIndex" : 1, + "depth" : 6, + "layer" : 6, + "computedX" : 64.888, + "computedY" : 453.087, + "placementX" : 64.888, + "placementY" : 453.087, + "placementWidth" : 124.723, + "placementHeight" : 53.872, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 124.723, + "contentHeight" : 53.872, + "margin" : { + "top" : 0.0, + "right" : 5.078, + "bottom" : 0.0, + "left" : 41.754 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "OrangeOpsCv[0]/Body[3]/Aside[0]/Achievements[2]/AchievementCard0[1]/AchievementText0[1]/AchievementTitle0[0]", + "entityName" : "AchievementTitle0", + "entityKind" : "ParagraphNode", + "parentPath" : "OrangeOpsCv[0]/Body[3]/Aside[0]/Achievements[2]/AchievementCard0[1]/AchievementText0[1]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 64.888, + "computedY" : 494.11, + "placementX" : 64.888, + "placementY" : 494.11, + "placementWidth" : 105.345, + "placementHeight" : 12.85, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 105.345, + "contentHeight" : 12.85, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.474, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "OrangeOpsCv[0]/Body[3]/Aside[0]/Achievements[2]/AchievementCard0[1]/AchievementText0[1]/AchievementBody0[1]", + "entityName" : "AchievementBody0", + "entityKind" : "ParagraphNode", + "parentPath" : "OrangeOpsCv[0]/Body[3]/Aside[0]/Achievements[2]/AchievementCard0[1]/AchievementText0[1]", + "childIndex" : 1, + "depth" : 7, + "layer" : 7, + "computedX" : 64.888, + "computedY" : 453.087, + "placementX" : 64.888, + "placementY" : 453.087, + "placementWidth" : 124.723, + "placementHeight" : 40.549, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 124.723, + "contentHeight" : 40.549, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "OrangeOpsCv[0]/Body[3]/Aside[0]/Achievements[2]/AchievementCard1[2]", + "entityName" : "AchievementCard1", + "entityKind" : "LayerStackNode", + "parentPath" : "OrangeOpsCv[0]/Body[3]/Aside[0]/Achievements[2]", + "childIndex" : 2, + "depth" : 5, + "layer" : 5, + "computedX" : 23.134, + "computedY" : 391.676, + "placementX" : 23.134, + "placementY" : 391.676, + "placementWidth" : 165.323, + "placementHeight" : 53.872, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 165.323, + "contentHeight" : 53.872, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 7.539, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "OrangeOpsCv[0]/Body[3]/Aside[0]/Achievements[2]/AchievementCard1[2]/AchievementBadge1Cell[0]", + "entityName" : "AchievementBadge1Cell", + "entityKind" : "SectionNode", + "parentPath" : "OrangeOpsCv[0]/Body[3]/Aside[0]/Achievements[2]/AchievementCard1[2]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 23.134, + "computedY" : 412.822, + "placementX" : 23.134, + "placementY" : 412.822, + "placementWidth" : 32.726, + "placementHeight" : 32.726, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 32.726, + "contentHeight" : 32.726, + "margin" : { + "top" : 0.0, + "right" : 132.597, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "OrangeOpsCv[0]/Body[3]/Aside[0]/Achievements[2]/AchievementCard1[2]/AchievementBadge1Cell[0]/AchievementBadge1[0]", + "entityName" : "AchievementBadge1", + "entityKind" : "ParagraphNode", + "parentPath" : "OrangeOpsCv[0]/Body[3]/Aside[0]/Achievements[2]/AchievementCard1[2]/AchievementBadge1Cell[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 23.134, + "computedY" : 412.822, + "placementX" : 23.134, + "placementY" : 412.822, + "placementWidth" : 32.726, + "placementHeight" : 32.726, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 32.726, + "contentHeight" : 32.726, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "OrangeOpsCv[0]/Body[3]/Aside[0]/Achievements[2]/AchievementCard1[2]/AchievementText1[1]", + "entityName" : "AchievementText1", + "entityKind" : "SectionNode", + "parentPath" : "OrangeOpsCv[0]/Body[3]/Aside[0]/Achievements[2]/AchievementCard1[2]", + "childIndex" : 1, + "depth" : 6, + "layer" : 6, + "computedX" : 64.888, + "computedY" : 391.676, + "placementX" : 64.888, + "placementY" : 391.676, + "placementWidth" : 113.82, + "placementHeight" : 53.872, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 113.82, + "contentHeight" : 53.872, + "margin" : { + "top" : 0.0, + "right" : 5.078, + "bottom" : 0.0, + "left" : 41.754 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "OrangeOpsCv[0]/Body[3]/Aside[0]/Achievements[2]/AchievementCard1[2]/AchievementText1[1]/AchievementTitle1[0]", + "entityName" : "AchievementTitle1", + "entityKind" : "ParagraphNode", + "parentPath" : "OrangeOpsCv[0]/Body[3]/Aside[0]/Achievements[2]/AchievementCard1[2]/AchievementText1[1]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 64.888, + "computedY" : 432.699, + "placementX" : 64.888, + "placementY" : 432.699, + "placementWidth" : 105.007, + "placementHeight" : 12.85, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 105.007, + "contentHeight" : 12.85, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.474, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "OrangeOpsCv[0]/Body[3]/Aside[0]/Achievements[2]/AchievementCard1[2]/AchievementText1[1]/AchievementBody1[1]", + "entityName" : "AchievementBody1", + "entityKind" : "ParagraphNode", + "parentPath" : "OrangeOpsCv[0]/Body[3]/Aside[0]/Achievements[2]/AchievementCard1[2]/AchievementText1[1]", + "childIndex" : 1, + "depth" : 7, + "layer" : 7, + "computedX" : 64.888, + "computedY" : 391.676, + "placementX" : 64.888, + "placementY" : 391.676, + "placementWidth" : 113.82, + "placementHeight" : 40.549, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 113.82, + "contentHeight" : 40.549, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "OrangeOpsCv[0]/Body[3]/Aside[0]/Achievements[2]/AchievementCard2[3]", + "entityName" : "AchievementCard2", + "entityKind" : "LayerStackNode", + "parentPath" : "OrangeOpsCv[0]/Body[3]/Aside[0]/Achievements[2]", + "childIndex" : 3, + "depth" : 5, + "layer" : 5, + "computedX" : 23.134, + "computedY" : 340.422, + "placementX" : 23.134, + "placementY" : 340.422, + "placementWidth" : 171.816, + "placementHeight" : 43.716, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 171.816, + "contentHeight" : 43.716, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 7.539, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "OrangeOpsCv[0]/Body[3]/Aside[0]/Achievements[2]/AchievementCard2[3]/AchievementBadge2Cell[0]", + "entityName" : "AchievementBadge2Cell", + "entityKind" : "SectionNode", + "parentPath" : "OrangeOpsCv[0]/Body[3]/Aside[0]/Achievements[2]/AchievementCard2[3]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 23.134, + "computedY" : 351.412, + "placementX" : 23.134, + "placementY" : 351.412, + "placementWidth" : 32.726, + "placementHeight" : 32.726, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 32.726, + "contentHeight" : 32.726, + "margin" : { + "top" : 0.0, + "right" : 132.597, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "OrangeOpsCv[0]/Body[3]/Aside[0]/Achievements[2]/AchievementCard2[3]/AchievementBadge2Cell[0]/AchievementBadge2[0]", + "entityName" : "AchievementBadge2", + "entityKind" : "ParagraphNode", + "parentPath" : "OrangeOpsCv[0]/Body[3]/Aside[0]/Achievements[2]/AchievementCard2[3]/AchievementBadge2Cell[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 23.134, + "computedY" : 351.412, + "placementX" : 23.134, + "placementY" : 351.412, + "placementWidth" : 32.726, + "placementHeight" : 32.726, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 32.726, + "contentHeight" : 32.726, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "OrangeOpsCv[0]/Body[3]/Aside[0]/Achievements[2]/AchievementCard2[3]/AchievementText2[1]", + "entityName" : "AchievementText2", + "entityKind" : "SectionNode", + "parentPath" : "OrangeOpsCv[0]/Body[3]/Aside[0]/Achievements[2]/AchievementCard2[3]", + "childIndex" : 1, + "depth" : 6, + "layer" : 6, + "computedX" : 64.888, + "computedY" : 340.422, + "placementX" : 64.888, + "placementY" : 340.422, + "placementWidth" : 124.984, + "placementHeight" : 43.716, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 124.984, + "contentHeight" : 43.716, + "margin" : { + "top" : 0.0, + "right" : 5.078, + "bottom" : 0.0, + "left" : 41.754 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "OrangeOpsCv[0]/Body[3]/Aside[0]/Achievements[2]/AchievementCard2[3]/AchievementText2[1]/AchievementTitle2[0]", + "entityName" : "AchievementTitle2", + "entityKind" : "ParagraphNode", + "parentPath" : "OrangeOpsCv[0]/Body[3]/Aside[0]/Achievements[2]/AchievementCard2[3]/AchievementText2[1]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 64.888, + "computedY" : 371.288, + "placementX" : 64.888, + "placementY" : 371.288, + "placementWidth" : 93.51, + "placementHeight" : 12.85, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 93.51, + "contentHeight" : 12.85, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.474, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "OrangeOpsCv[0]/Body[3]/Aside[0]/Achievements[2]/AchievementCard2[3]/AchievementText2[1]/AchievementBody2[1]", + "entityName" : "AchievementBody2", + "entityKind" : "ParagraphNode", + "parentPath" : "OrangeOpsCv[0]/Body[3]/Aside[0]/Achievements[2]/AchievementCard2[3]/AchievementText2[1]", + "childIndex" : 1, + "depth" : 7, + "layer" : 7, + "computedX" : 64.888, + "computedY" : 340.422, + "placementX" : 64.888, + "placementY" : 340.422, + "placementWidth" : 124.984, + "placementHeight" : 30.393, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 124.984, + "contentHeight" : 30.393, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "OrangeOpsCv[0]/Body[3]/Aside[0]/Achievements[2]/AchievementCard3[4]", + "entityName" : "AchievementCard3", + "entityKind" : "LayerStackNode", + "parentPath" : "OrangeOpsCv[0]/Body[3]/Aside[0]/Achievements[2]", + "childIndex" : 4, + "depth" : 5, + "layer" : 5, + "computedX" : 23.134, + "computedY" : 279.011, + "placementX" : 23.134, + "placementY" : 279.011, + "placementWidth" : 174.235, + "placementHeight" : 53.872, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 174.235, + "contentHeight" : 53.872, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "OrangeOpsCv[0]/Body[3]/Aside[0]/Achievements[2]/AchievementCard3[4]/AchievementBadge3Cell[0]", + "entityName" : "AchievementBadge3Cell", + "entityKind" : "SectionNode", + "parentPath" : "OrangeOpsCv[0]/Body[3]/Aside[0]/Achievements[2]/AchievementCard3[4]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 23.134, + "computedY" : 300.157, + "placementX" : 23.134, + "placementY" : 300.157, + "placementWidth" : 32.726, + "placementHeight" : 32.726, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 32.726, + "contentHeight" : 32.726, + "margin" : { + "top" : 0.0, + "right" : 132.597, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "OrangeOpsCv[0]/Body[3]/Aside[0]/Achievements[2]/AchievementCard3[4]/AchievementBadge3Cell[0]/AchievementBadge3[0]", + "entityName" : "AchievementBadge3", + "entityKind" : "ParagraphNode", + "parentPath" : "OrangeOpsCv[0]/Body[3]/Aside[0]/Achievements[2]/AchievementCard3[4]/AchievementBadge3Cell[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 23.134, + "computedY" : 300.157, + "placementX" : 23.134, + "placementY" : 300.157, + "placementWidth" : 32.726, + "placementHeight" : 32.726, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 32.726, + "contentHeight" : 32.726, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "OrangeOpsCv[0]/Body[3]/Aside[0]/Achievements[2]/AchievementCard3[4]/AchievementText3[1]", + "entityName" : "AchievementText3", + "entityKind" : "SectionNode", + "parentPath" : "OrangeOpsCv[0]/Body[3]/Aside[0]/Achievements[2]/AchievementCard3[4]", + "childIndex" : 1, + "depth" : 6, + "layer" : 6, + "computedX" : 64.888, + "computedY" : 279.011, + "placementX" : 64.888, + "placementY" : 279.011, + "placementWidth" : 127.403, + "placementHeight" : 53.872, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 127.403, + "contentHeight" : 53.872, + "margin" : { + "top" : 0.0, + "right" : 5.078, + "bottom" : 0.0, + "left" : 41.754 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "OrangeOpsCv[0]/Body[3]/Aside[0]/Achievements[2]/AchievementCard3[4]/AchievementText3[1]/AchievementTitle3[0]", + "entityName" : "AchievementTitle3", + "entityKind" : "ParagraphNode", + "parentPath" : "OrangeOpsCv[0]/Body[3]/Aside[0]/Achievements[2]/AchievementCard3[4]/AchievementText3[1]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 64.888, + "computedY" : 320.034, + "placementX" : 64.888, + "placementY" : 320.034, + "placementWidth" : 74.956, + "placementHeight" : 12.85, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 74.956, + "contentHeight" : 12.85, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.474, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "OrangeOpsCv[0]/Body[3]/Aside[0]/Achievements[2]/AchievementCard3[4]/AchievementText3[1]/AchievementBody3[1]", + "entityName" : "AchievementBody3", + "entityKind" : "ParagraphNode", + "parentPath" : "OrangeOpsCv[0]/Body[3]/Aside[0]/Achievements[2]/AchievementCard3[4]/AchievementText3[1]", + "childIndex" : 1, + "depth" : 7, + "layer" : 7, + "computedX" : 64.888, + "computedY" : 279.011, + "placementX" : 64.888, + "placementY" : 279.011, + "placementWidth" : 127.403, + "placementHeight" : 40.549, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 127.403, + "contentHeight" : 40.549, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "OrangeOpsCv[0]/Body[3]/Aside[0]/AfterAchievementsRule[3]", + "entityName" : "AfterAchievementsRule", + "entityKind" : "LineNode", + "parentPath" : "OrangeOpsCv[0]/Body[3]/Aside[0]", + "childIndex" : 3, + "depth" : 4, + "layer" : 4, + "computedX" : 23.134, + "computedY" : 267.387, + "placementX" : 23.134, + "placementY" : 267.387, + "placementWidth" : 174.351, + "placementHeight" : 1.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 174.351, + "contentHeight" : 1.0, + "margin" : { + "top" : 10.624, + "right" : 0.0, + "bottom" : 4.303, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "OrangeOpsCv[0]/Body[3]/Aside[0]/Education[4]", + "entityName" : "Education", + "entityKind" : "SectionNode", + "parentPath" : "OrangeOpsCv[0]/Body[3]/Aside[0]", + "childIndex" : 4, + "depth" : 4, + "layer" : 4, + "computedX" : 23.134, + "computedY" : 179.402, + "placementX" : 23.134, + "placementY" : 179.402, + "placementWidth" : 173.098, + "placementHeight" : 83.682, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 173.098, + "contentHeight" : 83.682, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "OrangeOpsCv[0]/Body[3]/Aside[0]/Education[4]/EducationHeading[0]", + "entityName" : "EducationHeading", + "entityKind" : "TableNode", + "parentPath" : "OrangeOpsCv[0]/Body[3]/Aside[0]/Education[4]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 23.134, + "computedY" : 243.124, + "placementX" : 23.134, + "placementY" : 243.124, + "placementWidth" : 51.453, + "placementHeight" : 19.96, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 51.453, + "contentHeight" : 19.96, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "OrangeOpsCv[0]/Body[3]/Aside[0]/Education[4]/EducationEntry[1]", + "entityName" : "EducationEntry", + "entityKind" : "LayerStackNode", + "parentPath" : "OrangeOpsCv[0]/Body[3]/Aside[0]/Education[4]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 23.134, + "computedY" : 179.402, + "placementX" : 23.134, + "placementY" : 179.402, + "placementWidth" : 173.098, + "placementHeight" : 57.476, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 173.098, + "contentHeight" : 57.476, + "margin" : { + "top" : 6.246, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "OrangeOpsCv[0]/Body[3]/Aside[0]/Education[4]/EducationEntry[1]/EducationBadgeCell[0]", + "entityName" : "EducationBadgeCell", + "entityKind" : "SectionNode", + "parentPath" : "OrangeOpsCv[0]/Body[3]/Aside[0]/Education[4]/EducationEntry[1]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 23.134, + "computedY" : 207.538, + "placementX" : 23.134, + "placementY" : 207.538, + "placementWidth" : 29.341, + "placementHeight" : 29.341, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 29.341, + "contentHeight" : 29.341, + "margin" : { + "top" : 0.0, + "right" : 135.418, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "OrangeOpsCv[0]/Body[3]/Aside[0]/Education[4]/EducationEntry[1]/EducationBadgeCell[0]/EducationBadge[0]", + "entityName" : "EducationBadge", + "entityKind" : "ParagraphNode", + "parentPath" : "OrangeOpsCv[0]/Body[3]/Aside[0]/Education[4]/EducationEntry[1]/EducationBadgeCell[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 23.134, + "computedY" : 207.538, + "placementX" : 23.134, + "placementY" : 207.538, + "placementWidth" : 29.341, + "placementHeight" : 29.341, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 29.341, + "contentHeight" : 29.341, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "OrangeOpsCv[0]/Body[3]/Aside[0]/Education[4]/EducationEntry[1]/EducationText[1]", + "entityName" : "EducationText", + "entityKind" : "SectionNode", + "parentPath" : "OrangeOpsCv[0]/Body[3]/Aside[0]/Education[4]/EducationEntry[1]", + "childIndex" : 1, + "depth" : 6, + "layer" : 6, + "computedX" : 62.067, + "computedY" : 179.402, + "placementX" : 62.067, + "placementY" : 179.402, + "placementWidth" : 134.165, + "placementHeight" : 57.476, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 134.165, + "contentHeight" : 57.476, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 38.933 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "OrangeOpsCv[0]/Body[3]/Aside[0]/Education[4]/EducationEntry[1]/EducationText[1]/EducationDegree[0]", + "entityName" : "EducationDegree", + "entityKind" : "ParagraphNode", + "parentPath" : "OrangeOpsCv[0]/Body[3]/Aside[0]/Education[4]/EducationEntry[1]/EducationText[1]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 62.067, + "computedY" : 214.95, + "placementX" : 62.067, + "placementY" : 214.95, + "placementWidth" : 134.165, + "placementHeight" : 21.929, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 134.165, + "contentHeight" : 21.929, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "OrangeOpsCv[0]/Body[3]/Aside[0]/Education[4]/EducationEntry[1]/EducationText[1]/EducationLine0[1]", + "entityName" : "EducationLine0", + "entityKind" : "ParagraphNode", + "parentPath" : "OrangeOpsCv[0]/Body[3]/Aside[0]/Education[4]/EducationEntry[1]/EducationText[1]", + "childIndex" : 1, + "depth" : 7, + "layer" : 7, + "computedX" : 62.067, + "computedY" : 203.101, + "placementX" : 62.067, + "placementY" : 203.101, + "placementWidth" : 82.76, + "placementHeight" : 9.6, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 82.76, + "contentHeight" : 9.6, + "margin" : { + "top" : 2.249, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "OrangeOpsCv[0]/Body[3]/Aside[0]/Education[4]/EducationEntry[1]/EducationText[1]/EducationLine1[2]", + "entityName" : "EducationLine1", + "entityKind" : "ParagraphNode", + "parentPath" : "OrangeOpsCv[0]/Body[3]/Aside[0]/Education[4]/EducationEntry[1]/EducationText[1]", + "childIndex" : 2, + "depth" : 7, + "layer" : 7, + "computedX" : 62.067, + "computedY" : 191.252, + "placementX" : 62.067, + "placementY" : 191.252, + "placementWidth" : 46.832, + "placementHeight" : 9.6, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 46.832, + "contentHeight" : 9.6, + "margin" : { + "top" : 2.249, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "OrangeOpsCv[0]/Body[3]/Aside[0]/Education[4]/EducationEntry[1]/EducationText[1]/EducationLine2[3]", + "entityName" : "EducationLine2", + "entityKind" : "ParagraphNode", + "parentPath" : "OrangeOpsCv[0]/Body[3]/Aside[0]/Education[4]/EducationEntry[1]/EducationText[1]", + "childIndex" : 3, + "depth" : 7, + "layer" : 7, + "computedX" : 62.067, + "computedY" : 179.402, + "placementX" : 62.067, + "placementY" : 179.402, + "placementWidth" : 45.856, + "placementHeight" : 9.6, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 45.856, + "contentHeight" : 9.6, + "margin" : { + "top" : 2.249, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "OrangeOpsCv[0]/Body[3]/Aside[0]/AfterEducationRule[5]", + "entityName" : "AfterEducationRule", + "entityKind" : "LineNode", + "parentPath" : "OrangeOpsCv[0]/Body[3]/Aside[0]", + "childIndex" : 5, + "depth" : 4, + "layer" : 4, + "computedX" : 23.134, + "computedY" : 169.95, + "placementX" : 23.134, + "placementY" : 169.95, + "placementWidth" : 174.351, + "placementHeight" : 1.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 174.351, + "contentHeight" : 1.0, + "margin" : { + "top" : 8.452, + "right" : 0.0, + "bottom" : 3.174, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "OrangeOpsCv[0]/Body[3]/Aside[0]/Certifications[6]", + "entityName" : "Certifications", + "entityKind" : "SectionNode", + "parentPath" : "OrangeOpsCv[0]/Body[3]/Aside[0]", + "childIndex" : 6, + "depth" : 4, + "layer" : 4, + "computedX" : 23.134, + "computedY" : 43.159, + "placementX" : 23.134, + "placementY" : 43.159, + "placementWidth" : 167.481, + "placementHeight" : 123.617, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 167.481, + "contentHeight" : 123.617, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "OrangeOpsCv[0]/Body[3]/Aside[0]/Certifications[6]/CertificationsHeading[0]", + "entityName" : "CertificationsHeading", + "entityKind" : "TableNode", + "parentPath" : "OrangeOpsCv[0]/Body[3]/Aside[0]/Certifications[6]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 23.134, + "computedY" : 146.816, + "placementX" : 23.134, + "placementY" : 146.816, + "placementWidth" : 73.37, + "placementHeight" : 19.96, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 73.37, + "contentHeight" : 19.96, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "OrangeOpsCv[0]/Body[3]/Aside[0]/Certifications[6]/CertTitle0[1]", + "entityName" : "CertTitle0", + "entityKind" : "ParagraphNode", + "parentPath" : "OrangeOpsCv[0]/Body[3]/Aside[0]/Certifications[6]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 23.134, + "computedY" : 130.731, + "placementX" : 23.134, + "placementY" : 130.731, + "placementWidth" : 93.095, + "placementHeight" : 9.84, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 93.095, + "contentHeight" : 9.84, + "margin" : { + "top" : 6.246, + "right" : 0.0, + "bottom" : 1.546, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "OrangeOpsCv[0]/Body[3]/Aside[0]/Certifications[6]/CertIssuer0[2]", + "entityName" : "CertIssuer0", + "entityKind" : "ParagraphNode", + "parentPath" : "OrangeOpsCv[0]/Body[3]/Aside[0]/Certifications[6]", + "childIndex" : 2, + "depth" : 5, + "layer" : 5, + "computedX" : 23.134, + "computedY" : 121.025, + "placementX" : 23.134, + "placementY" : 121.025, + "placementWidth" : 167.481, + "placementHeight" : 8.16, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 167.481, + "contentHeight" : 8.16, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 6.409, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 9.028 + } + }, { + "path" : "OrangeOpsCv[0]/Body[3]/Aside[0]/Certifications[6]/CertTitle1[3]", + "entityName" : "CertTitle1", + "entityKind" : "ParagraphNode", + "parentPath" : "OrangeOpsCv[0]/Body[3]/Aside[0]/Certifications[6]", + "childIndex" : 3, + "depth" : 5, + "layer" : 5, + "computedX" : 23.134, + "computedY" : 104.775, + "placementX" : 23.134, + "placementY" : 104.775, + "placementWidth" : 108.962, + "placementHeight" : 9.84, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 108.962, + "contentHeight" : 9.84, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 1.546, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "OrangeOpsCv[0]/Body[3]/Aside[0]/Certifications[6]/CertIssuer1[4]", + "entityName" : "CertIssuer1", + "entityKind" : "ParagraphNode", + "parentPath" : "OrangeOpsCv[0]/Body[3]/Aside[0]/Certifications[6]", + "childIndex" : 4, + "depth" : 5, + "layer" : 5, + "computedX" : 23.134, + "computedY" : 95.069, + "placementX" : 23.134, + "placementY" : 95.069, + "placementWidth" : 108.485, + "placementHeight" : 8.16, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 108.485, + "contentHeight" : 8.16, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 6.409, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 9.028 + } + }, { + "path" : "OrangeOpsCv[0]/Body[3]/Aside[0]/Certifications[6]/CertTitle2[5]", + "entityName" : "CertTitle2", + "entityKind" : "ParagraphNode", + "parentPath" : "OrangeOpsCv[0]/Body[3]/Aside[0]/Certifications[6]", + "childIndex" : 5, + "depth" : 5, + "layer" : 5, + "computedX" : 23.134, + "computedY" : 78.82, + "placementX" : 23.134, + "placementY" : 78.82, + "placementWidth" : 158.679, + "placementHeight" : 9.84, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 158.679, + "contentHeight" : 9.84, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 1.546, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "OrangeOpsCv[0]/Body[3]/Aside[0]/Certifications[6]/CertIssuer2[6]", + "entityName" : "CertIssuer2", + "entityKind" : "ParagraphNode", + "parentPath" : "OrangeOpsCv[0]/Body[3]/Aside[0]/Certifications[6]", + "childIndex" : 6, + "depth" : 5, + "layer" : 5, + "computedX" : 23.134, + "computedY" : 69.114, + "placementX" : 23.134, + "placementY" : 69.114, + "placementWidth" : 50.834, + "placementHeight" : 8.16, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 50.834, + "contentHeight" : 8.16, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 6.409, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 9.028 + } + }, { + "path" : "OrangeOpsCv[0]/Body[3]/Aside[0]/Certifications[6]/CertTitle3[7]", + "entityName" : "CertTitle3", + "entityKind" : "ParagraphNode", + "parentPath" : "OrangeOpsCv[0]/Body[3]/Aside[0]/Certifications[6]", + "childIndex" : 7, + "depth" : 5, + "layer" : 5, + "computedX" : 23.134, + "computedY" : 52.865, + "placementX" : 23.134, + "placementY" : 52.865, + "placementWidth" : 73.645, + "placementHeight" : 9.84, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 73.645, + "contentHeight" : 9.84, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 1.546, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "OrangeOpsCv[0]/Body[3]/Aside[0]/Certifications[6]/CertIssuer3[8]", + "entityName" : "CertIssuer3", + "entityKind" : "ParagraphNode", + "parentPath" : "OrangeOpsCv[0]/Body[3]/Aside[0]/Certifications[6]", + "childIndex" : 8, + "depth" : 5, + "layer" : 5, + "computedX" : 23.134, + "computedY" : 43.159, + "placementX" : 23.134, + "placementY" : 43.159, + "placementWidth" : 89.587, + "placementHeight" : 8.16, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 89.587, + "contentHeight" : 8.16, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 9.028 + } + }, { + "path" : "OrangeOpsCv[0]/Body[3]/Main[1]", + "entityName" : "Main", + "entityKind" : "SectionNode", + "parentPath" : "OrangeOpsCv[0]/Body[3]", + "childIndex" : 1, + "depth" : 3, + "layer" : 3, + "computedX" : 215.258, + "computedY" : 19.603, + "placementX" : 215.258, + "placementY" : 19.603, + "placementWidth" : 380.017, + "placementHeight" : 694.788, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 380.017, + "contentHeight" : 694.788, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 23.134, + "bottom" : 0.0, + "left" : 17.774 + } + }, { + "path" : "OrangeOpsCv[0]/Body[3]/Main[1]/Profile[0]", + "entityName" : "Profile", + "entityKind" : "SectionNode", + "parentPath" : "OrangeOpsCv[0]/Body[3]/Main[1]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 233.032, + "computedY" : 621.921, + "placementX" : 233.032, + "placementY" : 621.921, + "placementWidth" : 324.449, + "placementHeight" : 92.47, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 324.449, + "contentHeight" : 92.47, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "OrangeOpsCv[0]/Body[3]/Main[1]/Profile[0]/ProfileHeading[0]", + "entityName" : "ProfileHeading", + "entityKind" : "ParagraphNode", + "parentPath" : "OrangeOpsCv[0]/Body[3]/Main[1]/Profile[0]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 233.032, + "computedY" : 697.588, + "placementX" : 233.032, + "placementY" : 697.588, + "placementWidth" : 107.962, + "placementHeight" : 16.803, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 107.962, + "contentHeight" : 16.803, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 1.464, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "OrangeOpsCv[0]/Body[3]/Main[1]/Profile[0]/ProfileHeadingRule[1]", + "entityName" : "ProfileHeadingRule", + "entityKind" : "LineNode", + "parentPath" : "OrangeOpsCv[0]/Body[3]/Main[1]/Profile[0]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 233.032, + "computedY" : 694.431, + "placementX" : 233.032, + "placementY" : 694.431, + "placementWidth" : 62.067, + "placementHeight" : 1.693, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 62.067, + "contentHeight" : 1.693, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "OrangeOpsCv[0]/Body[3]/Main[1]/Profile[0]/ProfileBody[2]", + "entityName" : "ProfileBody", + "entityKind" : "ParagraphNode", + "parentPath" : "OrangeOpsCv[0]/Body[3]/Main[1]/Profile[0]", + "childIndex" : 2, + "depth" : 5, + "layer" : 5, + "computedX" : 233.032, + "computedY" : 621.921, + "placementX" : 233.032, + "placementY" : 621.921, + "placementWidth" : 324.449, + "placementHeight" : 67.957, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 324.449, + "contentHeight" : 67.957, + "margin" : { + "top" : 4.553, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "OrangeOpsCv[0]/Body[3]/Main[1]/AfterProfileRule[1]", + "entityName" : "AfterProfileRule", + "entityKind" : "LineNode", + "parentPath" : "OrangeOpsCv[0]/Body[3]/Main[1]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 233.032, + "computedY" : 606.869, + "placementX" : 233.032, + "placementY" : 606.869, + "placementWidth" : 339.11, + "placementHeight" : 1.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 339.11, + "contentHeight" : 1.0, + "margin" : { + "top" : 14.052, + "right" : 0.0, + "bottom" : 7.688, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "OrangeOpsCv[0]/Body[3]/Main[1]/Experience[2]", + "entityName" : "Experience", + "entityKind" : "SectionNode", + "parentPath" : "OrangeOpsCv[0]/Body[3]/Main[1]", + "childIndex" : 2, + "depth" : 4, + "layer" : 4, + "computedX" : 233.032, + "computedY" : 210.656, + "placementX" : 233.032, + "placementY" : 210.656, + "placementWidth" : 339.11, + "placementHeight" : 388.525, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 339.11, + "contentHeight" : 388.525, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "OrangeOpsCv[0]/Body[3]/Main[1]/Experience[2]/ExperienceHeading[0]", + "entityName" : "ExperienceHeading", + "entityKind" : "ParagraphNode", + "parentPath" : "OrangeOpsCv[0]/Body[3]/Main[1]/Experience[2]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 233.032, + "computedY" : 582.378, + "placementX" : 233.032, + "placementY" : 582.378, + "placementWidth" : 84.549, + "placementHeight" : 16.803, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 84.549, + "contentHeight" : 16.803, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 1.464, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "OrangeOpsCv[0]/Body[3]/Main[1]/Experience[2]/ExperienceHeadingRule[1]", + "entityName" : "ExperienceHeadingRule", + "entityKind" : "LineNode", + "parentPath" : "OrangeOpsCv[0]/Body[3]/Main[1]/Experience[2]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 233.032, + "computedY" : 579.221, + "placementX" : 233.032, + "placementY" : 579.221, + "placementWidth" : 62.067, + "placementHeight" : 1.693, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 62.067, + "contentHeight" : 1.693, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "OrangeOpsCv[0]/Body[3]/Main[1]/Experience[2]/JobHead0[2]", + "entityName" : "JobHead0", + "entityKind" : "TableNode", + "parentPath" : "OrangeOpsCv[0]/Body[3]/Main[1]/Experience[2]", + "childIndex" : 2, + "depth" : 5, + "layer" : 5, + "computedX" : 233.032, + "computedY" : 561.549, + "placementX" : 233.032, + "placementY" : 561.549, + "placementWidth" : 339.11, + "placementHeight" : 11.16, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 339.11, + "contentHeight" : 11.16, + "margin" : { + "top" : 6.512, + "right" : 0.0, + "bottom" : 1.65, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "OrangeOpsCv[0]/Body[3]/Main[1]/Experience[2]/JobCompany0[3]", + "entityName" : "JobCompany0", + "entityKind" : "ParagraphNode", + "parentPath" : "OrangeOpsCv[0]/Body[3]/Main[1]/Experience[2]", + "childIndex" : 3, + "depth" : 5, + "layer" : 5, + "computedX" : 233.032, + "computedY" : 549.698, + "placementX" : 233.032, + "placementY" : 549.698, + "placementWidth" : 172.669, + "placementHeight" : 10.2, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 172.669, + "contentHeight" : 10.2, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 5.117, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "OrangeOpsCv[0]/Body[3]/Main[1]/Experience[2]/JobBullets0[4]", + "entityName" : "JobBullets0", + "entityKind" : "ListNode", + "parentPath" : "OrangeOpsCv[0]/Body[3]/Main[1]/Experience[2]", + "childIndex" : 4, + "depth" : 5, + "layer" : 5, + "computedX" : 233.032, + "computedY" : 404.607, + "placementX" : 233.032, + "placementY" : 404.607, + "placementWidth" : 337.627, + "placementHeight" : 139.975, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 337.627, + "contentHeight" : 139.975, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "OrangeOpsCv[0]/Body[3]/Main[1]/Experience[2]/JobHead1[5]", + "entityName" : "JobHead1", + "entityKind" : "TableNode", + "parentPath" : "OrangeOpsCv[0]/Body[3]/Main[1]/Experience[2]", + "childIndex" : 5, + "depth" : 5, + "layer" : 5, + "computedX" : 233.032, + "computedY" : 380.728, + "placementX" : 233.032, + "placementY" : 380.728, + "placementWidth" : 339.11, + "placementHeight" : 11.16, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 339.11, + "contentHeight" : 11.16, + "margin" : { + "top" : 12.719, + "right" : 0.0, + "bottom" : 1.65, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "OrangeOpsCv[0]/Body[3]/Main[1]/Experience[2]/JobCompany1[6]", + "entityName" : "JobCompany1", + "entityKind" : "ParagraphNode", + "parentPath" : "OrangeOpsCv[0]/Body[3]/Main[1]/Experience[2]", + "childIndex" : 6, + "depth" : 5, + "layer" : 5, + "computedX" : 233.032, + "computedY" : 368.878, + "placementX" : 233.032, + "placementY" : 368.878, + "placementWidth" : 142.052, + "placementHeight" : 10.2, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 142.052, + "contentHeight" : 10.2, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 5.117, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "OrangeOpsCv[0]/Body[3]/Main[1]/Experience[2]/JobBullets1[7]", + "entityName" : "JobBullets1", + "entityKind" : "ListNode", + "parentPath" : "OrangeOpsCv[0]/Body[3]/Main[1]/Experience[2]", + "childIndex" : 7, + "depth" : 5, + "layer" : 5, + "computedX" : 233.032, + "computedY" : 301.019, + "placementX" : 233.032, + "placementY" : 301.019, + "placementWidth" : 318.349, + "placementHeight" : 62.742, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 318.349, + "contentHeight" : 62.742, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "OrangeOpsCv[0]/Body[3]/Main[1]/Experience[2]/JobHead2[8]", + "entityName" : "JobHead2", + "entityKind" : "TableNode", + "parentPath" : "OrangeOpsCv[0]/Body[3]/Main[1]/Experience[2]", + "childIndex" : 8, + "depth" : 5, + "layer" : 5, + "computedX" : 233.032, + "computedY" : 277.14, + "placementX" : 233.032, + "placementY" : 277.14, + "placementWidth" : 339.11, + "placementHeight" : 11.16, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 339.11, + "contentHeight" : 11.16, + "margin" : { + "top" : 12.719, + "right" : 0.0, + "bottom" : 1.65, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "OrangeOpsCv[0]/Body[3]/Main[1]/Experience[2]/JobCompany2[9]", + "entityName" : "JobCompany2", + "entityKind" : "ParagraphNode", + "parentPath" : "OrangeOpsCv[0]/Body[3]/Main[1]/Experience[2]", + "childIndex" : 9, + "depth" : 5, + "layer" : 5, + "computedX" : 233.032, + "computedY" : 265.289, + "placementX" : 233.032, + "placementY" : 265.289, + "placementWidth" : 154.394, + "placementHeight" : 10.2, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 154.394, + "contentHeight" : 10.2, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 5.117, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "OrangeOpsCv[0]/Body[3]/Main[1]/Experience[2]/JobBullets2[10]", + "entityName" : "JobBullets2", + "entityKind" : "ListNode", + "parentPath" : "OrangeOpsCv[0]/Body[3]/Main[1]/Experience[2]", + "childIndex" : 10, + "depth" : 5, + "layer" : 5, + "computedX" : 233.032, + "computedY" : 210.656, + "placementX" : 233.032, + "placementY" : 210.656, + "placementWidth" : 271.871, + "placementHeight" : 49.516, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 271.871, + "contentHeight" : 49.516, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "OrangeOpsCv[0]/Body[3]/Main[1]/AfterExperienceRule[3]", + "entityName" : "AfterExperienceRule", + "entityKind" : "LineNode", + "parentPath" : "OrangeOpsCv[0]/Body[3]/Main[1]", + "childIndex" : 3, + "depth" : 4, + "layer" : 4, + "computedX" : 233.032, + "computedY" : 196.114, + "placementX" : 233.032, + "placementY" : 196.114, + "placementWidth" : 339.11, + "placementHeight" : 1.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 339.11, + "contentHeight" : 1.0, + "margin" : { + "top" : 13.542, + "right" : 0.0, + "bottom" : 4.867, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "OrangeOpsCv[0]/Body[3]/Main[1]/Metrics[4]", + "entityName" : "Metrics", + "entityKind" : "SectionNode", + "parentPath" : "OrangeOpsCv[0]/Body[3]/Main[1]", + "childIndex" : 4, + "depth" : 4, + "layer" : 4, + "computedX" : 233.032, + "computedY" : 105.913, + "placementX" : 233.032, + "placementY" : 105.913, + "placementWidth" : 339.11, + "placementHeight" : 85.335, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 339.11, + "contentHeight" : 85.335, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "OrangeOpsCv[0]/Body[3]/Main[1]/Metrics[4]/MetricsHeading[0]", + "entityName" : "MetricsHeading", + "entityKind" : "ParagraphNode", + "parentPath" : "OrangeOpsCv[0]/Body[3]/Main[1]/Metrics[4]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 233.032, + "computedY" : 174.444, + "placementX" : 233.032, + "placementY" : 174.444, + "placementWidth" : 167.607, + "placementHeight" : 16.803, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 167.607, + "contentHeight" : 16.803, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 1.464, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "OrangeOpsCv[0]/Body[3]/Main[1]/Metrics[4]/MetricsHeadingRule[1]", + "entityName" : "MetricsHeadingRule", + "entityKind" : "LineNode", + "parentPath" : "OrangeOpsCv[0]/Body[3]/Main[1]/Metrics[4]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 233.032, + "computedY" : 171.287, + "placementX" : 233.032, + "placementY" : 171.287, + "placementWidth" : 62.067, + "placementHeight" : 1.693, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 62.067, + "contentHeight" : 1.693, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "OrangeOpsCv[0]/Body[3]/Main[1]/Metrics[4]/MetricTable[2]", + "entityName" : "MetricTable", + "entityKind" : "TableNode", + "parentPath" : "OrangeOpsCv[0]/Body[3]/Main[1]/Metrics[4]", + "childIndex" : 2, + "depth" : 5, + "layer" : 5, + "computedX" : 233.032, + "computedY" : 105.913, + "placementX" : 233.032, + "placementY" : 105.913, + "placementWidth" : 339.11, + "placementHeight" : 63.682, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 339.11, + "contentHeight" : 63.682, + "margin" : { + "top" : 1.693, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "OrangeOpsCv[0]/Body[3]/Main[1]/AfterMetricsRule[5]", + "entityName" : "AfterMetricsRule", + "entityKind" : "LineNode", + "parentPath" : "OrangeOpsCv[0]/Body[3]/Main[1]", + "childIndex" : 5, + "depth" : 4, + "layer" : 4, + "computedX" : 233.032, + "computedY" : 98.281, + "placementX" : 233.032, + "placementY" : 98.281, + "placementWidth" : 339.11, + "placementHeight" : 1.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 339.11, + "contentHeight" : 1.0, + "margin" : { + "top" : 6.632, + "right" : 0.0, + "bottom" : 3.738, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "OrangeOpsCv[0]/Body[3]/Main[1]/Additional[6]", + "entityName" : "Additional", + "entityKind" : "SectionNode", + "parentPath" : "OrangeOpsCv[0]/Body[3]/Main[1]", + "childIndex" : 6, + "depth" : 4, + "layer" : 4, + "computedX" : 233.032, + "computedY" : 19.603, + "placementX" : 233.032, + "placementY" : 19.603, + "placementWidth" : 296.172, + "placementHeight" : 74.939, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 296.172, + "contentHeight" : 74.939, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "OrangeOpsCv[0]/Body[3]/Main[1]/Additional[6]/AdditionalHeading[0]", + "entityName" : "AdditionalHeading", + "entityKind" : "ParagraphNode", + "parentPath" : "OrangeOpsCv[0]/Body[3]/Main[1]/Additional[6]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 233.032, + "computedY" : 77.739, + "placementX" : 233.032, + "placementY" : 77.739, + "placementWidth" : 119.867, + "placementHeight" : 16.803, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 119.867, + "contentHeight" : 16.803, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 1.464, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "OrangeOpsCv[0]/Body[3]/Main[1]/Additional[6]/AdditionalHeadingRule[1]", + "entityName" : "AdditionalHeadingRule", + "entityKind" : "LineNode", + "parentPath" : "OrangeOpsCv[0]/Body[3]/Main[1]/Additional[6]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 233.032, + "computedY" : 74.582, + "placementX" : 233.032, + "placementY" : 74.582, + "placementWidth" : 62.067, + "placementHeight" : 1.693, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 62.067, + "contentHeight" : 1.693, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "OrangeOpsCv[0]/Body[3]/Main[1]/Additional[6]/Additional0[2]", + "entityName" : "Additional0", + "entityKind" : "ParagraphNode", + "parentPath" : "OrangeOpsCv[0]/Body[3]/Main[1]/Additional[6]", + "childIndex" : 2, + "depth" : 5, + "layer" : 5, + "computedX" : 233.032, + "computedY" : 60.229, + "placementX" : 233.032, + "placementY" : 60.229, + "placementWidth" : 296.172, + "placementHeight" : 9.84, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 296.172, + "contentHeight" : 9.84, + "margin" : { + "top" : 4.514, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "OrangeOpsCv[0]/Body[3]/Main[1]/Additional[6]/Additional1[3]", + "entityName" : "Additional1", + "entityKind" : "ParagraphNode", + "parentPath" : "OrangeOpsCv[0]/Body[3]/Main[1]/Additional[6]", + "childIndex" : 3, + "depth" : 5, + "layer" : 5, + "computedX" : 233.032, + "computedY" : 46.687, + "placementX" : 233.032, + "placementY" : 46.687, + "placementWidth" : 114.403, + "placementHeight" : 9.84, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 114.403, + "contentHeight" : 9.84, + "margin" : { + "top" : 3.702, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "OrangeOpsCv[0]/Body[3]/Main[1]/Additional[6]/Additional2[4]", + "entityName" : "Additional2", + "entityKind" : "ParagraphNode", + "parentPath" : "OrangeOpsCv[0]/Body[3]/Main[1]/Additional[6]", + "childIndex" : 4, + "depth" : 5, + "layer" : 5, + "computedX" : 233.032, + "computedY" : 33.145, + "placementX" : 233.032, + "placementY" : 33.145, + "placementWidth" : 160.733, + "placementHeight" : 9.84, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 160.733, + "contentHeight" : 9.84, + "margin" : { + "top" : 3.702, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "OrangeOpsCv[0]/Body[3]/Main[1]/Additional[6]/Additional3[5]", + "entityName" : "Additional3", + "entityKind" : "ParagraphNode", + "parentPath" : "OrangeOpsCv[0]/Body[3]/Main[1]/Additional[6]", + "childIndex" : 5, + "depth" : 5, + "layer" : 5, + "computedX" : 233.032, + "computedY" : 19.603, + "placementX" : 233.032, + "placementY" : 19.603, + "placementWidth" : 287.48, + "placementHeight" : 9.84, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 287.48, + "contentHeight" : 9.84, + "margin" : { + "top" : 3.702, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + } ] +} diff --git a/qa/src/test/resources/layout-snapshots/canonical-templates/cv-v2/professional_sidebar_layout.json b/qa/src/test/resources/layout-snapshots/canonical-templates/cv-v2/professional_sidebar_layout.json new file mode 100644 index 000000000..17e496e80 --- /dev/null +++ b/qa/src/test/resources/layout-snapshots/canonical-templates/cv-v2/professional_sidebar_layout.json @@ -0,0 +1,5297 @@ +{ + "formatVersion" : "2.0", + "canvas" : { + "pageWidth" : 491.6, + "pageHeight" : 737.28, + "innerWidth" : 491.6, + "innerHeight" : 737.28, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, + "totalPages" : 1, + "nodes" : [ { + "path" : "ProfessionalSidebarCv[0]", + "entityName" : "ProfessionalSidebarCv", + "entityKind" : "ContainerNode", + "parentPath" : null, + "childIndex" : 0, + "depth" : 1, + "layer" : 1, + "computedX" : 0.0, + "computedY" : 12.73, + "placementX" : 0.0, + "placementY" : 12.73, + "placementWidth" : 491.6, + "placementHeight" : 724.55, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 491.6, + "contentHeight" : 724.55, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]", + "entityName" : "PageGrid", + "entityKind" : "RowNode", + "parentPath" : "ProfessionalSidebarCv[0]", + "childIndex" : 0, + "depth" : 2, + "layer" : 2, + "computedX" : 0.0, + "computedY" : 12.73, + "placementX" : 0.0, + "placementY" : 12.73, + "placementWidth" : 491.6, + "placementHeight" : 724.55, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 491.6, + "contentHeight" : 724.55, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]", + "entityName" : "Sidebar", + "entityKind" : "SectionNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]", + "childIndex" : 0, + "depth" : 3, + "layer" : 3, + "computedX" : 0.0, + "computedY" : 24.204, + "placementX" : 0.0, + "placementY" : 24.204, + "placementWidth" : 143.056, + "placementHeight" : 713.076, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 143.056, + "contentHeight" : 713.076, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/MonogramPlate[0]", + "entityName" : "MonogramPlate", + "entityKind" : "ShapeContainerNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 0.0, + "computedY" : 638.484, + "placementX" : 0.0, + "placementY" : 638.484, + "placementWidth" : 143.056, + "placementHeight" : 98.796, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 143.056, + "contentHeight" : 98.796, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/MonogramPlate[0]/MonogramRing[0]", + "entityName" : "MonogramRing", + "entityKind" : "ShapeContainerNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/MonogramPlate[0]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 43.928, + "computedY" : 660.282, + "placementX" : 43.928, + "placementY" : 660.282, + "placementWidth" : 55.2, + "placementHeight" : 55.2, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 55.2, + "contentHeight" : 55.2, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/MonogramPlate[0]/MonogramRing[0]/MonogramInitials[0]", + "entityName" : "MonogramInitials", + "entityKind" : "ParagraphNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/MonogramPlate[0]/MonogramRing[0]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 43.928, + "computedY" : 678.282, + "placementX" : 43.928, + "placementY" : 678.282, + "placementWidth" : 55.2, + "placementHeight" : 19.2, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 55.2, + "contentHeight" : 19.2, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]", + "entityName" : "SidebarBody", + "entityKind" : "SectionNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 0.0, + "computedY" : 24.204, + "placementX" : 0.0, + "placementY" : 24.204, + "placementWidth" : 143.056, + "placementHeight" : 614.28, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 143.056, + "contentHeight" : 614.28, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 22.0, + "right" : 17.8, + "bottom" : 0.0, + "left" : 17.8 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Contact[0]", + "entityName" : "Contact", + "entityKind" : "SectionNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 17.8, + "computedY" : 491.434, + "placementX" : 17.8, + "placementY" : 491.434, + "placementWidth" : 98.484, + "placementHeight" : 125.05, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 98.484, + "contentHeight" : 125.05, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Contact[0]/SidebarHeading_CONTACT[0]", + "entityName" : "SidebarHeading_CONTACT", + "entityKind" : "ParagraphNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Contact[0]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 17.8, + "computedY" : 606.284, + "placementX" : 17.8, + "placementY" : 606.284, + "placementWidth" : 47.723, + "placementHeight" : 10.2, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 47.723, + "contentHeight" : 10.2, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 4.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Contact[0]/SidebarHeadingRule_CONTACT[1]", + "entityName" : "SidebarHeadingRule_CONTACT", + "entityKind" : "LineNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Contact[0]", + "childIndex" : 1, + "depth" : 6, + "layer" : 6, + "computedX" : 17.8, + "computedY" : 601.234, + "placementX" : 17.8, + "placementY" : 601.234, + "placementWidth" : 19.0, + "placementHeight" : 1.05, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 19.0, + "contentHeight" : 1.05, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 16.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Contact[0]/Contact_phone[2]", + "entityName" : "Contact_phone", + "entityKind" : "ParagraphNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Contact[0]", + "childIndex" : 2, + "depth" : 6, + "layer" : 6, + "computedX" : 17.8, + "computedY" : 576.434, + "placementX" : 17.8, + "placementY" : 576.434, + "placementWidth" : 70.939, + "placementHeight" : 8.8, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 70.939, + "contentHeight" : 8.8, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 11.8, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Contact[0]/Contact_email[3]", + "entityName" : "Contact_email", + "entityKind" : "ParagraphNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Contact[0]", + "childIndex" : 3, + "depth" : 6, + "layer" : 6, + "computedX" : 17.8, + "computedY" : 555.434, + "placementX" : 17.8, + "placementY" : 555.434, + "placementWidth" : 95.827, + "placementHeight" : 9.2, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 95.827, + "contentHeight" : 9.2, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 11.8, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Contact[0]/Contact_location[4]", + "entityName" : "Contact_location", + "entityKind" : "ParagraphNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Contact[0]", + "childIndex" : 4, + "depth" : 6, + "layer" : 6, + "computedX" : 17.8, + "computedY" : 534.034, + "placementX" : 17.8, + "placementY" : 534.034, + "placementWidth" : 78.108, + "placementHeight" : 9.6, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 78.108, + "contentHeight" : 9.6, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 11.8, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Contact[0]/Contact_linkedin[5]", + "entityName" : "Contact_linkedin", + "entityKind" : "ParagraphNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Contact[0]", + "childIndex" : 5, + "depth" : 6, + "layer" : 6, + "computedX" : 17.8, + "computedY" : 512.834, + "placementX" : 17.8, + "placementY" : 512.834, + "placementWidth" : 98.484, + "placementHeight" : 9.4, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 98.484, + "contentHeight" : 9.4, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 11.8, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Contact[0]/Contact_website[6]", + "entityName" : "Contact_website", + "entityKind" : "ParagraphNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Contact[0]", + "childIndex" : 6, + "depth" : 6, + "layer" : 6, + "computedX" : 17.8, + "computedY" : 491.434, + "placementX" : 17.8, + "placementY" : 491.434, + "placementWidth" : 88.843, + "placementHeight" : 9.6, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 88.843, + "contentHeight" : 9.6, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/SidebarDivider[1]", + "entityName" : "SidebarDivider", + "entityKind" : "LineNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 17.8, + "computedY" : 466.434, + "placementX" : 17.8, + "placementY" : 466.434, + "placementWidth" : 107.456, + "placementHeight" : 1.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 107.456, + "contentHeight" : 1.0, + "margin" : { + "top" : 24.0, + "right" : 0.0, + "bottom" : 18.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Skills[2]", + "entityName" : "Skills", + "entityKind" : "SectionNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]", + "childIndex" : 2, + "depth" : 5, + "layer" : 5, + "computedX" : 17.8, + "computedY" : 250.984, + "placementX" : 17.8, + "placementY" : 250.984, + "placementWidth" : 107.456, + "placementHeight" : 197.45, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 107.456, + "contentHeight" : 197.45, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Skills[2]/SidebarHeading_SKILLS[0]", + "entityName" : "SidebarHeading_SKILLS", + "entityKind" : "ParagraphNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Skills[2]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 17.8, + "computedY" : 438.234, + "placementX" : 17.8, + "placementY" : 438.234, + "placementWidth" : 32.312, + "placementHeight" : 10.2, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 32.312, + "contentHeight" : 10.2, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 4.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Skills[2]/SidebarHeadingRule_SKILLS[1]", + "entityName" : "SidebarHeadingRule_SKILLS", + "entityKind" : "LineNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Skills[2]", + "childIndex" : 1, + "depth" : 6, + "layer" : 6, + "computedX" : 17.8, + "computedY" : 433.184, + "placementX" : 17.8, + "placementY" : 433.184, + "placementWidth" : 19.0, + "placementHeight" : 1.05, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 19.0, + "contentHeight" : 1.05, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 13.5, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Skills[2]/SkillRows[2]", + "entityName" : "SkillRows", + "entityKind" : "SectionNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Skills[2]", + "childIndex" : 2, + "depth" : 6, + "layer" : 6, + "computedX" : 17.8, + "computedY" : 250.984, + "placementX" : 17.8, + "placementY" : 250.984, + "placementWidth" : 107.456, + "placementHeight" : 168.7, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 107.456, + "contentHeight" : 168.7, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Skills[2]/SkillRows[2]/SkillRow_Java[0]", + "entityName" : "SkillRow_Java", + "entityKind" : "ShapeContainerNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Skills[2]/SkillRows[2]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 17.8, + "computedY" : 411.184, + "placementX" : 17.8, + "placementY" : 411.184, + "placementWidth" : 107.456, + "placementHeight" : 8.5, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 107.456, + "contentHeight" : 8.5, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Skills[2]/SkillRows[2]/SkillRow_Java[0]/Skill_Java[0]", + "entityName" : "Skill_Java", + "entityKind" : "ParagraphNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Skills[2]/SkillRows[2]/SkillRow_Java[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 17.8, + "computedY" : 411.164, + "placementX" : 17.8, + "placementY" : 411.164, + "placementWidth" : 13.724, + "placementHeight" : 8.52, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 13.724, + "contentHeight" : 8.52, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Skills[2]/SkillRows[2]/SkillRow_Java[0]/SkillMeter[1]", + "entityName" : "SkillMeter", + "entityKind" : "ShapeContainerNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Skills[2]/SkillRows[2]/SkillRow_Java[0]", + "childIndex" : 1, + "depth" : 8, + "layer" : 8, + "computedX" : 77.975, + "computedY" : 414.034, + "placementX" : 77.975, + "placementY" : 414.034, + "placementWidth" : 47.28, + "placementHeight" : 2.8, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 47.28, + "contentHeight" : 2.8, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Skills[2]/SkillRows[2]/SkillRow_Java[0]/SkillMeter[1]/SkillMeterFill[0]", + "entityName" : "SkillMeterFill", + "entityKind" : "ShapeNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Skills[2]/SkillRows[2]/SkillRow_Java[0]/SkillMeter[1]", + "childIndex" : 0, + "depth" : 9, + "layer" : 9, + "computedX" : 77.975, + "computedY" : 414.034, + "placementX" : 77.975, + "placementY" : 414.034, + "placementWidth" : 34.042, + "placementHeight" : 2.8, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 34.042, + "contentHeight" : 2.8, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Skills[2]/SkillRows[2]/SkillRow_SpringBoot[1]", + "entityName" : "SkillRow_SpringBoot", + "entityKind" : "ShapeContainerNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Skills[2]/SkillRows[2]", + "childIndex" : 1, + "depth" : 7, + "layer" : 7, + "computedX" : 17.8, + "computedY" : 393.384, + "placementX" : 17.8, + "placementY" : 393.384, + "placementWidth" : 107.456, + "placementHeight" : 8.5, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 107.456, + "contentHeight" : 8.5, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Skills[2]/SkillRows[2]/SkillRow_SpringBoot[1]/Skill_SpringBoot[0]", + "entityName" : "Skill_SpringBoot", + "entityKind" : "ParagraphNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Skills[2]/SkillRows[2]/SkillRow_SpringBoot[1]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 17.8, + "computedY" : 393.364, + "placementX" : 17.8, + "placementY" : 393.364, + "placementWidth" : 36.792, + "placementHeight" : 8.52, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 36.792, + "contentHeight" : 8.52, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Skills[2]/SkillRows[2]/SkillRow_SpringBoot[1]/SkillMeter[1]", + "entityName" : "SkillMeter", + "entityKind" : "ShapeContainerNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Skills[2]/SkillRows[2]/SkillRow_SpringBoot[1]", + "childIndex" : 1, + "depth" : 8, + "layer" : 8, + "computedX" : 77.975, + "computedY" : 396.234, + "placementX" : 77.975, + "placementY" : 396.234, + "placementWidth" : 47.28, + "placementHeight" : 2.8, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 47.28, + "contentHeight" : 2.8, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Skills[2]/SkillRows[2]/SkillRow_SpringBoot[1]/SkillMeter[1]/SkillMeterFill[0]", + "entityName" : "SkillMeterFill", + "entityKind" : "ShapeNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Skills[2]/SkillRows[2]/SkillRow_SpringBoot[1]/SkillMeter[1]", + "childIndex" : 0, + "depth" : 9, + "layer" : 9, + "computedX" : 77.975, + "computedY" : 396.234, + "placementX" : 77.975, + "placementY" : 396.234, + "placementWidth" : 33.096, + "placementHeight" : 2.8, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 33.096, + "contentHeight" : 2.8, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Skills[2]/SkillRows[2]/SkillRow_SQLDatabases[2]", + "entityName" : "SkillRow_SQLDatabases", + "entityKind" : "ShapeContainerNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Skills[2]/SkillRows[2]", + "childIndex" : 2, + "depth" : 7, + "layer" : 7, + "computedX" : 17.8, + "computedY" : 375.584, + "placementX" : 17.8, + "placementY" : 375.584, + "placementWidth" : 107.456, + "placementHeight" : 8.5, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 107.456, + "contentHeight" : 8.5, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Skills[2]/SkillRows[2]/SkillRow_SQLDatabases[2]/Skill_SQLDatabases[0]", + "entityName" : "Skill_SQLDatabases", + "entityKind" : "ParagraphNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Skills[2]/SkillRows[2]/SkillRow_SQLDatabases[2]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 17.8, + "computedY" : 375.564, + "placementX" : 17.8, + "placementY" : 375.564, + "placementWidth" : 52.448, + "placementHeight" : 8.52, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 52.448, + "contentHeight" : 8.52, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Skills[2]/SkillRows[2]/SkillRow_SQLDatabases[2]/SkillMeter[1]", + "entityName" : "SkillMeter", + "entityKind" : "ShapeContainerNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Skills[2]/SkillRows[2]/SkillRow_SQLDatabases[2]", + "childIndex" : 1, + "depth" : 8, + "layer" : 8, + "computedX" : 77.975, + "computedY" : 378.434, + "placementX" : 77.975, + "placementY" : 378.434, + "placementWidth" : 47.28, + "placementHeight" : 2.8, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 47.28, + "contentHeight" : 2.8, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Skills[2]/SkillRows[2]/SkillRow_SQLDatabases[2]/SkillMeter[1]/SkillMeterFill[0]", + "entityName" : "SkillMeterFill", + "entityKind" : "ShapeNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Skills[2]/SkillRows[2]/SkillRow_SQLDatabases[2]/SkillMeter[1]", + "childIndex" : 0, + "depth" : 9, + "layer" : 9, + "computedX" : 77.975, + "computedY" : 378.434, + "placementX" : 77.975, + "placementY" : 378.434, + "placementWidth" : 33.096, + "placementHeight" : 2.8, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 33.096, + "contentHeight" : 2.8, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Skills[2]/SkillRows[2]/SkillRow_RESTfulAPIs[3]", + "entityName" : "SkillRow_RESTfulAPIs", + "entityKind" : "ShapeContainerNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Skills[2]/SkillRows[2]", + "childIndex" : 3, + "depth" : 7, + "layer" : 7, + "computedX" : 17.8, + "computedY" : 357.784, + "placementX" : 17.8, + "placementY" : 357.784, + "placementWidth" : 107.456, + "placementHeight" : 8.5, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 107.456, + "contentHeight" : 8.5, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Skills[2]/SkillRows[2]/SkillRow_RESTfulAPIs[3]/Skill_RESTfulAPIs[0]", + "entityName" : "Skill_RESTfulAPIs", + "entityKind" : "ParagraphNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Skills[2]/SkillRows[2]/SkillRow_RESTfulAPIs[3]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 17.8, + "computedY" : 357.764, + "placementX" : 17.8, + "placementY" : 357.764, + "placementWidth" : 40.69, + "placementHeight" : 8.52, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 40.69, + "contentHeight" : 8.52, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Skills[2]/SkillRows[2]/SkillRow_RESTfulAPIs[3]/SkillMeter[1]", + "entityName" : "SkillMeter", + "entityKind" : "ShapeContainerNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Skills[2]/SkillRows[2]/SkillRow_RESTfulAPIs[3]", + "childIndex" : 1, + "depth" : 8, + "layer" : 8, + "computedX" : 77.975, + "computedY" : 360.634, + "placementX" : 77.975, + "placementY" : 360.634, + "placementWidth" : 47.28, + "placementHeight" : 2.8, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 47.28, + "contentHeight" : 2.8, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Skills[2]/SkillRows[2]/SkillRow_RESTfulAPIs[3]/SkillMeter[1]/SkillMeterFill[0]", + "entityName" : "SkillMeterFill", + "entityKind" : "ShapeNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Skills[2]/SkillRows[2]/SkillRow_RESTfulAPIs[3]/SkillMeter[1]", + "childIndex" : 0, + "depth" : 9, + "layer" : 9, + "computedX" : 77.975, + "computedY" : 360.634, + "placementX" : 77.975, + "placementY" : 360.634, + "placementWidth" : 28.368, + "placementHeight" : 2.8, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 28.368, + "contentHeight" : 2.8, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Skills[2]/SkillRows[2]/SkillRow_Microservices[4]", + "entityName" : "SkillRow_Microservices", + "entityKind" : "ShapeContainerNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Skills[2]/SkillRows[2]", + "childIndex" : 4, + "depth" : 7, + "layer" : 7, + "computedX" : 17.8, + "computedY" : 339.984, + "placementX" : 17.8, + "placementY" : 339.984, + "placementWidth" : 107.456, + "placementHeight" : 8.5, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 107.456, + "contentHeight" : 8.5, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Skills[2]/SkillRows[2]/SkillRow_Microservices[4]/Skill_Microservices[0]", + "entityName" : "Skill_Microservices", + "entityKind" : "ParagraphNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Skills[2]/SkillRows[2]/SkillRow_Microservices[4]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 17.8, + "computedY" : 339.964, + "placementX" : 17.8, + "placementY" : 339.964, + "placementWidth" : 43.296, + "placementHeight" : 8.52, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 43.296, + "contentHeight" : 8.52, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Skills[2]/SkillRows[2]/SkillRow_Microservices[4]/SkillMeter[1]", + "entityName" : "SkillMeter", + "entityKind" : "ShapeContainerNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Skills[2]/SkillRows[2]/SkillRow_Microservices[4]", + "childIndex" : 1, + "depth" : 8, + "layer" : 8, + "computedX" : 77.975, + "computedY" : 342.834, + "placementX" : 77.975, + "placementY" : 342.834, + "placementWidth" : 47.28, + "placementHeight" : 2.8, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 47.28, + "contentHeight" : 2.8, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Skills[2]/SkillRows[2]/SkillRow_Microservices[4]/SkillMeter[1]/SkillMeterFill[0]", + "entityName" : "SkillMeterFill", + "entityKind" : "ShapeNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Skills[2]/SkillRows[2]/SkillRow_Microservices[4]/SkillMeter[1]", + "childIndex" : 0, + "depth" : 9, + "layer" : 9, + "computedX" : 77.975, + "computedY" : 342.834, + "placementX" : 77.975, + "placementY" : 342.834, + "placementWidth" : 34.042, + "placementHeight" : 2.8, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 34.042, + "contentHeight" : 2.8, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Skills[2]/SkillRows[2]/SkillRow_GitGitHub[5]", + "entityName" : "SkillRow_GitGitHub", + "entityKind" : "ShapeContainerNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Skills[2]/SkillRows[2]", + "childIndex" : 5, + "depth" : 7, + "layer" : 7, + "computedX" : 17.8, + "computedY" : 322.184, + "placementX" : 17.8, + "placementY" : 322.184, + "placementWidth" : 107.456, + "placementHeight" : 8.5, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 107.456, + "contentHeight" : 8.5, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Skills[2]/SkillRows[2]/SkillRow_GitGitHub[5]/Skill_GitGitHub[0]", + "entityName" : "Skill_GitGitHub", + "entityKind" : "ParagraphNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Skills[2]/SkillRows[2]/SkillRow_GitGitHub[5]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 17.8, + "computedY" : 322.164, + "placementX" : 17.8, + "placementY" : 322.164, + "placementWidth" : 39.093, + "placementHeight" : 8.52, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 39.093, + "contentHeight" : 8.52, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Skills[2]/SkillRows[2]/SkillRow_GitGitHub[5]/SkillMeter[1]", + "entityName" : "SkillMeter", + "entityKind" : "ShapeContainerNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Skills[2]/SkillRows[2]/SkillRow_GitGitHub[5]", + "childIndex" : 1, + "depth" : 8, + "layer" : 8, + "computedX" : 77.975, + "computedY" : 325.034, + "placementX" : 77.975, + "placementY" : 325.034, + "placementWidth" : 47.28, + "placementHeight" : 2.8, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 47.28, + "contentHeight" : 2.8, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Skills[2]/SkillRows[2]/SkillRow_GitGitHub[5]/SkillMeter[1]/SkillMeterFill[0]", + "entityName" : "SkillMeterFill", + "entityKind" : "ShapeNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Skills[2]/SkillRows[2]/SkillRow_GitGitHub[5]/SkillMeter[1]", + "childIndex" : 0, + "depth" : 9, + "layer" : 9, + "computedX" : 77.975, + "computedY" : 325.034, + "placementX" : 77.975, + "placementY" : 325.034, + "placementWidth" : 34.042, + "placementHeight" : 2.8, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 34.042, + "contentHeight" : 2.8, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Skills[2]/SkillRows[2]/SkillRow_Docker[6]", + "entityName" : "SkillRow_Docker", + "entityKind" : "ShapeContainerNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Skills[2]/SkillRows[2]", + "childIndex" : 6, + "depth" : 7, + "layer" : 7, + "computedX" : 17.8, + "computedY" : 304.384, + "placementX" : 17.8, + "placementY" : 304.384, + "placementWidth" : 107.456, + "placementHeight" : 8.5, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 107.456, + "contentHeight" : 8.5, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Skills[2]/SkillRows[2]/SkillRow_Docker[6]/Skill_Docker[0]", + "entityName" : "Skill_Docker", + "entityKind" : "ParagraphNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Skills[2]/SkillRows[2]/SkillRow_Docker[6]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 17.8, + "computedY" : 304.364, + "placementX" : 17.8, + "placementY" : 304.364, + "placementWidth" : 22.763, + "placementHeight" : 8.52, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 22.763, + "contentHeight" : 8.52, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Skills[2]/SkillRows[2]/SkillRow_Docker[6]/SkillMeter[1]", + "entityName" : "SkillMeter", + "entityKind" : "ShapeContainerNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Skills[2]/SkillRows[2]/SkillRow_Docker[6]", + "childIndex" : 1, + "depth" : 8, + "layer" : 8, + "computedX" : 77.975, + "computedY" : 307.234, + "placementX" : 77.975, + "placementY" : 307.234, + "placementWidth" : 47.28, + "placementHeight" : 2.8, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 47.28, + "contentHeight" : 2.8, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Skills[2]/SkillRows[2]/SkillRow_Docker[6]/SkillMeter[1]/SkillMeterFill[0]", + "entityName" : "SkillMeterFill", + "entityKind" : "ShapeNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Skills[2]/SkillRows[2]/SkillRow_Docker[6]/SkillMeter[1]", + "childIndex" : 0, + "depth" : 9, + "layer" : 9, + "computedX" : 77.975, + "computedY" : 307.234, + "placementX" : 77.975, + "placementY" : 307.234, + "placementWidth" : 26.477, + "placementHeight" : 2.8, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 26.477, + "contentHeight" : 2.8, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Skills[2]/SkillRows[2]/SkillRow_TestingJUnit[7]", + "entityName" : "SkillRow_TestingJUnit", + "entityKind" : "ShapeContainerNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Skills[2]/SkillRows[2]", + "childIndex" : 7, + "depth" : 7, + "layer" : 7, + "computedX" : 17.8, + "computedY" : 286.584, + "placementX" : 17.8, + "placementY" : 286.584, + "placementWidth" : 107.456, + "placementHeight" : 8.5, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 107.456, + "contentHeight" : 8.5, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Skills[2]/SkillRows[2]/SkillRow_TestingJUnit[7]/Skill_TestingJUnit[0]", + "entityName" : "Skill_TestingJUnit", + "entityKind" : "ParagraphNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Skills[2]/SkillRows[2]/SkillRow_TestingJUnit[7]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 17.8, + "computedY" : 286.564, + "placementX" : 17.8, + "placementY" : 286.564, + "placementWidth" : 44.979, + "placementHeight" : 8.52, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 44.979, + "contentHeight" : 8.52, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Skills[2]/SkillRows[2]/SkillRow_TestingJUnit[7]/SkillMeter[1]", + "entityName" : "SkillMeter", + "entityKind" : "ShapeContainerNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Skills[2]/SkillRows[2]/SkillRow_TestingJUnit[7]", + "childIndex" : 1, + "depth" : 8, + "layer" : 8, + "computedX" : 77.975, + "computedY" : 289.434, + "placementX" : 77.975, + "placementY" : 289.434, + "placementWidth" : 47.28, + "placementHeight" : 2.8, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 47.28, + "contentHeight" : 2.8, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Skills[2]/SkillRows[2]/SkillRow_TestingJUnit[7]/SkillMeter[1]/SkillMeterFill[0]", + "entityName" : "SkillMeterFill", + "entityKind" : "ShapeNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Skills[2]/SkillRows[2]/SkillRow_TestingJUnit[7]/SkillMeter[1]", + "childIndex" : 0, + "depth" : 9, + "layer" : 9, + "computedX" : 77.975, + "computedY" : 289.434, + "placementX" : 77.975, + "placementY" : 289.434, + "placementWidth" : 35.933, + "placementHeight" : 2.8, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 35.933, + "contentHeight" : 2.8, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Skills[2]/SkillRows[2]/SkillRow_ProblemSolving[8]", + "entityName" : "SkillRow_ProblemSolving", + "entityKind" : "ShapeContainerNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Skills[2]/SkillRows[2]", + "childIndex" : 8, + "depth" : 7, + "layer" : 7, + "computedX" : 17.8, + "computedY" : 268.784, + "placementX" : 17.8, + "placementY" : 268.784, + "placementWidth" : 107.456, + "placementHeight" : 8.5, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 107.456, + "contentHeight" : 8.5, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Skills[2]/SkillRows[2]/SkillRow_ProblemSolving[8]/Skill_ProblemSolving[0]", + "entityName" : "Skill_ProblemSolving", + "entityKind" : "ParagraphNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Skills[2]/SkillRows[2]/SkillRow_ProblemSolving[8]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 17.8, + "computedY" : 268.764, + "placementX" : 17.8, + "placementY" : 268.764, + "placementWidth" : 50.517, + "placementHeight" : 8.52, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 50.517, + "contentHeight" : 8.52, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Skills[2]/SkillRows[2]/SkillRow_ProblemSolving[8]/SkillMeter[1]", + "entityName" : "SkillMeter", + "entityKind" : "ShapeContainerNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Skills[2]/SkillRows[2]/SkillRow_ProblemSolving[8]", + "childIndex" : 1, + "depth" : 8, + "layer" : 8, + "computedX" : 77.975, + "computedY" : 271.634, + "placementX" : 77.975, + "placementY" : 271.634, + "placementWidth" : 47.28, + "placementHeight" : 2.8, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 47.28, + "contentHeight" : 2.8, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Skills[2]/SkillRows[2]/SkillRow_ProblemSolving[8]/SkillMeter[1]/SkillMeterFill[0]", + "entityName" : "SkillMeterFill", + "entityKind" : "ShapeNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Skills[2]/SkillRows[2]/SkillRow_ProblemSolving[8]/SkillMeter[1]", + "childIndex" : 0, + "depth" : 9, + "layer" : 9, + "computedX" : 77.975, + "computedY" : 271.634, + "placementX" : 77.975, + "placementY" : 271.634, + "placementWidth" : 34.042, + "placementHeight" : 2.8, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 34.042, + "contentHeight" : 2.8, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Skills[2]/SkillRows[2]/SkillRow_Communication[9]", + "entityName" : "SkillRow_Communication", + "entityKind" : "ShapeContainerNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Skills[2]/SkillRows[2]", + "childIndex" : 9, + "depth" : 7, + "layer" : 7, + "computedX" : 17.8, + "computedY" : 250.984, + "placementX" : 17.8, + "placementY" : 250.984, + "placementWidth" : 107.456, + "placementHeight" : 8.5, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 107.456, + "contentHeight" : 8.5, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Skills[2]/SkillRows[2]/SkillRow_Communication[9]/Skill_Communication[0]", + "entityName" : "Skill_Communication", + "entityKind" : "ParagraphNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Skills[2]/SkillRows[2]/SkillRow_Communication[9]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 17.8, + "computedY" : 250.964, + "placementX" : 17.8, + "placementY" : 250.964, + "placementWidth" : 49.246, + "placementHeight" : 8.52, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 49.246, + "contentHeight" : 8.52, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Skills[2]/SkillRows[2]/SkillRow_Communication[9]/SkillMeter[1]", + "entityName" : "SkillMeter", + "entityKind" : "ShapeContainerNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Skills[2]/SkillRows[2]/SkillRow_Communication[9]", + "childIndex" : 1, + "depth" : 8, + "layer" : 8, + "computedX" : 77.975, + "computedY" : 253.834, + "placementX" : 77.975, + "placementY" : 253.834, + "placementWidth" : 47.28, + "placementHeight" : 2.8, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 47.28, + "contentHeight" : 2.8, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Skills[2]/SkillRows[2]/SkillRow_Communication[9]/SkillMeter[1]/SkillMeterFill[0]", + "entityName" : "SkillMeterFill", + "entityKind" : "ShapeNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Skills[2]/SkillRows[2]/SkillRow_Communication[9]/SkillMeter[1]", + "childIndex" : 0, + "depth" : 9, + "layer" : 9, + "computedX" : 77.975, + "computedY" : 253.834, + "placementX" : 77.975, + "placementY" : 253.834, + "placementWidth" : 33.096, + "placementHeight" : 2.8, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 33.096, + "contentHeight" : 2.8, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/SidebarDivider[3]", + "entityName" : "SidebarDivider", + "entityKind" : "LineNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]", + "childIndex" : 3, + "depth" : 5, + "layer" : 5, + "computedX" : 17.8, + "computedY" : 234.984, + "placementX" : 17.8, + "placementY" : 234.984, + "placementWidth" : 107.456, + "placementHeight" : 1.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 107.456, + "contentHeight" : 1.0, + "margin" : { + "top" : 15.0, + "right" : 0.0, + "bottom" : 18.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Education[4]", + "entityName" : "Education", + "entityKind" : "SectionNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]", + "childIndex" : 4, + "depth" : 5, + "layer" : 5, + "computedX" : 17.8, + "computedY" : 118.054, + "placementX" : 17.8, + "placementY" : 118.054, + "placementWidth" : 107.456, + "placementHeight" : 98.93, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 107.456, + "contentHeight" : 98.93, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Education[4]/SidebarHeading_EDUCATION[0]", + "entityName" : "SidebarHeading_EDUCATION", + "entityKind" : "ParagraphNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Education[4]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 17.8, + "computedY" : 206.784, + "placementX" : 17.8, + "placementY" : 206.784, + "placementWidth" : 59.505, + "placementHeight" : 10.2, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 59.505, + "contentHeight" : 10.2, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 4.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Education[4]/SidebarHeadingRule_EDUCATION[1]", + "entityName" : "SidebarHeadingRule_EDUCATION", + "entityKind" : "LineNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Education[4]", + "childIndex" : 1, + "depth" : 6, + "layer" : 6, + "computedX" : 17.8, + "computedY" : 201.734, + "placementX" : 17.8, + "placementY" : 201.734, + "placementWidth" : 19.0, + "placementHeight" : 1.05, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 19.0, + "contentHeight" : 1.05, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 10.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Education[4]/EducationRail[2]", + "entityName" : "EducationRail", + "entityKind" : "LayerStackNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Education[4]", + "childIndex" : 2, + "depth" : 6, + "layer" : 6, + "computedX" : 17.8, + "computedY" : 118.054, + "placementX" : 17.8, + "placementY" : 118.054, + "placementWidth" : 107.456, + "placementHeight" : 73.68, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 107.456, + "contentHeight" : 73.68, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Education[4]/EducationRail[2]/EducationRailHolder[0]", + "entityName" : "EducationRailHolder", + "entityKind" : "SectionNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Education[4]/EducationRail[2]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 18.45, + "computedY" : 118.054, + "placementX" : 18.45, + "placementY" : 118.054, + "placementWidth" : 106.806, + "placementHeight" : 73.68, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 106.806, + "contentHeight" : 73.68, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.65 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Education[4]/EducationRail[2]/EducationRailHolder[0]/SectionNode[0]", + "entityName" : null, + "entityKind" : "SectionNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Education[4]/EducationRail[2]/EducationRailHolder[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 18.45, + "computedY" : 118.054, + "placementX" : 18.45, + "placementY" : 118.054, + "placementWidth" : 106.806, + "placementHeight" : 73.68, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 106.806, + "contentHeight" : 73.68, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Education[4]/EducationRail[2]/EducationRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]", + "entityName" : null, + "entityKind" : "LayoutAnchorNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Education[4]/EducationRail[2]/EducationRailHolder[0]/SectionNode[0]", + "childIndex" : 0, + "depth" : 9, + "layer" : 9, + "computedX" : 18.45, + "computedY" : 149.394, + "placementX" : 18.45, + "placementY" : 149.394, + "placementWidth" : 106.806, + "placementHeight" : 42.34, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 106.806, + "contentHeight" : 42.34, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Education[4]/EducationRail[2]/EducationRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]", + "entityName" : null, + "entityKind" : "SectionNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Education[4]/EducationRail[2]/EducationRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]", + "childIndex" : 0, + "depth" : 10, + "layer" : 10, + "computedX" : 18.45, + "computedY" : 149.394, + "placementX" : 18.45, + "placementY" : 149.394, + "placementWidth" : 106.806, + "placementHeight" : 42.34, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 106.806, + "contentHeight" : 42.34, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 11.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Education[4]/EducationRail[2]/EducationRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]", + "entityName" : null, + "entityKind" : "HorizontalBandsNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Education[4]/EducationRail[2]/EducationRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]", + "childIndex" : 0, + "depth" : 11, + "layer" : 11, + "computedX" : 18.45, + "computedY" : 160.394, + "placementX" : 18.45, + "placementY" : 160.394, + "placementWidth" : 106.806, + "placementHeight" : 31.34, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 106.806, + "contentHeight" : 31.34, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Education[4]/EducationRail[2]/EducationRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]", + "entityName" : null, + "entityKind" : "RowNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Education[4]/EducationRail[2]/EducationRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]", + "childIndex" : 0, + "depth" : 12, + "layer" : 12, + "computedX" : 18.45, + "computedY" : 160.394, + "placementX" : 18.45, + "placementY" : 160.394, + "placementWidth" : 106.806, + "placementHeight" : 31.34, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 106.806, + "contentHeight" : 31.34, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Education[4]/EducationRail[2]/EducationRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]", + "entityName" : null, + "entityKind" : "SectionNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Education[4]/EducationRail[2]/EducationRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]", + "childIndex" : 0, + "depth" : 13, + "layer" : 13, + "computedX" : 18.45, + "computedY" : 182.434, + "placementX" : 18.45, + "placementY" : 182.434, + "placementWidth" : 4.3, + "placementHeight" : 9.3, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 4.3, + "contentHeight" : 9.3, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Education[4]/EducationRail[2]/EducationRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]", + "entityName" : null, + "entityKind" : "Align", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Education[4]/EducationRail[2]/EducationRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]", + "childIndex" : 0, + "depth" : 14, + "layer" : 14, + "computedX" : 18.45, + "computedY" : 182.434, + "placementX" : 18.45, + "placementY" : 182.434, + "placementWidth" : 4.3, + "placementHeight" : 9.3, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 4.3, + "contentHeight" : 9.3, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Education[4]/EducationRail[2]/EducationRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]/LayoutAnchorNode[0]", + "entityName" : null, + "entityKind" : "LayoutAnchorNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Education[4]/EducationRail[2]/EducationRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]", + "childIndex" : 0, + "depth" : 15, + "layer" : 15, + "computedX" : 18.45, + "computedY" : 182.434, + "placementX" : 18.45, + "placementY" : 182.434, + "placementWidth" : 4.3, + "placementHeight" : 9.3, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 4.3, + "contentHeight" : 9.3, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Education[4]/EducationRail[2]/EducationRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]/LayoutAnchorNode[0]/marker[0]", + "entityName" : "marker", + "entityKind" : "CanvasLayerNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Education[4]/EducationRail[2]/EducationRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]/LayoutAnchorNode[0]", + "childIndex" : 0, + "depth" : 16, + "layer" : 16, + "computedX" : 18.45, + "computedY" : 182.434, + "placementX" : 18.45, + "placementY" : 182.434, + "placementWidth" : 4.3, + "placementHeight" : 9.3, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 4.3, + "contentHeight" : 9.3, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Education[4]/EducationRail[2]/EducationRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]/LayoutAnchorNode[0]/marker[0]/SectionNode[0]", + "entityName" : null, + "entityKind" : "SectionNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Education[4]/EducationRail[2]/EducationRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]/LayoutAnchorNode[0]/marker[0]", + "childIndex" : 0, + "depth" : 17, + "layer" : 17, + "computedX" : 18.45, + "computedY" : 184.934, + "placementX" : 18.45, + "placementY" : 184.934, + "placementWidth" : 4.3, + "placementHeight" : 6.8, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 4.3, + "contentHeight" : 6.8, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Education[4]/EducationRail[2]/EducationRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]/LayoutAnchorNode[0]/marker[0]/SectionNode[0]/EducationMarker_0[0]", + "entityName" : "EducationMarker_0", + "entityKind" : "EllipseNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Education[4]/EducationRail[2]/EducationRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]/LayoutAnchorNode[0]/marker[0]/SectionNode[0]", + "childIndex" : 0, + "depth" : 18, + "layer" : 18, + "computedX" : 18.45, + "computedY" : 184.934, + "placementX" : 18.45, + "placementY" : 184.934, + "placementWidth" : 4.3, + "placementHeight" : 4.3, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 4.3, + "contentHeight" : 4.3, + "margin" : { + "top" : 2.5, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Education[4]/EducationRail[2]/EducationRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]", + "entityName" : null, + "entityKind" : "SectionNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Education[4]/EducationRail[2]/EducationRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]", + "childIndex" : 1, + "depth" : 13, + "layer" : 13, + "computedX" : 33.6, + "computedY" : 160.394, + "placementX" : 33.6, + "placementY" : 160.394, + "placementWidth" : 51.713, + "placementHeight" : 31.34, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 51.713, + "contentHeight" : 31.34, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Education[4]/EducationRail[2]/EducationRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]/EducationDegree_0[0]", + "entityName" : "EducationDegree_0", + "entityKind" : "ParagraphNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Education[4]/EducationRail[2]/EducationRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]", + "childIndex" : 0, + "depth" : 14, + "layer" : 14, + "computedX" : 33.6, + "computedY" : 182.704, + "placementX" : 33.6, + "placementY" : 182.704, + "placementWidth" : 51.713, + "placementHeight" : 8.76, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 51.713, + "contentHeight" : 8.76, + "margin" : { + "top" : 0.27, + "right" : 0.0, + "bottom" : 0.27, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Education[4]/EducationRail[2]/EducationRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]/EducationInstitution_0[1]", + "entityName" : "EducationInstitution_0", + "entityKind" : "ParagraphNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Education[4]/EducationRail[2]/EducationRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]", + "childIndex" : 1, + "depth" : 14, + "layer" : 14, + "computedX" : 33.6, + "computedY" : 171.414, + "placementX" : 33.6, + "placementY" : 171.414, + "placementWidth" : 49.21, + "placementHeight" : 8.52, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 49.21, + "contentHeight" : 8.52, + "margin" : { + "top" : 2.5, + "right" : 0.0, + "bottom" : 2.5, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Education[4]/EducationRail[2]/EducationRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]/EducationDates_0[2]", + "entityName" : "EducationDates_0", + "entityKind" : "ParagraphNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Education[4]/EducationRail[2]/EducationRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]", + "childIndex" : 2, + "depth" : 14, + "layer" : 14, + "computedX" : 33.6, + "computedY" : 160.394, + "placementX" : 33.6, + "placementY" : 160.394, + "placementWidth" : 40.697, + "placementHeight" : 8.52, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 40.697, + "contentHeight" : 8.52, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Education[4]/EducationRail[2]/EducationRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]", + "entityName" : null, + "entityKind" : "LayoutAnchorNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Education[4]/EducationRail[2]/EducationRailHolder[0]/SectionNode[0]", + "childIndex" : 1, + "depth" : 9, + "layer" : 9, + "computedX" : 18.45, + "computedY" : 118.054, + "placementX" : 18.45, + "placementY" : 118.054, + "placementWidth" : 106.806, + "placementHeight" : 31.34, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 106.806, + "contentHeight" : 31.34, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Education[4]/EducationRail[2]/EducationRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]", + "entityName" : null, + "entityKind" : "SectionNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Education[4]/EducationRail[2]/EducationRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]", + "childIndex" : 0, + "depth" : 10, + "layer" : 10, + "computedX" : 18.45, + "computedY" : 118.054, + "placementX" : 18.45, + "placementY" : 118.054, + "placementWidth" : 106.806, + "placementHeight" : 31.34, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 106.806, + "contentHeight" : 31.34, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Education[4]/EducationRail[2]/EducationRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]", + "entityName" : null, + "entityKind" : "HorizontalBandsNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Education[4]/EducationRail[2]/EducationRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]", + "childIndex" : 0, + "depth" : 11, + "layer" : 11, + "computedX" : 18.45, + "computedY" : 118.054, + "placementX" : 18.45, + "placementY" : 118.054, + "placementWidth" : 106.806, + "placementHeight" : 31.34, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 106.806, + "contentHeight" : 31.34, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Education[4]/EducationRail[2]/EducationRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]", + "entityName" : null, + "entityKind" : "RowNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Education[4]/EducationRail[2]/EducationRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]", + "childIndex" : 0, + "depth" : 12, + "layer" : 12, + "computedX" : 18.45, + "computedY" : 118.054, + "placementX" : 18.45, + "placementY" : 118.054, + "placementWidth" : 106.806, + "placementHeight" : 31.34, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 106.806, + "contentHeight" : 31.34, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Education[4]/EducationRail[2]/EducationRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]", + "entityName" : null, + "entityKind" : "SectionNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Education[4]/EducationRail[2]/EducationRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]", + "childIndex" : 0, + "depth" : 13, + "layer" : 13, + "computedX" : 18.45, + "computedY" : 140.094, + "placementX" : 18.45, + "placementY" : 140.094, + "placementWidth" : 4.3, + "placementHeight" : 9.3, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 4.3, + "contentHeight" : 9.3, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Education[4]/EducationRail[2]/EducationRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]", + "entityName" : null, + "entityKind" : "Align", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Education[4]/EducationRail[2]/EducationRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]", + "childIndex" : 0, + "depth" : 14, + "layer" : 14, + "computedX" : 18.45, + "computedY" : 140.094, + "placementX" : 18.45, + "placementY" : 140.094, + "placementWidth" : 4.3, + "placementHeight" : 9.3, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 4.3, + "contentHeight" : 9.3, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Education[4]/EducationRail[2]/EducationRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]/LayoutAnchorNode[0]", + "entityName" : null, + "entityKind" : "LayoutAnchorNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Education[4]/EducationRail[2]/EducationRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]", + "childIndex" : 0, + "depth" : 15, + "layer" : 15, + "computedX" : 18.45, + "computedY" : 140.094, + "placementX" : 18.45, + "placementY" : 140.094, + "placementWidth" : 4.3, + "placementHeight" : 9.3, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 4.3, + "contentHeight" : 9.3, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Education[4]/EducationRail[2]/EducationRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]/LayoutAnchorNode[0]/marker[0]", + "entityName" : "marker", + "entityKind" : "CanvasLayerNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Education[4]/EducationRail[2]/EducationRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]/LayoutAnchorNode[0]", + "childIndex" : 0, + "depth" : 16, + "layer" : 16, + "computedX" : 18.45, + "computedY" : 140.094, + "placementX" : 18.45, + "placementY" : 140.094, + "placementWidth" : 4.3, + "placementHeight" : 9.3, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 4.3, + "contentHeight" : 9.3, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Education[4]/EducationRail[2]/EducationRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]/LayoutAnchorNode[0]/marker[0]/SectionNode[0]", + "entityName" : null, + "entityKind" : "SectionNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Education[4]/EducationRail[2]/EducationRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]/LayoutAnchorNode[0]/marker[0]", + "childIndex" : 0, + "depth" : 17, + "layer" : 17, + "computedX" : 18.45, + "computedY" : 142.594, + "placementX" : 18.45, + "placementY" : 142.594, + "placementWidth" : 4.3, + "placementHeight" : 6.8, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 4.3, + "contentHeight" : 6.8, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Education[4]/EducationRail[2]/EducationRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]/LayoutAnchorNode[0]/marker[0]/SectionNode[0]/EducationMarker_1[0]", + "entityName" : "EducationMarker_1", + "entityKind" : "EllipseNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Education[4]/EducationRail[2]/EducationRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]/LayoutAnchorNode[0]/marker[0]/SectionNode[0]", + "childIndex" : 0, + "depth" : 18, + "layer" : 18, + "computedX" : 18.45, + "computedY" : 142.594, + "placementX" : 18.45, + "placementY" : 142.594, + "placementWidth" : 4.3, + "placementHeight" : 4.3, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 4.3, + "contentHeight" : 4.3, + "margin" : { + "top" : 2.5, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Education[4]/EducationRail[2]/EducationRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]", + "entityName" : null, + "entityKind" : "SectionNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Education[4]/EducationRail[2]/EducationRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]", + "childIndex" : 1, + "depth" : 13, + "layer" : 13, + "computedX" : 33.6, + "computedY" : 118.054, + "placementX" : 33.6, + "placementY" : 118.054, + "placementWidth" : 51.713, + "placementHeight" : 31.34, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 51.713, + "contentHeight" : 31.34, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Education[4]/EducationRail[2]/EducationRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]/EducationDegree_1[0]", + "entityName" : "EducationDegree_1", + "entityKind" : "ParagraphNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Education[4]/EducationRail[2]/EducationRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]", + "childIndex" : 0, + "depth" : 14, + "layer" : 14, + "computedX" : 33.6, + "computedY" : 140.364, + "placementX" : 33.6, + "placementY" : 140.364, + "placementWidth" : 51.713, + "placementHeight" : 8.76, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 51.713, + "contentHeight" : 8.76, + "margin" : { + "top" : 0.27, + "right" : 0.0, + "bottom" : 0.27, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Education[4]/EducationRail[2]/EducationRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]/EducationInstitution_1[1]", + "entityName" : "EducationInstitution_1", + "entityKind" : "ParagraphNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Education[4]/EducationRail[2]/EducationRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]", + "childIndex" : 1, + "depth" : 14, + "layer" : 14, + "computedX" : 33.6, + "computedY" : 129.074, + "placementX" : 33.6, + "placementY" : 129.074, + "placementWidth" : 49.21, + "placementHeight" : 8.52, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 49.21, + "contentHeight" : 8.52, + "margin" : { + "top" : 2.5, + "right" : 0.0, + "bottom" : 2.5, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Education[4]/EducationRail[2]/EducationRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]/EducationDates_1[2]", + "entityName" : "EducationDates_1", + "entityKind" : "ParagraphNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Education[4]/EducationRail[2]/EducationRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]", + "childIndex" : 2, + "depth" : 14, + "layer" : 14, + "computedX" : 33.6, + "computedY" : 118.054, + "placementX" : 33.6, + "placementY" : 118.054, + "placementWidth" : 40.697, + "placementHeight" : 8.52, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 40.697, + "contentHeight" : 8.52, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/SidebarDivider[5]", + "entityName" : "SidebarDivider", + "entityKind" : "LineNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]", + "childIndex" : 5, + "depth" : 5, + "layer" : 5, + "computedX" : 17.8, + "computedY" : 109.054, + "placementX" : 17.8, + "placementY" : 109.054, + "placementWidth" : 107.456, + "placementHeight" : 1.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 107.456, + "contentHeight" : 1.0, + "margin" : { + "top" : 8.0, + "right" : 0.0, + "bottom" : 16.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Languages[6]", + "entityName" : "Languages", + "entityKind" : "SectionNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]", + "childIndex" : 6, + "depth" : 5, + "layer" : 5, + "computedX" : 17.8, + "computedY" : 24.204, + "placementX" : 17.8, + "placementY" : 24.204, + "placementWidth" : 107.456, + "placementHeight" : 68.85, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 107.456, + "contentHeight" : 68.85, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Languages[6]/SidebarHeading_LANGUAGES[0]", + "entityName" : "SidebarHeading_LANGUAGES", + "entityKind" : "ParagraphNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Languages[6]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 17.8, + "computedY" : 82.854, + "placementX" : 17.8, + "placementY" : 82.854, + "placementWidth" : 60.253, + "placementHeight" : 10.2, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 60.253, + "contentHeight" : 10.2, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 4.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Languages[6]/SidebarHeadingRule_LANGUAGES[1]", + "entityName" : "SidebarHeadingRule_LANGUAGES", + "entityKind" : "LineNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Languages[6]", + "childIndex" : 1, + "depth" : 6, + "layer" : 6, + "computedX" : 17.8, + "computedY" : 77.804, + "placementX" : 17.8, + "placementY" : 77.804, + "placementWidth" : 19.0, + "placementHeight" : 1.05, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 19.0, + "contentHeight" : 1.05, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 13.5, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Languages[6]/LanguageRows[2]", + "entityName" : "LanguageRows", + "entityKind" : "SectionNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Languages[6]", + "childIndex" : 2, + "depth" : 6, + "layer" : 6, + "computedX" : 17.8, + "computedY" : 24.204, + "placementX" : 17.8, + "placementY" : 24.204, + "placementWidth" : 107.456, + "placementHeight" : 40.1, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 107.456, + "contentHeight" : 40.1, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Languages[6]/LanguageRows[2]/LanguageRow_English[0]", + "entityName" : "LanguageRow_English", + "entityKind" : "ShapeContainerNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Languages[6]/LanguageRows[2]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 17.8, + "computedY" : 55.804, + "placementX" : 17.8, + "placementY" : 55.804, + "placementWidth" : 107.456, + "placementHeight" : 8.5, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 107.456, + "contentHeight" : 8.5, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Languages[6]/LanguageRows[2]/LanguageRow_English[0]/Language_English[0]", + "entityName" : "Language_English", + "entityKind" : "ParagraphNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Languages[6]/LanguageRows[2]/LanguageRow_English[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 17.8, + "computedY" : 55.784, + "placementX" : 17.8, + "placementY" : 55.784, + "placementWidth" : 22.173, + "placementHeight" : 8.52, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 22.173, + "contentHeight" : 8.52, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Languages[6]/LanguageRows[2]/LanguageRow_English[0]/LanguageRating[1]", + "entityName" : "LanguageRating", + "entityKind" : "ShapeContainerNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Languages[6]/LanguageRows[2]/LanguageRow_English[0]", + "childIndex" : 1, + "depth" : 8, + "layer" : 8, + "computedX" : 70.453, + "computedY" : 57.554, + "placementX" : 70.453, + "placementY" : 57.554, + "placementWidth" : 45.4, + "placementHeight" : 5.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 45.4, + "contentHeight" : 5.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Languages[6]/LanguageRows[2]/LanguageRow_English[0]/LanguageRating[1]/LanguageDot_0[0]", + "entityName" : "LanguageDot_0", + "entityKind" : "EllipseNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Languages[6]/LanguageRows[2]/LanguageRow_English[0]/LanguageRating[1]", + "childIndex" : 0, + "depth" : 9, + "layer" : 9, + "computedX" : 70.453, + "computedY" : 57.554, + "placementX" : 70.453, + "placementY" : 57.554, + "placementWidth" : 5.0, + "placementHeight" : 5.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 5.0, + "contentHeight" : 5.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Languages[6]/LanguageRows[2]/LanguageRow_English[0]/LanguageRating[1]/LanguageDot_1[1]", + "entityName" : "LanguageDot_1", + "entityKind" : "EllipseNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Languages[6]/LanguageRows[2]/LanguageRow_English[0]/LanguageRating[1]", + "childIndex" : 1, + "depth" : 9, + "layer" : 9, + "computedX" : 80.553, + "computedY" : 57.554, + "placementX" : 80.553, + "placementY" : 57.554, + "placementWidth" : 5.0, + "placementHeight" : 5.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 5.0, + "contentHeight" : 5.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Languages[6]/LanguageRows[2]/LanguageRow_English[0]/LanguageRating[1]/LanguageDot_2[2]", + "entityName" : "LanguageDot_2", + "entityKind" : "EllipseNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Languages[6]/LanguageRows[2]/LanguageRow_English[0]/LanguageRating[1]", + "childIndex" : 2, + "depth" : 9, + "layer" : 9, + "computedX" : 90.653, + "computedY" : 57.554, + "placementX" : 90.653, + "placementY" : 57.554, + "placementWidth" : 5.0, + "placementHeight" : 5.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 5.0, + "contentHeight" : 5.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Languages[6]/LanguageRows[2]/LanguageRow_English[0]/LanguageRating[1]/LanguageDot_3[3]", + "entityName" : "LanguageDot_3", + "entityKind" : "EllipseNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Languages[6]/LanguageRows[2]/LanguageRow_English[0]/LanguageRating[1]", + "childIndex" : 3, + "depth" : 9, + "layer" : 9, + "computedX" : 100.753, + "computedY" : 57.554, + "placementX" : 100.753, + "placementY" : 57.554, + "placementWidth" : 5.0, + "placementHeight" : 5.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 5.0, + "contentHeight" : 5.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Languages[6]/LanguageRows[2]/LanguageRow_English[0]/LanguageRating[1]/LanguageDot_4[4]", + "entityName" : "LanguageDot_4", + "entityKind" : "EllipseNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Languages[6]/LanguageRows[2]/LanguageRow_English[0]/LanguageRating[1]", + "childIndex" : 4, + "depth" : 9, + "layer" : 9, + "computedX" : 110.853, + "computedY" : 57.554, + "placementX" : 110.853, + "placementY" : 57.554, + "placementWidth" : 5.0, + "placementHeight" : 5.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 5.0, + "contentHeight" : 5.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Languages[6]/LanguageRows[2]/LanguageRow_Ukrainian[1]", + "entityName" : "LanguageRow_Ukrainian", + "entityKind" : "ShapeContainerNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Languages[6]/LanguageRows[2]", + "childIndex" : 1, + "depth" : 7, + "layer" : 7, + "computedX" : 17.8, + "computedY" : 40.004, + "placementX" : 17.8, + "placementY" : 40.004, + "placementWidth" : 107.456, + "placementHeight" : 8.5, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 107.456, + "contentHeight" : 8.5, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Languages[6]/LanguageRows[2]/LanguageRow_Ukrainian[1]/Language_Ukrainian[0]", + "entityName" : "Language_Ukrainian", + "entityKind" : "ParagraphNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Languages[6]/LanguageRows[2]/LanguageRow_Ukrainian[1]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 17.8, + "computedY" : 39.984, + "placementX" : 17.8, + "placementY" : 39.984, + "placementWidth" : 29.806, + "placementHeight" : 8.52, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 29.806, + "contentHeight" : 8.52, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Languages[6]/LanguageRows[2]/LanguageRow_Ukrainian[1]/LanguageRating[1]", + "entityName" : "LanguageRating", + "entityKind" : "ShapeContainerNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Languages[6]/LanguageRows[2]/LanguageRow_Ukrainian[1]", + "childIndex" : 1, + "depth" : 8, + "layer" : 8, + "computedX" : 70.453, + "computedY" : 41.754, + "placementX" : 70.453, + "placementY" : 41.754, + "placementWidth" : 45.4, + "placementHeight" : 5.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 45.4, + "contentHeight" : 5.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Languages[6]/LanguageRows[2]/LanguageRow_Ukrainian[1]/LanguageRating[1]/LanguageDot_0[0]", + "entityName" : "LanguageDot_0", + "entityKind" : "EllipseNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Languages[6]/LanguageRows[2]/LanguageRow_Ukrainian[1]/LanguageRating[1]", + "childIndex" : 0, + "depth" : 9, + "layer" : 9, + "computedX" : 70.453, + "computedY" : 41.754, + "placementX" : 70.453, + "placementY" : 41.754, + "placementWidth" : 5.0, + "placementHeight" : 5.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 5.0, + "contentHeight" : 5.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Languages[6]/LanguageRows[2]/LanguageRow_Ukrainian[1]/LanguageRating[1]/LanguageDot_1[1]", + "entityName" : "LanguageDot_1", + "entityKind" : "EllipseNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Languages[6]/LanguageRows[2]/LanguageRow_Ukrainian[1]/LanguageRating[1]", + "childIndex" : 1, + "depth" : 9, + "layer" : 9, + "computedX" : 80.553, + "computedY" : 41.754, + "placementX" : 80.553, + "placementY" : 41.754, + "placementWidth" : 5.0, + "placementHeight" : 5.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 5.0, + "contentHeight" : 5.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Languages[6]/LanguageRows[2]/LanguageRow_Ukrainian[1]/LanguageRating[1]/LanguageDot_2[2]", + "entityName" : "LanguageDot_2", + "entityKind" : "EllipseNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Languages[6]/LanguageRows[2]/LanguageRow_Ukrainian[1]/LanguageRating[1]", + "childIndex" : 2, + "depth" : 9, + "layer" : 9, + "computedX" : 90.653, + "computedY" : 41.754, + "placementX" : 90.653, + "placementY" : 41.754, + "placementWidth" : 5.0, + "placementHeight" : 5.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 5.0, + "contentHeight" : 5.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Languages[6]/LanguageRows[2]/LanguageRow_Ukrainian[1]/LanguageRating[1]/LanguageDot_3[3]", + "entityName" : "LanguageDot_3", + "entityKind" : "EllipseNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Languages[6]/LanguageRows[2]/LanguageRow_Ukrainian[1]/LanguageRating[1]", + "childIndex" : 3, + "depth" : 9, + "layer" : 9, + "computedX" : 100.753, + "computedY" : 41.754, + "placementX" : 100.753, + "placementY" : 41.754, + "placementWidth" : 5.0, + "placementHeight" : 5.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 5.0, + "contentHeight" : 5.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Languages[6]/LanguageRows[2]/LanguageRow_Ukrainian[1]/LanguageRating[1]/LanguageDot_4[4]", + "entityName" : "LanguageDot_4", + "entityKind" : "EllipseNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Languages[6]/LanguageRows[2]/LanguageRow_Ukrainian[1]/LanguageRating[1]", + "childIndex" : 4, + "depth" : 9, + "layer" : 9, + "computedX" : 110.853, + "computedY" : 41.754, + "placementX" : 110.853, + "placementY" : 41.754, + "placementWidth" : 5.0, + "placementHeight" : 5.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 5.0, + "contentHeight" : 5.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Languages[6]/LanguageRows[2]/LanguageRow_Russian[2]", + "entityName" : "LanguageRow_Russian", + "entityKind" : "ShapeContainerNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Languages[6]/LanguageRows[2]", + "childIndex" : 2, + "depth" : 7, + "layer" : 7, + "computedX" : 17.8, + "computedY" : 24.204, + "placementX" : 17.8, + "placementY" : 24.204, + "placementWidth" : 107.456, + "placementHeight" : 8.5, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 107.456, + "contentHeight" : 8.5, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Languages[6]/LanguageRows[2]/LanguageRow_Russian[2]/Language_Russian[0]", + "entityName" : "Language_Russian", + "entityKind" : "ParagraphNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Languages[6]/LanguageRows[2]/LanguageRow_Russian[2]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 17.8, + "computedY" : 24.184, + "placementX" : 17.8, + "placementY" : 24.184, + "placementWidth" : 23.757, + "placementHeight" : 8.52, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 23.757, + "contentHeight" : 8.52, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Languages[6]/LanguageRows[2]/LanguageRow_Russian[2]/LanguageRating[1]", + "entityName" : "LanguageRating", + "entityKind" : "ShapeContainerNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Languages[6]/LanguageRows[2]/LanguageRow_Russian[2]", + "childIndex" : 1, + "depth" : 8, + "layer" : 8, + "computedX" : 70.453, + "computedY" : 25.954, + "placementX" : 70.453, + "placementY" : 25.954, + "placementWidth" : 45.4, + "placementHeight" : 5.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 45.4, + "contentHeight" : 5.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Languages[6]/LanguageRows[2]/LanguageRow_Russian[2]/LanguageRating[1]/LanguageDot_0[0]", + "entityName" : "LanguageDot_0", + "entityKind" : "EllipseNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Languages[6]/LanguageRows[2]/LanguageRow_Russian[2]/LanguageRating[1]", + "childIndex" : 0, + "depth" : 9, + "layer" : 9, + "computedX" : 70.453, + "computedY" : 25.954, + "placementX" : 70.453, + "placementY" : 25.954, + "placementWidth" : 5.0, + "placementHeight" : 5.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 5.0, + "contentHeight" : 5.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Languages[6]/LanguageRows[2]/LanguageRow_Russian[2]/LanguageRating[1]/LanguageDot_1[1]", + "entityName" : "LanguageDot_1", + "entityKind" : "EllipseNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Languages[6]/LanguageRows[2]/LanguageRow_Russian[2]/LanguageRating[1]", + "childIndex" : 1, + "depth" : 9, + "layer" : 9, + "computedX" : 80.553, + "computedY" : 25.954, + "placementX" : 80.553, + "placementY" : 25.954, + "placementWidth" : 5.0, + "placementHeight" : 5.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 5.0, + "contentHeight" : 5.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Languages[6]/LanguageRows[2]/LanguageRow_Russian[2]/LanguageRating[1]/LanguageDot_2[2]", + "entityName" : "LanguageDot_2", + "entityKind" : "EllipseNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Languages[6]/LanguageRows[2]/LanguageRow_Russian[2]/LanguageRating[1]", + "childIndex" : 2, + "depth" : 9, + "layer" : 9, + "computedX" : 90.653, + "computedY" : 25.954, + "placementX" : 90.653, + "placementY" : 25.954, + "placementWidth" : 5.0, + "placementHeight" : 5.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 5.0, + "contentHeight" : 5.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Languages[6]/LanguageRows[2]/LanguageRow_Russian[2]/LanguageRating[1]/LanguageDot_3[3]", + "entityName" : "LanguageDot_3", + "entityKind" : "EllipseNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Languages[6]/LanguageRows[2]/LanguageRow_Russian[2]/LanguageRating[1]", + "childIndex" : 3, + "depth" : 9, + "layer" : 9, + "computedX" : 100.753, + "computedY" : 25.954, + "placementX" : 100.753, + "placementY" : 25.954, + "placementWidth" : 5.0, + "placementHeight" : 5.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 5.0, + "contentHeight" : 5.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Languages[6]/LanguageRows[2]/LanguageRow_Russian[2]/LanguageRating[1]/LanguageDot_4[4]", + "entityName" : "LanguageDot_4", + "entityKind" : "EllipseNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Sidebar[0]/SidebarBody[1]/Languages[6]/LanguageRows[2]/LanguageRow_Russian[2]/LanguageRating[1]", + "childIndex" : 4, + "depth" : 9, + "layer" : 9, + "computedX" : 110.853, + "computedY" : 25.954, + "placementX" : 110.853, + "placementY" : 25.954, + "placementWidth" : 5.0, + "placementHeight" : 5.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 5.0, + "contentHeight" : 5.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Main[1]", + "entityName" : "Main", + "entityKind" : "SectionNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]", + "childIndex" : 1, + "depth" : 3, + "layer" : 3, + "computedX" : 143.056, + "computedY" : 12.73, + "placementX" : 143.056, + "placementY" : 12.73, + "placementWidth" : 348.544, + "placementHeight" : 724.55, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 348.544, + "contentHeight" : 724.55, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 21.9, + "right" : 20.0, + "bottom" : 0.0, + "left" : 27.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Main[1]/Identity[0]", + "entityName" : "Identity", + "entityKind" : "SectionNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Main[1]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 170.056, + "computedY" : 617.19, + "placementX" : 170.056, + "placementY" : 617.19, + "placementWidth" : 301.544, + "placementHeight" : 98.19, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 301.544, + "contentHeight" : 98.19, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Main[1]/Identity[0]/DisplayName[0]", + "entityName" : "DisplayName", + "entityKind" : "ParagraphNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Main[1]/Identity[0]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 170.056, + "computedY" : 671.34, + "placementX" : 170.056, + "placementY" : 671.34, + "placementWidth" : 176.476, + "placementHeight" : 44.04, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 176.476, + "contentHeight" : 44.04, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 6.3, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Main[1]/Identity[0]/ProfessionalTitle[1]", + "entityName" : "ProfessionalTitle", + "entityKind" : "ParagraphNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Main[1]/Identity[0]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 170.056, + "computedY" : 653.64, + "placementX" : 170.056, + "placementY" : 653.64, + "placementWidth" : 163.438, + "placementHeight" : 11.4, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 163.438, + "contentHeight" : 11.4, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 14.9, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Main[1]/Identity[0]/IdentityRule[2]", + "entityName" : "IdentityRule", + "entityKind" : "ShapeContainerNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Main[1]/Identity[0]", + "childIndex" : 2, + "depth" : 5, + "layer" : 5, + "computedX" : 170.056, + "computedY" : 637.69, + "placementX" : 170.056, + "placementY" : 637.69, + "placementWidth" : 301.544, + "placementHeight" : 1.05, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 301.544, + "contentHeight" : 1.05, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Main[1]/Identity[0]/IdentityRule[2]/IdentityAccentSegment[0]", + "entityName" : "IdentityAccentSegment", + "entityKind" : "ShapeNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Main[1]/Identity[0]/IdentityRule[2]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 170.056, + "computedY" : 637.69, + "placementX" : 170.056, + "placementY" : 637.69, + "placementWidth" : 27.0, + "placementHeight" : 1.05, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 27.0, + "contentHeight" : 1.05, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Main[1]/Identity[0]/SpacerNode[3]", + "entityName" : null, + "entityKind" : "SpacerNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Main[1]/Identity[0]", + "childIndex" : 3, + "depth" : 5, + "layer" : 5, + "computedX" : 170.056, + "computedY" : 617.19, + "placementX" : 170.056, + "placementY" : 617.19, + "placementWidth" : 0.0, + "placementHeight" : 20.5, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 0.0, + "contentHeight" : 20.5, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Main[1]/Profile[1]", + "entityName" : "Profile", + "entityKind" : "SectionNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Main[1]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 170.056, + "computedY" : 525.58, + "placementX" : 170.056, + "placementY" : 525.58, + "placementWidth" : 287.543, + "placementHeight" : 91.61, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 287.543, + "contentHeight" : 91.61, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Main[1]/Profile[1]/MainHeading_PROFILE[0]", + "entityName" : "MainHeading_PROFILE", + "entityKind" : "ParagraphNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Main[1]/Profile[1]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 170.056, + "computedY" : 606.75, + "placementX" : 170.056, + "placementY" : 606.75, + "placementWidth" : 42.147, + "placementHeight" : 10.44, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 42.147, + "contentHeight" : 10.44, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 4.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Main[1]/Profile[1]/MainHeadingRule_PROFILE[1]", + "entityName" : "MainHeadingRule_PROFILE", + "entityKind" : "LineNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Main[1]/Profile[1]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 170.056, + "computedY" : 601.7, + "placementX" : 170.056, + "placementY" : 601.7, + "placementWidth" : 19.0, + "placementHeight" : 1.05, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 19.0, + "contentHeight" : 1.05, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 14.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Main[1]/Profile[1]/ProfileText[2]", + "entityName" : "ProfileText", + "entityKind" : "ParagraphNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Main[1]/Profile[1]", + "childIndex" : 2, + "depth" : 5, + "layer" : 5, + "computedX" : 170.056, + "computedY" : 550.08, + "placementX" : 170.056, + "placementY" : 550.08, + "placementWidth" : 287.543, + "placementHeight" : 37.62, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 287.543, + "contentHeight" : 37.62, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Main[1]/Profile[1]/SpacerNode[3]", + "entityName" : null, + "entityKind" : "SpacerNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Main[1]/Profile[1]", + "childIndex" : 3, + "depth" : 5, + "layer" : 5, + "computedX" : 170.056, + "computedY" : 525.58, + "placementX" : 170.056, + "placementY" : 525.58, + "placementWidth" : 0.0, + "placementHeight" : 24.5, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 0.0, + "contentHeight" : 24.5, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Main[1]/Experience[2]", + "entityName" : "Experience", + "entityKind" : "SectionNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Main[1]", + "childIndex" : 2, + "depth" : 4, + "layer" : 4, + "computedX" : 170.056, + "computedY" : 165.63, + "placementX" : 170.056, + "placementY" : 165.63, + "placementWidth" : 301.544, + "placementHeight" : 359.95, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 301.544, + "contentHeight" : 359.95, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Main[1]/Experience[2]/MainHeading_EXPERIENCE[0]", + "entityName" : "MainHeading_EXPERIENCE", + "entityKind" : "ParagraphNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Main[1]/Experience[2]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 170.056, + "computedY" : 515.14, + "placementX" : 170.056, + "placementY" : 515.14, + "placementWidth" : 62.533, + "placementHeight" : 10.44, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 62.533, + "contentHeight" : 10.44, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 4.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Main[1]/Experience[2]/MainHeadingRule_EXPERIENCE[1]", + "entityName" : "MainHeadingRule_EXPERIENCE", + "entityKind" : "LineNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Main[1]/Experience[2]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 170.056, + "computedY" : 510.09, + "placementX" : 170.056, + "placementY" : 510.09, + "placementWidth" : 19.0, + "placementHeight" : 1.05, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 19.0, + "contentHeight" : 1.05, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 18.3, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Main[1]/Experience[2]/ExperienceHead_0[2]", + "entityName" : "ExperienceHead_0", + "entityKind" : "ShapeContainerNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Main[1]/Experience[2]", + "childIndex" : 2, + "depth" : 5, + "layer" : 5, + "computedX" : 170.056, + "computedY" : 482.49, + "placementX" : 170.056, + "placementY" : 482.49, + "placementWidth" : 301.544, + "placementHeight" : 9.3, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 301.544, + "contentHeight" : 9.3, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Main[1]/Experience[2]/ExperienceHead_0[2]/ExperienceHead_0Title[0]", + "entityName" : "ExperienceHead_0Title", + "entityKind" : "ParagraphNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Main[1]/Experience[2]/ExperienceHead_0[2]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 170.056, + "computedY" : 482.64, + "placementX" : 170.056, + "placementY" : 482.64, + "placementWidth" : 101.858, + "placementHeight" : 9.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 101.858, + "contentHeight" : 9.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Main[1]/Experience[2]/ExperienceHead_0[2]/ExperienceHead_0Dates[1]", + "entityName" : "ExperienceHead_0Dates", + "entityKind" : "ParagraphNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Main[1]/Experience[2]/ExperienceHead_0[2]", + "childIndex" : 1, + "depth" : 6, + "layer" : 6, + "computedX" : 170.056, + "computedY" : 482.82, + "placementX" : 170.056, + "placementY" : 482.82, + "placementWidth" : 301.544, + "placementHeight" : 8.64, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 301.544, + "contentHeight" : 8.64, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Main[1]/Experience[2]/ExperienceMeta_0[3]", + "entityName" : "ExperienceMeta_0", + "entityKind" : "ParagraphNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Main[1]/Experience[2]", + "childIndex" : 3, + "depth" : 5, + "layer" : 5, + "computedX" : 170.056, + "computedY" : 471.85, + "placementX" : 170.056, + "placementY" : 471.85, + "placementWidth" : 91.62, + "placementHeight" : 8.64, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 91.62, + "contentHeight" : 8.64, + "margin" : { + "top" : 2.0, + "right" : 0.0, + "bottom" : 14.3, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Main[1]/Experience[2]/ExperienceHighlights_0[4]", + "entityName" : "ExperienceHighlights_0", + "entityKind" : "ListNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Main[1]/Experience[2]", + "childIndex" : 4, + "depth" : 5, + "layer" : 5, + "computedX" : 170.056, + "computedY" : 409.07, + "placementX" : 170.056, + "placementY" : 409.07, + "placementWidth" : 208.058, + "placementHeight" : 48.48, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 208.058, + "contentHeight" : 48.48, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Main[1]/Experience[2]/MainDivider[5]", + "entityName" : "MainDivider", + "entityKind" : "LineNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Main[1]/Experience[2]", + "childIndex" : 5, + "depth" : 5, + "layer" : 5, + "computedX" : 170.056, + "computedY" : 394.07, + "placementX" : 170.056, + "placementY" : 394.07, + "placementWidth" : 301.544, + "placementHeight" : 1.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 301.544, + "contentHeight" : 1.0, + "margin" : { + "top" : 14.0, + "right" : 0.0, + "bottom" : 13.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Main[1]/Experience[2]/ExperienceHead_1[6]", + "entityName" : "ExperienceHead_1", + "entityKind" : "ShapeContainerNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Main[1]/Experience[2]", + "childIndex" : 6, + "depth" : 5, + "layer" : 5, + "computedX" : 170.056, + "computedY" : 371.77, + "placementX" : 170.056, + "placementY" : 371.77, + "placementWidth" : 301.544, + "placementHeight" : 9.3, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 301.544, + "contentHeight" : 9.3, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Main[1]/Experience[2]/ExperienceHead_1[6]/ExperienceHead_1Title[0]", + "entityName" : "ExperienceHead_1Title", + "entityKind" : "ParagraphNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Main[1]/Experience[2]/ExperienceHead_1[6]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 170.056, + "computedY" : 371.92, + "placementX" : 170.056, + "placementY" : 371.92, + "placementWidth" : 86.258, + "placementHeight" : 9.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 86.258, + "contentHeight" : 9.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Main[1]/Experience[2]/ExperienceHead_1[6]/ExperienceHead_1Dates[1]", + "entityName" : "ExperienceHead_1Dates", + "entityKind" : "ParagraphNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Main[1]/Experience[2]/ExperienceHead_1[6]", + "childIndex" : 1, + "depth" : 6, + "layer" : 6, + "computedX" : 170.056, + "computedY" : 372.1, + "placementX" : 170.056, + "placementY" : 372.1, + "placementWidth" : 301.544, + "placementHeight" : 8.64, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 301.544, + "contentHeight" : 8.64, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Main[1]/Experience[2]/ExperienceMeta_1[7]", + "entityName" : "ExperienceMeta_1", + "entityKind" : "ParagraphNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Main[1]/Experience[2]", + "childIndex" : 7, + "depth" : 5, + "layer" : 5, + "computedX" : 170.056, + "computedY" : 361.13, + "placementX" : 170.056, + "placementY" : 361.13, + "placementWidth" : 120.643, + "placementHeight" : 8.64, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 120.643, + "contentHeight" : 8.64, + "margin" : { + "top" : 2.0, + "right" : 0.0, + "bottom" : 14.3, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Main[1]/Experience[2]/ExperienceHighlights_1[8]", + "entityName" : "ExperienceHighlights_1", + "entityKind" : "ListNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Main[1]/Experience[2]", + "childIndex" : 8, + "depth" : 5, + "layer" : 5, + "computedX" : 170.056, + "computedY" : 298.35, + "placementX" : 170.056, + "placementY" : 298.35, + "placementWidth" : 194.696, + "placementHeight" : 48.48, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 194.696, + "contentHeight" : 48.48, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Main[1]/Experience[2]/MainDivider[9]", + "entityName" : "MainDivider", + "entityKind" : "LineNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Main[1]/Experience[2]", + "childIndex" : 9, + "depth" : 5, + "layer" : 5, + "computedX" : 170.056, + "computedY" : 283.35, + "placementX" : 170.056, + "placementY" : 283.35, + "placementWidth" : 301.544, + "placementHeight" : 1.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 301.544, + "contentHeight" : 1.0, + "margin" : { + "top" : 14.0, + "right" : 0.0, + "bottom" : 13.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Main[1]/Experience[2]/ExperienceHead_2[10]", + "entityName" : "ExperienceHead_2", + "entityKind" : "ShapeContainerNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Main[1]/Experience[2]", + "childIndex" : 10, + "depth" : 5, + "layer" : 5, + "computedX" : 170.056, + "computedY" : 261.05, + "placementX" : 170.056, + "placementY" : 261.05, + "placementWidth" : 301.544, + "placementHeight" : 9.3, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 301.544, + "contentHeight" : 9.3, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Main[1]/Experience[2]/ExperienceHead_2[10]/ExperienceHead_2Title[0]", + "entityName" : "ExperienceHead_2Title", + "entityKind" : "ParagraphNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Main[1]/Experience[2]/ExperienceHead_2[10]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 170.056, + "computedY" : 261.2, + "placementX" : 170.056, + "placementY" : 261.2, + "placementWidth" : 93.48, + "placementHeight" : 9.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 93.48, + "contentHeight" : 9.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Main[1]/Experience[2]/ExperienceHead_2[10]/ExperienceHead_2Dates[1]", + "entityName" : "ExperienceHead_2Dates", + "entityKind" : "ParagraphNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Main[1]/Experience[2]/ExperienceHead_2[10]", + "childIndex" : 1, + "depth" : 6, + "layer" : 6, + "computedX" : 170.056, + "computedY" : 261.38, + "placementX" : 170.056, + "placementY" : 261.38, + "placementWidth" : 301.544, + "placementHeight" : 8.64, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 301.544, + "contentHeight" : 8.64, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Main[1]/Experience[2]/ExperienceMeta_2[11]", + "entityName" : "ExperienceMeta_2", + "entityKind" : "ParagraphNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Main[1]/Experience[2]", + "childIndex" : 11, + "depth" : 5, + "layer" : 5, + "computedX" : 170.056, + "computedY" : 250.41, + "placementX" : 170.056, + "placementY" : 250.41, + "placementWidth" : 68.875, + "placementHeight" : 8.64, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 68.875, + "contentHeight" : 8.64, + "margin" : { + "top" : 2.0, + "right" : 0.0, + "bottom" : 14.3, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Main[1]/Experience[2]/ExperienceHighlights_2[12]", + "entityName" : "ExperienceHighlights_2", + "entityKind" : "ListNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Main[1]/Experience[2]", + "childIndex" : 12, + "depth" : 5, + "layer" : 5, + "computedX" : 170.056, + "computedY" : 187.63, + "placementX" : 170.056, + "placementY" : 187.63, + "placementWidth" : 193.731, + "placementHeight" : 48.48, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 193.731, + "contentHeight" : 48.48, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Main[1]/Experience[2]/SpacerNode[13]", + "entityName" : null, + "entityKind" : "SpacerNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Main[1]/Experience[2]", + "childIndex" : 13, + "depth" : 5, + "layer" : 5, + "computedX" : 170.056, + "computedY" : 165.63, + "placementX" : 170.056, + "placementY" : 165.63, + "placementWidth" : 0.0, + "placementHeight" : 22.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 0.0, + "contentHeight" : 22.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Main[1]/Projects[3]", + "entityName" : "Projects", + "entityKind" : "SectionNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Main[1]", + "childIndex" : 3, + "depth" : 4, + "layer" : 4, + "computedX" : 170.056, + "computedY" : 45.1, + "placementX" : 170.056, + "placementY" : 45.1, + "placementWidth" : 301.544, + "placementHeight" : 120.53, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 301.544, + "contentHeight" : 120.53, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Main[1]/Projects[3]/MainHeading_PROJECTS[0]", + "entityName" : "MainHeading_PROJECTS", + "entityKind" : "ParagraphNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Main[1]/Projects[3]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 170.056, + "computedY" : 155.19, + "placementX" : 170.056, + "placementY" : 155.19, + "placementWidth" : 50.795, + "placementHeight" : 10.44, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 50.795, + "contentHeight" : 10.44, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 4.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Main[1]/Projects[3]/MainHeadingRule_PROJECTS[1]", + "entityName" : "MainHeadingRule_PROJECTS", + "entityKind" : "LineNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Main[1]/Projects[3]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 170.056, + "computedY" : 150.14, + "placementX" : 170.056, + "placementY" : 150.14, + "placementWidth" : 19.0, + "placementHeight" : 1.05, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 19.0, + "contentHeight" : 1.05, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 8.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Main[1]/Projects[3]/ProjectHead_0[2]", + "entityName" : "ProjectHead_0", + "entityKind" : "ShapeContainerNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Main[1]/Projects[3]", + "childIndex" : 2, + "depth" : 5, + "layer" : 5, + "computedX" : 170.056, + "computedY" : 132.84, + "placementX" : 170.056, + "placementY" : 132.84, + "placementWidth" : 301.544, + "placementHeight" : 9.3, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 301.544, + "contentHeight" : 9.3, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Main[1]/Projects[3]/ProjectHead_0[2]/ProjectHead_0Title[0]", + "entityName" : "ProjectHead_0Title", + "entityKind" : "ParagraphNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Main[1]/Projects[3]/ProjectHead_0[2]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 170.056, + "computedY" : 132.99, + "placementX" : 170.056, + "placementY" : 132.99, + "placementWidth" : 47.88, + "placementHeight" : 9.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 47.88, + "contentHeight" : 9.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Main[1]/Projects[3]/ProjectHead_0[2]/ProjectHead_0Dates[1]", + "entityName" : "ProjectHead_0Dates", + "entityKind" : "ParagraphNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Main[1]/Projects[3]/ProjectHead_0[2]", + "childIndex" : 1, + "depth" : 6, + "layer" : 6, + "computedX" : 170.056, + "computedY" : 133.17, + "placementX" : 170.056, + "placementY" : 133.17, + "placementWidth" : 301.544, + "placementHeight" : 8.64, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 301.544, + "contentHeight" : 8.64, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Main[1]/Projects[3]/SpacerNode[3]", + "entityName" : null, + "entityKind" : "SpacerNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Main[1]/Projects[3]", + "childIndex" : 3, + "depth" : 5, + "layer" : 5, + "computedX" : 170.056, + "computedY" : 127.84, + "placementX" : 170.056, + "placementY" : 127.84, + "placementWidth" : 0.0, + "placementHeight" : 5.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 0.0, + "contentHeight" : 5.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Main[1]/Projects[3]/ProjectDescription_0[4]", + "entityName" : "ProjectDescription_0", + "entityKind" : "ParagraphNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Main[1]/Projects[3]", + "childIndex" : 4, + "depth" : 5, + "layer" : 5, + "computedX" : 170.056, + "computedY" : 109.62, + "placementX" : 170.056, + "placementY" : 109.62, + "placementWidth" : 289.197, + "placementHeight" : 18.22, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 289.197, + "contentHeight" : 18.22, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Main[1]/Projects[3]/MainDivider[5]", + "entityName" : "MainDivider", + "entityKind" : "LineNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Main[1]/Projects[3]", + "childIndex" : 5, + "depth" : 5, + "layer" : 5, + "computedX" : 170.056, + "computedY" : 102.62, + "placementX" : 170.056, + "placementY" : 102.62, + "placementWidth" : 301.544, + "placementHeight" : 1.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 301.544, + "contentHeight" : 1.0, + "margin" : { + "top" : 6.0, + "right" : 0.0, + "bottom" : 7.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Main[1]/Projects[3]/ProjectHead_1[6]", + "entityName" : "ProjectHead_1", + "entityKind" : "ShapeContainerNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Main[1]/Projects[3]", + "childIndex" : 6, + "depth" : 5, + "layer" : 5, + "computedX" : 170.056, + "computedY" : 86.32, + "placementX" : 170.056, + "placementY" : 86.32, + "placementWidth" : 301.544, + "placementHeight" : 9.3, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 301.544, + "contentHeight" : 9.3, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Main[1]/Projects[3]/ProjectHead_1[6]/ProjectHead_1Title[0]", + "entityName" : "ProjectHead_1Title", + "entityKind" : "ParagraphNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Main[1]/Projects[3]/ProjectHead_1[6]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 170.056, + "computedY" : 86.47, + "placementX" : 170.056, + "placementY" : 86.47, + "placementWidth" : 63.15, + "placementHeight" : 9.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 63.15, + "contentHeight" : 9.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Main[1]/Projects[3]/ProjectHead_1[6]/ProjectHead_1Dates[1]", + "entityName" : "ProjectHead_1Dates", + "entityKind" : "ParagraphNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Main[1]/Projects[3]/ProjectHead_1[6]", + "childIndex" : 1, + "depth" : 6, + "layer" : 6, + "computedX" : 170.056, + "computedY" : 86.65, + "placementX" : 170.056, + "placementY" : 86.65, + "placementWidth" : 301.544, + "placementHeight" : 8.64, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 301.544, + "contentHeight" : 8.64, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Main[1]/Projects[3]/SpacerNode[7]", + "entityName" : null, + "entityKind" : "SpacerNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Main[1]/Projects[3]", + "childIndex" : 7, + "depth" : 5, + "layer" : 5, + "computedX" : 170.056, + "computedY" : 81.32, + "placementX" : 170.056, + "placementY" : 81.32, + "placementWidth" : 0.0, + "placementHeight" : 5.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 0.0, + "contentHeight" : 5.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Main[1]/Projects[3]/ProjectDescription_1[8]", + "entityName" : "ProjectDescription_1", + "entityKind" : "ParagraphNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Main[1]/Projects[3]", + "childIndex" : 8, + "depth" : 5, + "layer" : 5, + "computedX" : 170.056, + "computedY" : 63.1, + "placementX" : 170.056, + "placementY" : 63.1, + "placementWidth" : 296.901, + "placementHeight" : 18.22, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 296.901, + "contentHeight" : 18.22, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Main[1]/Projects[3]/SpacerNode[9]", + "entityName" : null, + "entityKind" : "SpacerNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Main[1]/Projects[3]", + "childIndex" : 9, + "depth" : 5, + "layer" : 5, + "computedX" : 170.056, + "computedY" : 45.1, + "placementX" : 170.056, + "placementY" : 45.1, + "placementWidth" : 0.0, + "placementHeight" : 18.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 0.0, + "contentHeight" : 18.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Main[1]/References[4]", + "entityName" : "References", + "entityKind" : "SectionNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Main[1]", + "childIndex" : 4, + "depth" : 4, + "layer" : 4, + "computedX" : 170.056, + "computedY" : 12.73, + "placementX" : 170.056, + "placementY" : 12.73, + "placementWidth" : 76.287, + "placementHeight" : 32.37, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 76.287, + "contentHeight" : 32.37, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Main[1]/References[4]/MainHeading_REFERENCES[0]", + "entityName" : "MainHeading_REFERENCES", + "entityKind" : "ParagraphNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Main[1]/References[4]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 170.056, + "computedY" : 34.66, + "placementX" : 170.056, + "placementY" : 34.66, + "placementWidth" : 64.021, + "placementHeight" : 10.44, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 64.021, + "contentHeight" : 10.44, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 4.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Main[1]/References[4]/MainHeadingRule_REFERENCES[1]", + "entityName" : "MainHeadingRule_REFERENCES", + "entityKind" : "LineNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Main[1]/References[4]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 170.056, + "computedY" : 29.61, + "placementX" : 170.056, + "placementY" : 29.61, + "placementWidth" : 19.0, + "placementHeight" : 1.05, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 19.0, + "contentHeight" : 1.05, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 8.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ProfessionalSidebarCv[0]/PageGrid[0]/Main[1]/References[4]/ReferencesNote[2]", + "entityName" : "ReferencesNote", + "entityKind" : "ParagraphNode", + "parentPath" : "ProfessionalSidebarCv[0]/PageGrid[0]/Main[1]/References[4]", + "childIndex" : 2, + "depth" : 5, + "layer" : 5, + "computedX" : 170.056, + "computedY" : 12.73, + "placementX" : 170.056, + "placementY" : 12.73, + "placementWidth" : 76.287, + "placementHeight" : 8.88, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 76.287, + "contentHeight" : 8.88, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + } ] +} diff --git a/qa/src/test/resources/layout-snapshots/canonical-templates/cv-v2/serif_headline_layout.json b/qa/src/test/resources/layout-snapshots/canonical-templates/cv-v2/serif_headline_layout.json new file mode 100644 index 000000000..a743fc3bf --- /dev/null +++ b/qa/src/test/resources/layout-snapshots/canonical-templates/cv-v2/serif_headline_layout.json @@ -0,0 +1,7997 @@ +{ + "formatVersion" : "2.0", + "canvas" : { + "pageWidth" : 595.276, + "pageHeight" : 841.89, + "innerWidth" : 531.911, + "innerHeight" : 778.525, + "margin" : { + "top" : 31.682, + "right" : 31.682, + "bottom" : 31.682, + "left" : 31.682 + } + }, + "totalPages" : 1, + "nodes" : [ { + "path" : "SerifHeadlineCv[0]", + "entityName" : "SerifHeadlineCv", + "entityKind" : "ContainerNode", + "parentPath" : null, + "childIndex" : 0, + "depth" : 1, + "layer" : 1, + "computedX" : 31.682, + "computedY" : 43.938, + "placementX" : 31.682, + "placementY" : 43.938, + "placementWidth" : 531.911, + "placementHeight" : 766.27, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 531.911, + "contentHeight" : 766.27, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/Masthead[0]", + "entityName" : "Masthead", + "entityKind" : "RowNode", + "parentPath" : "SerifHeadlineCv[0]", + "childIndex" : 0, + "depth" : 2, + "layer" : 2, + "computedX" : 31.682, + "computedY" : 740.942, + "placementX" : 31.682, + "placementY" : 740.942, + "placementWidth" : 531.911, + "placementHeight" : 69.266, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 531.911, + "contentHeight" : 69.266, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 11.595, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/Masthead[0]/Identity[0]", + "entityName" : "Identity", + "entityKind" : "SectionNode", + "parentPath" : "SerifHeadlineCv[0]/Masthead[0]", + "childIndex" : 0, + "depth" : 3, + "layer" : 3, + "computedX" : 31.682, + "computedY" : 740.942, + "placementX" : 31.682, + "placementY" : 740.942, + "placementWidth" : 377.596, + "placementHeight" : 69.266, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 377.596, + "contentHeight" : 69.266, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/Masthead[0]/Identity[0]/Name[0]", + "entityName" : "Name", + "entityKind" : "ParagraphNode", + "parentPath" : "SerifHeadlineCv[0]/Masthead[0]/Identity[0]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 31.682, + "computedY" : 767.132, + "placementX" : 31.682, + "placementY" : 767.132, + "placementWidth" : 377.596, + "placementHeight" : 43.076, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 377.596, + "contentHeight" : 43.076, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.359, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/Masthead[0]/Identity[0]/Role[1]", + "entityName" : "Role", + "entityKind" : "ParagraphNode", + "parentPath" : "SerifHeadlineCv[0]/Masthead[0]/Identity[0]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 31.682, + "computedY" : 751.271, + "placementX" : 31.682, + "placementY" : 751.271, + "placementWidth" : 172.592, + "placementHeight" : 15.502, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 172.592, + "contentHeight" : 15.502, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 8.585, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/Masthead[0]/Identity[0]/GoldRule[2]", + "entityName" : "GoldRule", + "entityKind" : "LineNode", + "parentPath" : "SerifHeadlineCv[0]/Masthead[0]/Identity[0]", + "childIndex" : 2, + "depth" : 4, + "layer" : 4, + "computedX" : 31.682, + "computedY" : 740.942, + "placementX" : 31.682, + "placementY" : 740.942, + "placementWidth" : 45.925, + "placementHeight" : 1.744, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 45.925, + "contentHeight" : 1.744, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/Masthead[0]/Contact[1]", + "entityName" : "Contact", + "entityKind" : "SectionNode", + "parentPath" : "SerifHeadlineCv[0]/Masthead[0]", + "childIndex" : 1, + "depth" : 3, + "layer" : 3, + "computedX" : 446.733, + "computedY" : 741.916, + "placementX" : 446.733, + "placementY" : 741.916, + "placementWidth" : 116.861, + "placementHeight" : 68.292, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 116.861, + "contentHeight" : 68.292, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/Masthead[0]/Contact[1]/ContactRow_phone[0]", + "entityName" : "ContactRow_phone", + "entityKind" : "ShapeContainerNode", + "parentPath" : "SerifHeadlineCv[0]/Masthead[0]/Contact[1]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 446.733, + "computedY" : 799.389, + "placementX" : 446.733, + "placementY" : 799.389, + "placementWidth" : 116.861, + "placementHeight" : 10.819, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 116.861, + "contentHeight" : 10.819, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/Masthead[0]/Contact[1]/ContactRow_phone[0]/ContactGlyph_phone[0]", + "entityName" : "ContactGlyph_phone", + "entityKind" : "ImageNode", + "parentPath" : "SerifHeadlineCv[0]/Masthead[0]/Contact[1]/ContactRow_phone[0]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 446.733, + "computedY" : 800.147, + "placementX" : 446.733, + "placementY" : 800.147, + "placementWidth" : 9.301, + "placementHeight" : 9.301, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 9.301, + "contentHeight" : 9.301, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/Masthead[0]/Contact[1]/ContactRow_phone[0]/ContactValue_phone[1]", + "entityName" : "ContactValue_phone", + "entityKind" : "ParagraphNode", + "parentPath" : "SerifHeadlineCv[0]/Masthead[0]/Contact[1]/ContactRow_phone[0]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 465.335, + "computedY" : 799.954, + "placementX" : 465.335, + "placementY" : 799.954, + "placementWidth" : 67.643, + "placementHeight" : 9.689, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 67.643, + "contentHeight" : 9.689, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/Masthead[0]/Contact[1]/ContactRow_email[1]", + "entityName" : "ContactRow_email", + "entityKind" : "ShapeContainerNode", + "parentPath" : "SerifHeadlineCv[0]/Masthead[0]/Contact[1]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 446.733, + "computedY" : 785.02, + "placementX" : 446.733, + "placementY" : 785.02, + "placementWidth" : 116.861, + "placementHeight" : 10.819, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 116.861, + "contentHeight" : 10.819, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/Masthead[0]/Contact[1]/ContactRow_email[1]/ContactGlyph_email[0]", + "entityName" : "ContactGlyph_email", + "entityKind" : "ImageNode", + "parentPath" : "SerifHeadlineCv[0]/Masthead[0]/Contact[1]/ContactRow_email[1]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 446.733, + "computedY" : 785.779, + "placementX" : 446.733, + "placementY" : 785.779, + "placementWidth" : 9.301, + "placementHeight" : 9.301, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 9.301, + "contentHeight" : 9.301, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/Masthead[0]/Contact[1]/ContactRow_email[1]/ContactValue_email[1]", + "entityName" : "ContactValue_email", + "entityKind" : "ParagraphNode", + "parentPath" : "SerifHeadlineCv[0]/Masthead[0]/Contact[1]/ContactRow_email[1]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 465.335, + "computedY" : 785.586, + "placementX" : 465.335, + "placementY" : 785.586, + "placementWidth" : 85.495, + "placementHeight" : 9.689, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 85.495, + "contentHeight" : 9.689, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/Masthead[0]/Contact[1]/ContactRow_location[2]", + "entityName" : "ContactRow_location", + "entityKind" : "ShapeContainerNode", + "parentPath" : "SerifHeadlineCv[0]/Masthead[0]/Contact[1]", + "childIndex" : 2, + "depth" : 4, + "layer" : 4, + "computedX" : 446.733, + "computedY" : 770.652, + "placementX" : 446.733, + "placementY" : 770.652, + "placementWidth" : 116.861, + "placementHeight" : 10.819, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 116.861, + "contentHeight" : 10.819, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/Masthead[0]/Contact[1]/ContactRow_location[2]/ContactGlyph_location[0]", + "entityName" : "ContactGlyph_location", + "entityKind" : "ImageNode", + "parentPath" : "SerifHeadlineCv[0]/Masthead[0]/Contact[1]/ContactRow_location[2]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 446.733, + "computedY" : 771.411, + "placementX" : 446.733, + "placementY" : 771.411, + "placementWidth" : 9.301, + "placementHeight" : 9.301, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 9.301, + "contentHeight" : 9.301, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/Masthead[0]/Contact[1]/ContactRow_location[2]/ContactValue_location[1]", + "entityName" : "ContactValue_location", + "entityKind" : "ParagraphNode", + "parentPath" : "SerifHeadlineCv[0]/Masthead[0]/Contact[1]/ContactRow_location[2]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 465.335, + "computedY" : 771.217, + "placementX" : 465.335, + "placementY" : 771.217, + "placementWidth" : 70.3, + "placementHeight" : 9.689, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 70.3, + "contentHeight" : 9.689, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/Masthead[0]/Contact[1]/ContactRow_linkedin[3]", + "entityName" : "ContactRow_linkedin", + "entityKind" : "ShapeContainerNode", + "parentPath" : "SerifHeadlineCv[0]/Masthead[0]/Contact[1]", + "childIndex" : 3, + "depth" : 4, + "layer" : 4, + "computedX" : 446.733, + "computedY" : 756.284, + "placementX" : 446.733, + "placementY" : 756.284, + "placementWidth" : 116.861, + "placementHeight" : 10.819, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 116.861, + "contentHeight" : 10.819, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/Masthead[0]/Contact[1]/ContactRow_linkedin[3]/ContactGlyph_linkedin[0]", + "entityName" : "ContactGlyph_linkedin", + "entityKind" : "ImageNode", + "parentPath" : "SerifHeadlineCv[0]/Masthead[0]/Contact[1]/ContactRow_linkedin[3]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 446.733, + "computedY" : 757.043, + "placementX" : 446.733, + "placementY" : 757.043, + "placementWidth" : 9.301, + "placementHeight" : 9.301, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 9.301, + "contentHeight" : 9.301, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/Masthead[0]/Contact[1]/ContactRow_linkedin[3]/ContactValue_linkedin[1]", + "entityName" : "ContactValue_linkedin", + "entityKind" : "ParagraphNode", + "parentPath" : "SerifHeadlineCv[0]/Masthead[0]/Contact[1]/ContactRow_linkedin[3]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 465.335, + "computedY" : 756.849, + "placementX" : 465.335, + "placementY" : 756.849, + "placementWidth" : 99.487, + "placementHeight" : 9.689, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 99.487, + "contentHeight" : 9.689, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/Masthead[0]/Contact[1]/ContactRow_github[4]", + "entityName" : "ContactRow_github", + "entityKind" : "ShapeContainerNode", + "parentPath" : "SerifHeadlineCv[0]/Masthead[0]/Contact[1]", + "childIndex" : 4, + "depth" : 4, + "layer" : 4, + "computedX" : 446.733, + "computedY" : 741.916, + "placementX" : 446.733, + "placementY" : 741.916, + "placementWidth" : 116.861, + "placementHeight" : 10.819, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 116.861, + "contentHeight" : 10.819, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/Masthead[0]/Contact[1]/ContactRow_github[4]/ContactGlyph_github[0]", + "entityName" : "ContactGlyph_github", + "entityKind" : "ImageNode", + "parentPath" : "SerifHeadlineCv[0]/Masthead[0]/Contact[1]/ContactRow_github[4]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 446.733, + "computedY" : 742.675, + "placementX" : 446.733, + "placementY" : 742.675, + "placementWidth" : 9.301, + "placementHeight" : 9.301, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 9.301, + "contentHeight" : 9.301, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/Masthead[0]/Contact[1]/ContactRow_github[4]/ContactValue_github[1]", + "entityName" : "ContactValue_github", + "entityKind" : "ParagraphNode", + "parentPath" : "SerifHeadlineCv[0]/Masthead[0]/Contact[1]/ContactRow_github[4]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 465.335, + "computedY" : 742.481, + "placementX" : 465.335, + "placementY" : 742.481, + "placementWidth" : 84.284, + "placementHeight" : 9.689, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 84.284, + "contentHeight" : 9.689, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/Summary[1]", + "entityName" : "Summary", + "entityKind" : "ParagraphNode", + "parentPath" : "SerifHeadlineCv[0]", + "childIndex" : 1, + "depth" : 2, + "layer" : 2, + "computedX" : 31.682, + "computedY" : 697.541, + "placementX" : 31.682, + "placementY" : 697.541, + "placementWidth" : 362.544, + "placementHeight" : 31.806, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 362.544, + "contentHeight" : 31.806, + "margin" : { + "top" : 0.0, + "right" : 150.584, + "bottom" : 13.041, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/MastheadRule[2]", + "entityName" : "MastheadRule", + "entityKind" : "LineNode", + "parentPath" : "SerifHeadlineCv[0]", + "childIndex" : 2, + "depth" : 2, + "layer" : 2, + "computedX" : 31.682, + "computedY" : 683.5, + "placementX" : 31.682, + "placementY" : 683.5, + "placementWidth" : 531.911, + "placementHeight" : 1.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 531.911, + "contentHeight" : 1.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 13.511, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]", + "entityName" : "BodyColumns", + "entityKind" : "RowNode", + "parentPath" : "SerifHeadlineCv[0]", + "childIndex" : 3, + "depth" : 2, + "layer" : 2, + "computedX" : 31.682, + "computedY" : 189.371, + "placementX" : 31.682, + "placementY" : 189.371, + "placementWidth" : 531.911, + "placementHeight" : 480.618, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 531.911, + "contentHeight" : 480.618, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 5.43, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/MainColumn[0]", + "entityName" : "MainColumn", + "entityKind" : "SectionNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]", + "childIndex" : 0, + "depth" : 3, + "layer" : 3, + "computedX" : 31.682, + "computedY" : 218.426, + "placementX" : 31.682, + "placementY" : 218.426, + "placementWidth" : 362.152, + "placementHeight" : 451.563, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 362.152, + "contentHeight" : 451.563, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 19.175, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/MainColumn[0]/SectionHeading_Experience[0]", + "entityName" : "SectionHeading_Experience", + "entityKind" : "ShapeContainerNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/MainColumn[0]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 31.682, + "computedY" : 657.394, + "placementX" : 31.682, + "placementY" : 657.394, + "placementWidth" : 342.976, + "placementHeight" : 12.595, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 342.976, + "contentHeight" : 12.595, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/MainColumn[0]/SectionHeading_Experience[0]/HeadingDash_Experience[0]", + "entityName" : "HeadingDash_Experience", + "entityKind" : "LineNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/MainColumn[0]/SectionHeading_Experience[0]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 14.533, + "computedY" : 662.819, + "placementX" : 14.533, + "placementY" : 662.819, + "placementWidth" : 12.789, + "placementHeight" : 1.744, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 12.789, + "contentHeight" : 1.744, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/MainColumn[0]/SectionHeading_Experience[0]/SectionTitle_Experience[1]", + "entityName" : "SectionTitle_Experience", + "entityKind" : "ParagraphNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/MainColumn[0]/SectionHeading_Experience[0]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 36.623, + "computedY" : 657.878, + "placementX" : 36.623, + "placementY" : 657.878, + "placementWidth" : 72.353, + "placementHeight" : 11.626, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 72.353, + "contentHeight" : 11.626, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/MainColumn[0]/ExperienceRail[1]", + "entityName" : "ExperienceRail", + "entityKind" : "LayerStackNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/MainColumn[0]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 31.682, + "computedY" : 321.477, + "placementX" : 31.682, + "placementY" : 321.477, + "placementWidth" : 342.976, + "placementHeight" : 335.917, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 342.976, + "contentHeight" : 335.917, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/MainColumn[0]/ExperienceRail[1]/ExperienceRailHolder[0]", + "entityName" : "ExperienceRailHolder", + "entityKind" : "SectionNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/MainColumn[0]/ExperienceRail[1]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 33.077, + "computedY" : 321.477, + "placementX" : 33.077, + "placementY" : 321.477, + "placementWidth" : 341.581, + "placementHeight" : 317.167, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 341.581, + "contentHeight" : 317.167, + "margin" : { + "top" : 18.75, + "right" : 0.0, + "bottom" : 0.0, + "left" : 1.395 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/MainColumn[0]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]", + "entityName" : null, + "entityKind" : "SectionNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/MainColumn[0]/ExperienceRail[1]/ExperienceRailHolder[0]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 33.077, + "computedY" : 321.477, + "placementX" : 33.077, + "placementY" : 321.477, + "placementWidth" : 341.581, + "placementHeight" : 317.167, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 341.581, + "contentHeight" : 317.167, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/MainColumn[0]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]", + "entityName" : null, + "entityKind" : "LayoutAnchorNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/MainColumn[0]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 33.077, + "computedY" : 502.561, + "placementX" : 33.077, + "placementY" : 502.561, + "placementWidth" : 341.581, + "placementHeight" : 136.083, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 341.581, + "contentHeight" : 136.083, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/MainColumn[0]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]", + "entityName" : null, + "entityKind" : "SectionNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/MainColumn[0]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 33.077, + "computedY" : 502.561, + "placementX" : 33.077, + "placementY" : 502.561, + "placementWidth" : 341.581, + "placementHeight" : 136.083, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 341.581, + "contentHeight" : 136.083, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/MainColumn[0]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]", + "entityName" : null, + "entityKind" : "HorizontalBandsNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/MainColumn[0]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]", + "childIndex" : 0, + "depth" : 9, + "layer" : 9, + "computedX" : 33.077, + "computedY" : 502.561, + "placementX" : 33.077, + "placementY" : 502.561, + "placementWidth" : 341.581, + "placementHeight" : 136.083, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 341.581, + "contentHeight" : 136.083, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/MainColumn[0]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]", + "entityName" : null, + "entityKind" : "RowNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/MainColumn[0]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]", + "childIndex" : 0, + "depth" : 10, + "layer" : 10, + "computedX" : 33.077, + "computedY" : 502.561, + "placementX" : 33.077, + "placementY" : 502.561, + "placementWidth" : 341.581, + "placementHeight" : 136.083, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 341.581, + "contentHeight" : 136.083, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/MainColumn[0]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]", + "entityName" : null, + "entityKind" : "SectionNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/MainColumn[0]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]", + "childIndex" : 0, + "depth" : 11, + "layer" : 11, + "computedX" : 33.077, + "computedY" : 632.249, + "placementX" : 33.077, + "placementY" : 632.249, + "placementWidth" : 6.395, + "placementHeight" : 6.395, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 6.395, + "contentHeight" : 6.395, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/MainColumn[0]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]", + "entityName" : null, + "entityKind" : "Align", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/MainColumn[0]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]", + "childIndex" : 0, + "depth" : 12, + "layer" : 12, + "computedX" : 33.077, + "computedY" : 632.249, + "placementX" : 33.077, + "placementY" : 632.249, + "placementWidth" : 6.395, + "placementHeight" : 6.395, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 6.395, + "contentHeight" : 6.395, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/MainColumn[0]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]/LayoutAnchorNode[0]", + "entityName" : null, + "entityKind" : "LayoutAnchorNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/MainColumn[0]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]", + "childIndex" : 0, + "depth" : 13, + "layer" : 13, + "computedX" : 33.077, + "computedY" : 632.249, + "placementX" : 33.077, + "placementY" : 632.249, + "placementWidth" : 6.395, + "placementHeight" : 6.395, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 6.395, + "contentHeight" : 6.395, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/MainColumn[0]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]/LayoutAnchorNode[0]/marker[0]", + "entityName" : "marker", + "entityKind" : "CanvasLayerNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/MainColumn[0]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]/LayoutAnchorNode[0]", + "childIndex" : 0, + "depth" : 14, + "layer" : 14, + "computedX" : 33.077, + "computedY" : 632.249, + "placementX" : 33.077, + "placementY" : 632.249, + "placementWidth" : 6.395, + "placementHeight" : 6.395, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 6.395, + "contentHeight" : 6.395, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/MainColumn[0]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]/LayoutAnchorNode[0]/marker[0]/SectionNode[0]", + "entityName" : null, + "entityKind" : "SectionNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/MainColumn[0]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]/LayoutAnchorNode[0]/marker[0]", + "childIndex" : 0, + "depth" : 15, + "layer" : 15, + "computedX" : 33.077, + "computedY" : 632.249, + "placementX" : 33.077, + "placementY" : 632.249, + "placementWidth" : 6.395, + "placementHeight" : 6.395, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 6.395, + "contentHeight" : 6.395, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/MainColumn[0]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]/LayoutAnchorNode[0]/marker[0]/SectionNode[0]/Marker_SeniorSoftwareEngineer[0]", + "entityName" : "Marker_SeniorSoftwareEngineer", + "entityKind" : "EllipseNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/MainColumn[0]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]/LayoutAnchorNode[0]/marker[0]/SectionNode[0]", + "childIndex" : 0, + "depth" : 16, + "layer" : 16, + "computedX" : 33.077, + "computedY" : 632.249, + "placementX" : 33.077, + "placementY" : 632.249, + "placementWidth" : 6.395, + "placementHeight" : 6.395, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 6.395, + "contentHeight" : 6.395, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/MainColumn[0]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]", + "entityName" : null, + "entityKind" : "SectionNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/MainColumn[0]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]", + "childIndex" : 1, + "depth" : 11, + "layer" : 11, + "computedX" : 60.748, + "computedY" : 502.561, + "placementX" : 60.748, + "placementY" : 502.561, + "placementWidth" : 313.91, + "placementHeight" : 136.083, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 313.91, + "contentHeight" : 136.083, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/MainColumn[0]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]/EntryHead_SeniorSoftwareEngineer[0]", + "entityName" : "EntryHead_SeniorSoftwareEngineer", + "entityKind" : "ShapeContainerNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/MainColumn[0]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]", + "childIndex" : 0, + "depth" : 12, + "layer" : 12, + "computedX" : 60.748, + "computedY" : 632.249, + "placementX" : 60.748, + "placementY" : 632.249, + "placementWidth" : 313.91, + "placementHeight" : 6.395, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 313.91, + "contentHeight" : 6.395, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/MainColumn[0]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]/EntryHead_SeniorSoftwareEngineer[0]/JobTitle_SeniorSoftwareEngineer[0]", + "entityName" : "JobTitle_SeniorSoftwareEngineer", + "entityKind" : "ParagraphNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/MainColumn[0]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]/EntryHead_SeniorSoftwareEngineer[0]", + "childIndex" : 0, + "depth" : 13, + "layer" : 13, + "computedX" : 60.748, + "computedY" : 630.118, + "placementX" : 60.748, + "placementY" : 630.118, + "placementWidth" : 109.812, + "placementHeight" : 11.626, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 109.812, + "contentHeight" : 11.626, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/MainColumn[0]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]/EntryHead_SeniorSoftwareEngineer[0]/JobDates_SeniorSoftwareEngineer[1]", + "entityName" : "JobDates_SeniorSoftwareEngineer", + "entityKind" : "ParagraphNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/MainColumn[0]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]/EntryHead_SeniorSoftwareEngineer[0]", + "childIndex" : 1, + "depth" : 13, + "layer" : 13, + "computedX" : 60.748, + "computedY" : 631.006, + "placementX" : 60.748, + "placementY" : 631.006, + "placementWidth" : 313.91, + "placementHeight" : 9.689, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 313.91, + "contentHeight" : 9.689, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/MainColumn[0]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]/Employer_SeniorSoftwareEngineer[1]", + "entityName" : "Employer_SeniorSoftwareEngineer", + "entityKind" : "ParagraphNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/MainColumn[0]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]", + "childIndex" : 1, + "depth" : 12, + "layer" : 12, + "computedX" : 60.748, + "computedY" : 613.964, + "placementX" : 60.748, + "placementY" : 613.964, + "placementWidth" : 110.997, + "placementHeight" : 8.284, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 110.997, + "contentHeight" : 8.284, + "margin" : { + "top" : 10.001, + "right" : 0.0, + "bottom" : 8.153, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/MainColumn[0]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]/Highlights_SeniorSoftwareEngineer[2]", + "entityName" : "Highlights_SeniorSoftwareEngineer", + "entityKind" : "ListNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/MainColumn[0]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]", + "childIndex" : 2, + "depth" : 12, + "layer" : 12, + "computedX" : 60.748, + "computedY" : 537.877, + "placementX" : 60.748, + "placementY" : 537.877, + "placementWidth" : 310.75, + "placementHeight" : 67.934, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 310.75, + "contentHeight" : 67.934, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/MainColumn[0]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]/EntrySeparator_SeniorSoftwareEngineer[3]", + "entityName" : "EntrySeparator_SeniorSoftwareEngineer", + "entityKind" : "LineNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/MainColumn[0]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]", + "childIndex" : 3, + "depth" : 12, + "layer" : 12, + "computedX" : 60.748, + "computedY" : 521.38, + "placementX" : 60.748, + "placementY" : 521.38, + "placementWidth" : 313.91, + "placementHeight" : 1.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 313.91, + "contentHeight" : 1.0, + "margin" : { + "top" : 15.497, + "right" : 0.0, + "bottom" : 18.819, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/MainColumn[0]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]", + "entityName" : null, + "entityKind" : "LayoutAnchorNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/MainColumn[0]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]", + "childIndex" : 1, + "depth" : 7, + "layer" : 7, + "computedX" : 33.077, + "computedY" : 388.288, + "placementX" : 33.077, + "placementY" : 388.288, + "placementWidth" : 341.581, + "placementHeight" : 114.272, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 341.581, + "contentHeight" : 114.272, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/MainColumn[0]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]", + "entityName" : null, + "entityKind" : "SectionNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/MainColumn[0]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 33.077, + "computedY" : 388.288, + "placementX" : 33.077, + "placementY" : 388.288, + "placementWidth" : 341.581, + "placementHeight" : 114.272, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 341.581, + "contentHeight" : 114.272, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/MainColumn[0]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]", + "entityName" : null, + "entityKind" : "HorizontalBandsNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/MainColumn[0]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]", + "childIndex" : 0, + "depth" : 9, + "layer" : 9, + "computedX" : 33.077, + "computedY" : 388.288, + "placementX" : 33.077, + "placementY" : 388.288, + "placementWidth" : 341.581, + "placementHeight" : 114.272, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 341.581, + "contentHeight" : 114.272, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/MainColumn[0]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]", + "entityName" : null, + "entityKind" : "RowNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/MainColumn[0]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]", + "childIndex" : 0, + "depth" : 10, + "layer" : 10, + "computedX" : 33.077, + "computedY" : 388.288, + "placementX" : 33.077, + "placementY" : 388.288, + "placementWidth" : 341.581, + "placementHeight" : 114.272, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 341.581, + "contentHeight" : 114.272, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/MainColumn[0]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]", + "entityName" : null, + "entityKind" : "SectionNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/MainColumn[0]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]", + "childIndex" : 0, + "depth" : 11, + "layer" : 11, + "computedX" : 33.077, + "computedY" : 496.166, + "placementX" : 33.077, + "placementY" : 496.166, + "placementWidth" : 6.395, + "placementHeight" : 6.395, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 6.395, + "contentHeight" : 6.395, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/MainColumn[0]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]", + "entityName" : null, + "entityKind" : "Align", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/MainColumn[0]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]", + "childIndex" : 0, + "depth" : 12, + "layer" : 12, + "computedX" : 33.077, + "computedY" : 496.166, + "placementX" : 33.077, + "placementY" : 496.166, + "placementWidth" : 6.395, + "placementHeight" : 6.395, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 6.395, + "contentHeight" : 6.395, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/MainColumn[0]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]/LayoutAnchorNode[0]", + "entityName" : null, + "entityKind" : "LayoutAnchorNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/MainColumn[0]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]", + "childIndex" : 0, + "depth" : 13, + "layer" : 13, + "computedX" : 33.077, + "computedY" : 496.166, + "placementX" : 33.077, + "placementY" : 496.166, + "placementWidth" : 6.395, + "placementHeight" : 6.395, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 6.395, + "contentHeight" : 6.395, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/MainColumn[0]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]/LayoutAnchorNode[0]/marker[0]", + "entityName" : "marker", + "entityKind" : "CanvasLayerNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/MainColumn[0]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]/LayoutAnchorNode[0]", + "childIndex" : 0, + "depth" : 14, + "layer" : 14, + "computedX" : 33.077, + "computedY" : 496.166, + "placementX" : 33.077, + "placementY" : 496.166, + "placementWidth" : 6.395, + "placementHeight" : 6.395, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 6.395, + "contentHeight" : 6.395, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/MainColumn[0]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]/LayoutAnchorNode[0]/marker[0]/SectionNode[0]", + "entityName" : null, + "entityKind" : "SectionNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/MainColumn[0]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]/LayoutAnchorNode[0]/marker[0]", + "childIndex" : 0, + "depth" : 15, + "layer" : 15, + "computedX" : 33.077, + "computedY" : 496.166, + "placementX" : 33.077, + "placementY" : 496.166, + "placementWidth" : 6.395, + "placementHeight" : 6.395, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 6.395, + "contentHeight" : 6.395, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/MainColumn[0]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]/LayoutAnchorNode[0]/marker[0]/SectionNode[0]/Marker_SoftwareEngineer[0]", + "entityName" : "Marker_SoftwareEngineer", + "entityKind" : "EllipseNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/MainColumn[0]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]/LayoutAnchorNode[0]/marker[0]/SectionNode[0]", + "childIndex" : 0, + "depth" : 16, + "layer" : 16, + "computedX" : 33.077, + "computedY" : 496.166, + "placementX" : 33.077, + "placementY" : 496.166, + "placementWidth" : 6.395, + "placementHeight" : 6.395, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 6.395, + "contentHeight" : 6.395, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/MainColumn[0]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]", + "entityName" : null, + "entityKind" : "SectionNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/MainColumn[0]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]", + "childIndex" : 1, + "depth" : 11, + "layer" : 11, + "computedX" : 60.748, + "computedY" : 388.288, + "placementX" : 60.748, + "placementY" : 388.288, + "placementWidth" : 313.91, + "placementHeight" : 114.272, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 313.91, + "contentHeight" : 114.272, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/MainColumn[0]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]/EntryHead_SoftwareEngineer[0]", + "entityName" : "EntryHead_SoftwareEngineer", + "entityKind" : "ShapeContainerNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/MainColumn[0]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]", + "childIndex" : 0, + "depth" : 12, + "layer" : 12, + "computedX" : 60.748, + "computedY" : 496.166, + "placementX" : 60.748, + "placementY" : 496.166, + "placementWidth" : 313.91, + "placementHeight" : 6.395, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 313.91, + "contentHeight" : 6.395, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/MainColumn[0]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]/EntryHead_SoftwareEngineer[0]/JobTitle_SoftwareEngineer[0]", + "entityName" : "JobTitle_SoftwareEngineer", + "entityKind" : "ParagraphNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/MainColumn[0]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]/EntryHead_SoftwareEngineer[0]", + "childIndex" : 0, + "depth" : 13, + "layer" : 13, + "computedX" : 60.748, + "computedY" : 494.034, + "placementX" : 60.748, + "placementY" : 494.034, + "placementWidth" : 79.855, + "placementHeight" : 11.626, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 79.855, + "contentHeight" : 11.626, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/MainColumn[0]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]/EntryHead_SoftwareEngineer[0]/JobDates_SoftwareEngineer[1]", + "entityName" : "JobDates_SoftwareEngineer", + "entityKind" : "ParagraphNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/MainColumn[0]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]/EntryHead_SoftwareEngineer[0]", + "childIndex" : 1, + "depth" : 13, + "layer" : 13, + "computedX" : 60.748, + "computedY" : 494.923, + "placementX" : 60.748, + "placementY" : 494.923, + "placementWidth" : 313.91, + "placementHeight" : 9.689, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 313.91, + "contentHeight" : 9.689, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/MainColumn[0]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]/Employer_SoftwareEngineer[1]", + "entityName" : "Employer_SoftwareEngineer", + "entityKind" : "ParagraphNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/MainColumn[0]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]", + "childIndex" : 1, + "depth" : 12, + "layer" : 12, + "computedX" : 60.748, + "computedY" : 477.881, + "placementX" : 60.748, + "placementY" : 477.881, + "placementWidth" : 104.315, + "placementHeight" : 8.284, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 104.315, + "contentHeight" : 8.284, + "margin" : { + "top" : 10.001, + "right" : 0.0, + "bottom" : 8.153, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/MainColumn[0]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]/Highlights_SoftwareEngineer[2]", + "entityName" : "Highlights_SoftwareEngineer", + "entityKind" : "ListNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/MainColumn[0]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]", + "childIndex" : 2, + "depth" : 12, + "layer" : 12, + "computedX" : 60.748, + "computedY" : 423.605, + "placementX" : 60.748, + "placementY" : 423.605, + "placementWidth" : 249.59, + "placementHeight" : 46.123, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 249.59, + "contentHeight" : 46.123, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/MainColumn[0]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]/EntrySeparator_SoftwareEngineer[3]", + "entityName" : "EntrySeparator_SoftwareEngineer", + "entityKind" : "LineNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/MainColumn[0]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]", + "childIndex" : 3, + "depth" : 12, + "layer" : 12, + "computedX" : 60.748, + "computedY" : 407.108, + "placementX" : 60.748, + "placementY" : 407.108, + "placementWidth" : 313.91, + "placementHeight" : 1.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 313.91, + "contentHeight" : 1.0, + "margin" : { + "top" : 15.497, + "right" : 0.0, + "bottom" : 18.819, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/MainColumn[0]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]", + "entityName" : null, + "entityKind" : "LayoutAnchorNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/MainColumn[0]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]", + "childIndex" : 2, + "depth" : 7, + "layer" : 7, + "computedX" : 33.077, + "computedY" : 321.477, + "placementX" : 33.077, + "placementY" : 321.477, + "placementWidth" : 341.581, + "placementHeight" : 66.811, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 341.581, + "contentHeight" : 66.811, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/MainColumn[0]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]", + "entityName" : null, + "entityKind" : "SectionNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/MainColumn[0]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 33.077, + "computedY" : 321.477, + "placementX" : 33.077, + "placementY" : 321.477, + "placementWidth" : 341.581, + "placementHeight" : 66.811, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 341.581, + "contentHeight" : 66.811, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/MainColumn[0]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]", + "entityName" : null, + "entityKind" : "HorizontalBandsNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/MainColumn[0]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]", + "childIndex" : 0, + "depth" : 9, + "layer" : 9, + "computedX" : 33.077, + "computedY" : 321.477, + "placementX" : 33.077, + "placementY" : 321.477, + "placementWidth" : 341.581, + "placementHeight" : 66.811, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 341.581, + "contentHeight" : 66.811, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/MainColumn[0]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]", + "entityName" : null, + "entityKind" : "RowNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/MainColumn[0]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]", + "childIndex" : 0, + "depth" : 10, + "layer" : 10, + "computedX" : 33.077, + "computedY" : 321.477, + "placementX" : 33.077, + "placementY" : 321.477, + "placementWidth" : 341.581, + "placementHeight" : 66.811, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 341.581, + "contentHeight" : 66.811, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/MainColumn[0]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]", + "entityName" : null, + "entityKind" : "SectionNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/MainColumn[0]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]", + "childIndex" : 0, + "depth" : 11, + "layer" : 11, + "computedX" : 33.077, + "computedY" : 381.894, + "placementX" : 33.077, + "placementY" : 381.894, + "placementWidth" : 6.395, + "placementHeight" : 6.395, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 6.395, + "contentHeight" : 6.395, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/MainColumn[0]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]", + "entityName" : null, + "entityKind" : "Align", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/MainColumn[0]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]", + "childIndex" : 0, + "depth" : 12, + "layer" : 12, + "computedX" : 33.077, + "computedY" : 381.894, + "placementX" : 33.077, + "placementY" : 381.894, + "placementWidth" : 6.395, + "placementHeight" : 6.395, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 6.395, + "contentHeight" : 6.395, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/MainColumn[0]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]/LayoutAnchorNode[0]", + "entityName" : null, + "entityKind" : "LayoutAnchorNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/MainColumn[0]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]", + "childIndex" : 0, + "depth" : 13, + "layer" : 13, + "computedX" : 33.077, + "computedY" : 381.894, + "placementX" : 33.077, + "placementY" : 381.894, + "placementWidth" : 6.395, + "placementHeight" : 6.395, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 6.395, + "contentHeight" : 6.395, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/MainColumn[0]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]/LayoutAnchorNode[0]/marker[0]", + "entityName" : "marker", + "entityKind" : "CanvasLayerNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/MainColumn[0]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]/LayoutAnchorNode[0]", + "childIndex" : 0, + "depth" : 14, + "layer" : 14, + "computedX" : 33.077, + "computedY" : 381.894, + "placementX" : 33.077, + "placementY" : 381.894, + "placementWidth" : 6.395, + "placementHeight" : 6.395, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 6.395, + "contentHeight" : 6.395, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/MainColumn[0]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]/LayoutAnchorNode[0]/marker[0]/SectionNode[0]", + "entityName" : null, + "entityKind" : "SectionNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/MainColumn[0]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]/LayoutAnchorNode[0]/marker[0]", + "childIndex" : 0, + "depth" : 15, + "layer" : 15, + "computedX" : 33.077, + "computedY" : 381.894, + "placementX" : 33.077, + "placementY" : 381.894, + "placementWidth" : 6.395, + "placementHeight" : 6.395, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 6.395, + "contentHeight" : 6.395, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/MainColumn[0]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]/LayoutAnchorNode[0]/marker[0]/SectionNode[0]/Marker_JuniorSoftwareEngineer[0]", + "entityName" : "Marker_JuniorSoftwareEngineer", + "entityKind" : "EllipseNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/MainColumn[0]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]/LayoutAnchorNode[0]/marker[0]/SectionNode[0]", + "childIndex" : 0, + "depth" : 16, + "layer" : 16, + "computedX" : 33.077, + "computedY" : 381.894, + "placementX" : 33.077, + "placementY" : 381.894, + "placementWidth" : 6.395, + "placementHeight" : 6.395, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 6.395, + "contentHeight" : 6.395, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/MainColumn[0]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]", + "entityName" : null, + "entityKind" : "SectionNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/MainColumn[0]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]", + "childIndex" : 1, + "depth" : 11, + "layer" : 11, + "computedX" : 60.748, + "computedY" : 321.477, + "placementX" : 60.748, + "placementY" : 321.477, + "placementWidth" : 313.91, + "placementHeight" : 66.811, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 313.91, + "contentHeight" : 66.811, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/MainColumn[0]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]/EntryHead_JuniorSoftwareEngineer[0]", + "entityName" : "EntryHead_JuniorSoftwareEngineer", + "entityKind" : "ShapeContainerNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/MainColumn[0]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]", + "childIndex" : 0, + "depth" : 12, + "layer" : 12, + "computedX" : 60.748, + "computedY" : 381.894, + "placementX" : 60.748, + "placementY" : 381.894, + "placementWidth" : 313.91, + "placementHeight" : 6.395, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 313.91, + "contentHeight" : 6.395, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/MainColumn[0]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]/EntryHead_JuniorSoftwareEngineer[0]/JobTitle_JuniorSoftwareEngineer[0]", + "entityName" : "JobTitle_JuniorSoftwareEngineer", + "entityKind" : "ParagraphNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/MainColumn[0]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]/EntryHead_JuniorSoftwareEngineer[0]", + "childIndex" : 0, + "depth" : 13, + "layer" : 13, + "computedX" : 60.748, + "computedY" : 379.762, + "placementX" : 60.748, + "placementY" : 379.762, + "placementWidth" : 108.95, + "placementHeight" : 11.626, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 108.95, + "contentHeight" : 11.626, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/MainColumn[0]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]/EntryHead_JuniorSoftwareEngineer[0]/JobDates_JuniorSoftwareEngineer[1]", + "entityName" : "JobDates_JuniorSoftwareEngineer", + "entityKind" : "ParagraphNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/MainColumn[0]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]/EntryHead_JuniorSoftwareEngineer[0]", + "childIndex" : 1, + "depth" : 13, + "layer" : 13, + "computedX" : 60.748, + "computedY" : 380.65, + "placementX" : 60.748, + "placementY" : 380.65, + "placementWidth" : 313.91, + "placementHeight" : 9.689, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 313.91, + "contentHeight" : 9.689, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/MainColumn[0]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]/Employer_JuniorSoftwareEngineer[1]", + "entityName" : "Employer_JuniorSoftwareEngineer", + "entityKind" : "ParagraphNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/MainColumn[0]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]", + "childIndex" : 1, + "depth" : 12, + "layer" : 12, + "computedX" : 60.748, + "computedY" : 363.608, + "placementX" : 60.748, + "placementY" : 363.608, + "placementWidth" : 89.341, + "placementHeight" : 8.284, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 89.341, + "contentHeight" : 8.284, + "margin" : { + "top" : 10.001, + "right" : 0.0, + "bottom" : 8.153, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/MainColumn[0]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]/Highlights_JuniorSoftwareEngineer[2]", + "entityName" : "Highlights_JuniorSoftwareEngineer", + "entityKind" : "ListNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/MainColumn[0]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]", + "childIndex" : 2, + "depth" : 12, + "layer" : 12, + "computedX" : 60.748, + "computedY" : 321.477, + "placementX" : 60.748, + "placementY" : 321.477, + "placementWidth" : 253.877, + "placementHeight" : 33.978, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 253.877, + "contentHeight" : 33.978, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/MainColumn[0]/SectionHeading_Projects[2]", + "entityName" : "SectionHeading_Projects", + "entityKind" : "ShapeContainerNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/MainColumn[0]", + "childIndex" : 2, + "depth" : 4, + "layer" : 4, + "computedX" : 31.682, + "computedY" : 285.277, + "placementX" : 31.682, + "placementY" : 285.277, + "placementWidth" : 342.976, + "placementHeight" : 12.595, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 342.976, + "contentHeight" : 12.595, + "margin" : { + "top" : 23.605, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/MainColumn[0]/SectionHeading_Projects[2]/HeadingDash_Projects[0]", + "entityName" : "HeadingDash_Projects", + "entityKind" : "LineNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/MainColumn[0]/SectionHeading_Projects[2]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 14.533, + "computedY" : 290.703, + "placementX" : 14.533, + "placementY" : 290.703, + "placementWidth" : 12.789, + "placementHeight" : 1.744, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 12.789, + "contentHeight" : 1.744, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/MainColumn[0]/SectionHeading_Projects[2]/SectionTitle_Projects[1]", + "entityName" : "SectionTitle_Projects", + "entityKind" : "ParagraphNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/MainColumn[0]/SectionHeading_Projects[2]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 36.623, + "computedY" : 285.762, + "placementX" : 36.623, + "placementY" : 285.762, + "placementWidth" : 58.622, + "placementHeight" : 11.626, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 58.622, + "contentHeight" : 11.626, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/MainColumn[0]/SectionHeading_Projects[2]/HeadingTail_Projects[2]", + "entityName" : "HeadingTail_Projects", + "entityKind" : "LineNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/MainColumn[0]/SectionHeading_Projects[2]", + "childIndex" : 2, + "depth" : 5, + "layer" : 5, + "computedX" : 105.848, + "computedY" : 291.075, + "placementX" : 105.848, + "placementY" : 291.075, + "placementWidth" : 268.81, + "placementHeight" : 1.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 268.81, + "contentHeight" : 1.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/MainColumn[0]/ProjectsBand[3]", + "entityName" : "ProjectsBand", + "entityKind" : "LayerStackNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/MainColumn[0]", + "childIndex" : 3, + "depth" : 4, + "layer" : 4, + "computedX" : 31.682, + "computedY" : 218.426, + "placementX" : 31.682, + "placementY" : 218.426, + "placementWidth" : 342.976, + "placementHeight" : 52.675, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 342.976, + "contentHeight" : 52.675, + "margin" : { + "top" : 14.176, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/MainColumn[0]/ProjectsBand[3]/ProjectCard_ECommercePlatform[0]", + "entityName" : "ProjectCard_ECommercePlatform", + "entityKind" : "SectionNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/MainColumn[0]/ProjectsBand[3]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 31.682, + "computedY" : 218.426, + "placementX" : 31.682, + "placementY" : 218.426, + "placementWidth" : 161.798, + "placementHeight" : 52.675, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 161.798, + "contentHeight" : 52.675, + "margin" : { + "top" : 0.0, + "right" : 177.301, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 33.135 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/MainColumn[0]/ProjectsBand[3]/ProjectCard_ECommercePlatform[0]/ProjectTitle_ECommercePlatform[0]", + "entityName" : "ProjectTitle_ECommercePlatform", + "entityKind" : "ParagraphNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/MainColumn[0]/ProjectsBand[3]/ProjectCard_ECommercePlatform[0]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 64.818, + "computedY" : 261.413, + "placementX" : 64.818, + "placementY" : 261.413, + "placementWidth" : 80.094, + "placementHeight" : 9.689, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 80.094, + "contentHeight" : 9.689, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 4.223, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/MainColumn[0]/ProjectsBand[3]/ProjectCard_ECommercePlatform[0]/ProjectStack_ECommercePlatform[1]", + "entityName" : "ProjectStack_ECommercePlatform", + "entityKind" : "ParagraphNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/MainColumn[0]/ProjectsBand[3]/ProjectCard_ECommercePlatform[0]", + "childIndex" : 1, + "depth" : 6, + "layer" : 6, + "computedX" : 64.818, + "computedY" : 248.906, + "placementX" : 64.818, + "placementY" : 248.906, + "placementWidth" : 123.837, + "placementHeight" : 8.284, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 123.837, + "contentHeight" : 8.284, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 2.995, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/MainColumn[0]/ProjectsBand[3]/ProjectCard_ECommercePlatform[0]/ProjectBody_ECommercePlatform[2]", + "entityName" : "ProjectBody_ECommercePlatform", + "entityKind" : "ParagraphNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/MainColumn[0]/ProjectsBand[3]/ProjectCard_ECommercePlatform[0]", + "childIndex" : 2, + "depth" : 6, + "layer" : 6, + "computedX" : 64.818, + "computedY" : 218.426, + "placementX" : 64.818, + "placementY" : 218.426, + "placementWidth" : 128.662, + "placementHeight" : 27.484, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 128.662, + "contentHeight" : 27.484, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/MainColumn[0]/ProjectsBand[3]/ProjectCard_TaskManagementAPI[1]", + "entityName" : "ProjectCard_TaskManagementAPI", + "entityKind" : "SectionNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/MainColumn[0]/ProjectsBand[3]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 203.17, + "computedY" : 218.426, + "placementX" : 203.17, + "placementY" : 218.426, + "placementWidth" : 164.738, + "placementHeight" : 52.675, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 164.738, + "contentHeight" : 52.675, + "margin" : { + "top" : 0.0, + "right" : 5.813, + "bottom" : 0.0, + "left" : 171.488 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 41.274 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/MainColumn[0]/ProjectsBand[3]/ProjectCard_TaskManagementAPI[1]/ProjectTitle_TaskManagementAPI[0]", + "entityName" : "ProjectTitle_TaskManagementAPI", + "entityKind" : "ParagraphNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/MainColumn[0]/ProjectsBand[3]/ProjectCard_TaskManagementAPI[1]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 244.444, + "computedY" : 261.413, + "placementX" : 244.444, + "placementY" : 261.413, + "placementWidth" : 81.345, + "placementHeight" : 9.689, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 81.345, + "contentHeight" : 9.689, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 4.223, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/MainColumn[0]/ProjectsBand[3]/ProjectCard_TaskManagementAPI[1]/ProjectStack_TaskManagementAPI[1]", + "entityName" : "ProjectStack_TaskManagementAPI", + "entityKind" : "ParagraphNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/MainColumn[0]/ProjectsBand[3]/ProjectCard_TaskManagementAPI[1]", + "childIndex" : 1, + "depth" : 6, + "layer" : 6, + "computedX" : 244.444, + "computedY" : 248.906, + "placementX" : 244.444, + "placementY" : 248.906, + "placementWidth" : 105.847, + "placementHeight" : 8.284, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 105.847, + "contentHeight" : 8.284, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 2.995, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/MainColumn[0]/ProjectsBand[3]/ProjectCard_TaskManagementAPI[1]/ProjectBody_TaskManagementAPI[2]", + "entityName" : "ProjectBody_TaskManagementAPI", + "entityKind" : "ParagraphNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/MainColumn[0]/ProjectsBand[3]/ProjectCard_TaskManagementAPI[1]", + "childIndex" : 2, + "depth" : 6, + "layer" : 6, + "computedX" : 244.444, + "computedY" : 218.426, + "placementX" : 244.444, + "placementY" : 218.426, + "placementWidth" : 123.464, + "placementHeight" : 27.484, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 123.464, + "contentHeight" : 27.484, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/MainColumn[0]/ProjectsBand[3]/ProjectsGlyph_0[2]", + "entityName" : "ProjectsGlyph_0", + "entityKind" : "SectionNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/MainColumn[0]/ProjectsBand[3]", + "childIndex" : 2, + "depth" : 5, + "layer" : 5, + "computedX" : 29.357, + "computedY" : 243.779, + "placementX" : 29.357, + "placementY" : 243.779, + "placementWidth" : 27.322, + "placementHeight" : 27.322, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 27.322, + "contentHeight" : 27.322, + "margin" : { + "top" : 0.0, + "right" : 171.488, + "bottom" : 0.0, + "left" : -2.325 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/MainColumn[0]/ProjectsBand[3]/ProjectsGlyph_0[2]/ProjectPlate_ECommercePlatform[0]", + "entityName" : "ProjectPlate_ECommercePlatform", + "entityKind" : "ShapeContainerNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/MainColumn[0]/ProjectsBand[3]/ProjectsGlyph_0[2]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 29.357, + "computedY" : 243.779, + "placementX" : 29.357, + "placementY" : 243.779, + "placementWidth" : 27.322, + "placementHeight" : 27.322, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 27.322, + "contentHeight" : 27.322, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/MainColumn[0]/ProjectsBand[3]/ProjectsGlyph_0[2]/ProjectPlate_ECommercePlatform[0]/ProjectPlate_ECommercePlatformGlyph[0]", + "entityName" : "ProjectPlate_ECommercePlatformGlyph", + "entityKind" : "ImageNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/MainColumn[0]/ProjectsBand[3]/ProjectsGlyph_0[2]/ProjectPlate_ECommercePlatform[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 35.468, + "computedY" : 249.89, + "placementX" : 35.468, + "placementY" : 249.89, + "placementWidth" : 15.1, + "placementHeight" : 15.1, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 15.1, + "contentHeight" : 15.1, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/MainColumn[0]/ProjectsBand[3]/ProjectsGlyph_1[3]", + "entityName" : "ProjectsGlyph_1", + "entityKind" : "SectionNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/MainColumn[0]/ProjectsBand[3]", + "childIndex" : 3, + "depth" : 5, + "layer" : 5, + "computedX" : 208.984, + "computedY" : 243.779, + "placementX" : 208.984, + "placementY" : 243.779, + "placementWidth" : 27.322, + "placementHeight" : 27.322, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 27.322, + "contentHeight" : 27.322, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 177.301 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/MainColumn[0]/ProjectsBand[3]/ProjectsGlyph_1[3]/ProjectPlate_TaskManagementAPI[0]", + "entityName" : "ProjectPlate_TaskManagementAPI", + "entityKind" : "ShapeContainerNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/MainColumn[0]/ProjectsBand[3]/ProjectsGlyph_1[3]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 208.984, + "computedY" : 243.779, + "placementX" : 208.984, + "placementY" : 243.779, + "placementWidth" : 27.322, + "placementHeight" : 27.322, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 27.322, + "contentHeight" : 27.322, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/MainColumn[0]/ProjectsBand[3]/ProjectsGlyph_1[3]/ProjectPlate_TaskManagementAPI[0]/ProjectPlate_TaskManagementAPIGlyph[0]", + "entityName" : "ProjectPlate_TaskManagementAPIGlyph", + "entityKind" : "ImageNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/MainColumn[0]/ProjectsBand[3]/ProjectsGlyph_1[3]/ProjectPlate_TaskManagementAPI[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 215.095, + "computedY" : 249.89, + "placementX" : 215.095, + "placementY" : 249.89, + "placementWidth" : 15.1, + "placementHeight" : 15.1, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 15.1, + "contentHeight" : 15.1, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/MainColumn[0]/ProjectsBand[3]/ProjectsSeparator_1[4]", + "entityName" : "ProjectsSeparator_1", + "entityKind" : "SectionNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/MainColumn[0]/ProjectsBand[3]", + "childIndex" : 4, + "depth" : 5, + "layer" : 5, + "computedX" : 203.17, + "computedY" : 222.962, + "placementX" : 203.17, + "placementY" : 222.962, + "placementWidth" : 0.698, + "placementHeight" : 48.139, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 0.698, + "contentHeight" : 48.139, + "margin" : { + "top" : 0.0, + "right" : 170.791, + "bottom" : 0.0, + "left" : 171.488 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/MainColumn[0]/ProjectsBand[3]/ProjectsSeparator_1[4]/ProjectsSeparatorLine_1[0]", + "entityName" : "ProjectsSeparatorLine_1", + "entityKind" : "LineNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/MainColumn[0]/ProjectsBand[3]/ProjectsSeparator_1[4]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 203.17, + "computedY" : 222.962, + "placementX" : 203.17, + "placementY" : 222.962, + "placementWidth" : 1.0, + "placementHeight" : 48.139, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 1.0, + "contentHeight" : 48.139, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]", + "entityName" : "AsideColumn", + "entityKind" : "SectionNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]", + "childIndex" : 1, + "depth" : 3, + "layer" : 3, + "computedX" : 393.834, + "computedY" : 189.371, + "placementX" : 393.834, + "placementY" : 189.371, + "placementWidth" : 169.76, + "placementHeight" : 480.618, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 169.76, + "contentHeight" : 480.618, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 19.175 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/SectionHeading_Education[0]", + "entityName" : "SectionHeading_Education", + "entityKind" : "ShapeContainerNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 413.009, + "computedY" : 657.394, + "placementX" : 413.009, + "placementY" : 657.394, + "placementWidth" : 150.584, + "placementHeight" : 12.595, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 150.584, + "contentHeight" : 12.595, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/SectionHeading_Education[0]/HeadingDash_Education[0]", + "entityName" : "HeadingDash_Education", + "entityKind" : "LineNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/SectionHeading_Education[0]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 408.367, + "computedY" : 662.819, + "placementX" : 408.367, + "placementY" : 662.819, + "placementWidth" : 12.789, + "placementHeight" : 1.744, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 12.789, + "contentHeight" : 1.744, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/SectionHeading_Education[0]/SectionTitle_Education[1]", + "entityName" : "SectionTitle_Education", + "entityKind" : "ParagraphNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/SectionHeading_Education[0]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 430.457, + "computedY" : 657.878, + "placementX" : 430.457, + "placementY" : 657.878, + "placementWidth" : 70.23, + "placementHeight" : 11.626, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 70.23, + "contentHeight" : 11.626, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/Degree_MSinComputerScience[1]", + "entityName" : "Degree_MSinComputerScience", + "entityKind" : "ParagraphNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 413.009, + "computedY" : 633.038, + "placementX" : 413.009, + "placementY" : 633.038, + "placementWidth" : 92.059, + "placementHeight" : 9.689, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 92.059, + "contentHeight" : 9.689, + "margin" : { + "top" : 14.667, + "right" : 0.0, + "bottom" : 3.732, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/Institution_MSinComputerScience[2]", + "entityName" : "Institution_MSinComputerScience", + "entityKind" : "ParagraphNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]", + "childIndex" : 2, + "depth" : 4, + "layer" : 4, + "computedX" : 413.009, + "computedY" : 621.022, + "placementX" : 413.009, + "placementY" : 621.022, + "placementWidth" : 63.116, + "placementHeight" : 8.284, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 63.116, + "contentHeight" : 8.284, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 3.978, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/Years_MSinComputerScience[3]", + "entityName" : "Years_MSinComputerScience", + "entityKind" : "ParagraphNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]", + "childIndex" : 3, + "depth" : 4, + "layer" : 4, + "computedX" : 413.009, + "computedY" : 608.761, + "placementX" : 413.009, + "placementY" : 608.761, + "placementWidth" : 95.175, + "placementHeight" : 8.284, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 95.175, + "contentHeight" : 8.284, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/EducationStub_MSinComputerScience[4]", + "entityName" : "EducationStub_MSinComputerScience", + "entityKind" : "LineNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]", + "childIndex" : 4, + "depth" : 4, + "layer" : 4, + "computedX" : 413.009, + "computedY" : 599.223, + "placementX" : 413.009, + "placementY" : 599.223, + "placementWidth" : 15.696, + "placementHeight" : 1.163, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 15.696, + "contentHeight" : 1.163, + "margin" : { + "top" : 8.375, + "right" : 0.0, + "bottom" : 9.824, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/Degree_BSinComputerScience[5]", + "entityName" : "Degree_BSinComputerScience", + "entityKind" : "ParagraphNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]", + "childIndex" : 5, + "depth" : 4, + "layer" : 4, + "computedX" : 413.009, + "computedY" : 579.71, + "placementX" : 413.009, + "placementY" : 579.71, + "placementWidth" : 89.75, + "placementHeight" : 9.689, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 89.75, + "contentHeight" : 9.689, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 3.732, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/Institution_BSinComputerScience[6]", + "entityName" : "Institution_BSinComputerScience", + "entityKind" : "ParagraphNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]", + "childIndex" : 6, + "depth" : 4, + "layer" : 4, + "computedX" : 413.009, + "computedY" : 567.695, + "placementX" : 413.009, + "placementY" : 567.695, + "placementWidth" : 109.561, + "placementHeight" : 8.284, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 109.561, + "contentHeight" : 8.284, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 3.978, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/Years_BSinComputerScience[7]", + "entityName" : "Years_BSinComputerScience", + "entityKind" : "ParagraphNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]", + "childIndex" : 7, + "depth" : 4, + "layer" : 4, + "computedX" : 413.009, + "computedY" : 555.433, + "placementX" : 413.009, + "placementY" : 555.433, + "placementWidth" : 100.766, + "placementHeight" : 8.284, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 100.766, + "contentHeight" : 8.284, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/SectionHeading_Skills[8]", + "entityName" : "SectionHeading_Skills", + "entityKind" : "ShapeContainerNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]", + "childIndex" : 8, + "depth" : 4, + "layer" : 4, + "computedX" : 413.009, + "computedY" : 525.864, + "placementX" : 413.009, + "placementY" : 525.864, + "placementWidth" : 150.584, + "placementHeight" : 12.595, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 150.584, + "contentHeight" : 12.595, + "margin" : { + "top" : 16.973, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/SectionHeading_Skills[8]/HeadingDash_Skills[0]", + "entityName" : "HeadingDash_Skills", + "entityKind" : "LineNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/SectionHeading_Skills[8]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 408.367, + "computedY" : 531.29, + "placementX" : 408.367, + "placementY" : 531.29, + "placementWidth" : 12.789, + "placementHeight" : 1.744, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 12.789, + "contentHeight" : 1.744, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/SectionHeading_Skills[8]/SectionTitle_Skills[1]", + "entityName" : "SectionTitle_Skills", + "entityKind" : "ParagraphNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/SectionHeading_Skills[8]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 430.457, + "computedY" : 526.349, + "placementX" : 430.457, + "placementY" : 526.349, + "placementWidth" : 38.052, + "placementHeight" : 11.626, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 38.052, + "contentHeight" : 11.626, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/SkillCaption_Languages[9]", + "entityName" : "SkillCaption_Languages", + "entityKind" : "ParagraphNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]", + "childIndex" : 9, + "depth" : 4, + "layer" : 4, + "computedX" : 413.009, + "computedY" : 506.519, + "placementX" : 413.009, + "placementY" : 506.519, + "placementWidth" : 50.545, + "placementHeight" : 8.284, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 50.545, + "contentHeight" : 8.284, + "margin" : { + "top" : 11.061, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/SkillGroup_Languages[10]", + "entityName" : "SkillGroup_Languages", + "entityKind" : "SectionNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]", + "childIndex" : 10, + "depth" : 4, + "layer" : 4, + "computedX" : 413.009, + "computedY" : 458.96, + "placementX" : 413.009, + "placementY" : 458.96, + "placementWidth" : 150.584, + "placementHeight" : 40.658, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 150.584, + "contentHeight" : 40.658, + "margin" : { + "top" : 6.901, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/SkillGroup_Languages[10]/SkillRow_Java[0]", + "entityName" : "SkillRow_Java", + "entityKind" : "ShapeContainerNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/SkillGroup_Languages[10]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 413.009, + "computedY" : 490.644, + "placementX" : 413.009, + "placementY" : 490.644, + "placementWidth" : 150.584, + "placementHeight" : 8.974, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 150.584, + "contentHeight" : 8.974, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/SkillGroup_Languages[10]/SkillRow_Java[0]/SkillLabel_Java[0]", + "entityName" : "SkillLabel_Java", + "entityKind" : "ParagraphNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/SkillGroup_Languages[10]/SkillRow_Java[0]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 413.009, + "computedY" : 490.989, + "placementX" : 413.009, + "placementY" : 490.989, + "placementWidth" : 13.344, + "placementHeight" : 8.284, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 13.344, + "contentHeight" : 8.284, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/SkillGroup_Languages[10]/SkillRow_Java[0]/SkillTrack_Java[1]", + "entityName" : "SkillTrack_Java", + "entityKind" : "ShapeContainerNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/SkillGroup_Languages[10]/SkillRow_Java[0]", + "childIndex" : 1, + "depth" : 6, + "layer" : 6, + "computedX" : 496.16, + "computedY" : 494.259, + "placementX" : 496.16, + "placementY" : 494.259, + "placementWidth" : 67.434, + "placementHeight" : 1.744, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 67.434, + "contentHeight" : 1.744, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/SkillGroup_Languages[10]/SkillRow_Java[0]/SkillTrack_Java[1]/SkillFill_Java[0]", + "entityName" : "SkillFill_Java", + "entityKind" : "ShapeNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/SkillGroup_Languages[10]/SkillRow_Java[0]/SkillTrack_Java[1]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 496.16, + "computedY" : 494.259, + "placementX" : 496.16, + "placementY" : 494.259, + "placementWidth" : 50.575, + "placementHeight" : 1.744, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 50.575, + "contentHeight" : 1.744, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/SkillGroup_Languages[10]/SkillRow_Python[1]", + "entityName" : "SkillRow_Python", + "entityKind" : "ShapeContainerNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/SkillGroup_Languages[10]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 413.009, + "computedY" : 480.083, + "placementX" : 413.009, + "placementY" : 480.083, + "placementWidth" : 150.584, + "placementHeight" : 8.974, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 150.584, + "contentHeight" : 8.974, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/SkillGroup_Languages[10]/SkillRow_Python[1]/SkillLabel_Python[0]", + "entityName" : "SkillLabel_Python", + "entityKind" : "ParagraphNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/SkillGroup_Languages[10]/SkillRow_Python[1]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 413.009, + "computedY" : 480.428, + "placementX" : 413.009, + "placementY" : 480.428, + "placementWidth" : 21.807, + "placementHeight" : 8.284, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 21.807, + "contentHeight" : 8.284, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/SkillGroup_Languages[10]/SkillRow_Python[1]/SkillTrack_Python[1]", + "entityName" : "SkillTrack_Python", + "entityKind" : "ShapeContainerNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/SkillGroup_Languages[10]/SkillRow_Python[1]", + "childIndex" : 1, + "depth" : 6, + "layer" : 6, + "computedX" : 496.16, + "computedY" : 483.698, + "placementX" : 496.16, + "placementY" : 483.698, + "placementWidth" : 67.434, + "placementHeight" : 1.744, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 67.434, + "contentHeight" : 1.744, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/SkillGroup_Languages[10]/SkillRow_Python[1]/SkillTrack_Python[1]/SkillFill_Python[0]", + "entityName" : "SkillFill_Python", + "entityKind" : "ShapeNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/SkillGroup_Languages[10]/SkillRow_Python[1]/SkillTrack_Python[1]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 496.16, + "computedY" : 483.698, + "placementX" : 496.16, + "placementY" : 483.698, + "placementWidth" : 39.111, + "placementHeight" : 1.744, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 39.111, + "contentHeight" : 1.744, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/SkillGroup_Languages[10]/SkillRow_JavaScriptTypeScript[2]", + "entityName" : "SkillRow_JavaScriptTypeScript", + "entityKind" : "ShapeContainerNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/SkillGroup_Languages[10]", + "childIndex" : 2, + "depth" : 5, + "layer" : 5, + "computedX" : 413.009, + "computedY" : 469.522, + "placementX" : 413.009, + "placementY" : 469.522, + "placementWidth" : 150.584, + "placementHeight" : 8.974, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 150.584, + "contentHeight" : 8.974, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/SkillGroup_Languages[10]/SkillRow_JavaScriptTypeScript[2]/SkillLabel_JavaScriptTypeScript[0]", + "entityName" : "SkillLabel_JavaScriptTypeScript", + "entityKind" : "ParagraphNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/SkillGroup_Languages[10]/SkillRow_JavaScriptTypeScript[2]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 413.009, + "computedY" : 469.867, + "placementX" : 413.009, + "placementY" : 469.867, + "placementWidth" : 70.295, + "placementHeight" : 8.284, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 70.295, + "contentHeight" : 8.284, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/SkillGroup_Languages[10]/SkillRow_JavaScriptTypeScript[2]/SkillTrack_JavaScriptTypeScript[1]", + "entityName" : "SkillTrack_JavaScriptTypeScript", + "entityKind" : "ShapeContainerNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/SkillGroup_Languages[10]/SkillRow_JavaScriptTypeScript[2]", + "childIndex" : 1, + "depth" : 6, + "layer" : 6, + "computedX" : 496.16, + "computedY" : 473.137, + "placementX" : 496.16, + "placementY" : 473.137, + "placementWidth" : 67.434, + "placementHeight" : 1.744, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 67.434, + "contentHeight" : 1.744, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/SkillGroup_Languages[10]/SkillRow_JavaScriptTypeScript[2]/SkillTrack_JavaScriptTypeScript[1]/SkillFill_JavaScriptTypeScript[0]", + "entityName" : "SkillFill_JavaScriptTypeScript", + "entityKind" : "ShapeNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/SkillGroup_Languages[10]/SkillRow_JavaScriptTypeScript[2]/SkillTrack_JavaScriptTypeScript[1]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 496.16, + "computedY" : 473.137, + "placementX" : 496.16, + "placementY" : 473.137, + "placementWidth" : 32.368, + "placementHeight" : 1.744, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 32.368, + "contentHeight" : 1.744, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/SkillGroup_Languages[10]/SkillRow_SQL[3]", + "entityName" : "SkillRow_SQL", + "entityKind" : "ShapeContainerNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/SkillGroup_Languages[10]", + "childIndex" : 3, + "depth" : 5, + "layer" : 5, + "computedX" : 413.009, + "computedY" : 458.96, + "placementX" : 413.009, + "placementY" : 458.96, + "placementWidth" : 150.584, + "placementHeight" : 8.974, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 150.584, + "contentHeight" : 8.974, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/SkillGroup_Languages[10]/SkillRow_SQL[3]/SkillLabel_SQL[0]", + "entityName" : "SkillLabel_SQL", + "entityKind" : "ParagraphNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/SkillGroup_Languages[10]/SkillRow_SQL[3]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 413.009, + "computedY" : 459.306, + "placementX" : 413.009, + "placementY" : 459.306, + "placementWidth" : 12.826, + "placementHeight" : 8.284, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 12.826, + "contentHeight" : 8.284, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/SkillGroup_Languages[10]/SkillRow_SQL[3]/SkillTrack_SQL[1]", + "entityName" : "SkillTrack_SQL", + "entityKind" : "ShapeContainerNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/SkillGroup_Languages[10]/SkillRow_SQL[3]", + "childIndex" : 1, + "depth" : 6, + "layer" : 6, + "computedX" : 496.16, + "computedY" : 462.576, + "placementX" : 496.16, + "placementY" : 462.576, + "placementWidth" : 67.434, + "placementHeight" : 1.744, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 67.434, + "contentHeight" : 1.744, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/SkillGroup_Languages[10]/SkillRow_SQL[3]/SkillTrack_SQL[1]/SkillFill_SQL[0]", + "entityName" : "SkillFill_SQL", + "entityKind" : "ShapeNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/SkillGroup_Languages[10]/SkillRow_SQL[3]/SkillTrack_SQL[1]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 496.16, + "computedY" : 462.576, + "placementX" : 496.16, + "placementY" : 462.576, + "placementWidth" : 50.575, + "placementHeight" : 1.744, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 50.575, + "contentHeight" : 1.744, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/SkillCaption_FrameworksLibraries[11]", + "entityName" : "SkillCaption_FrameworksLibraries", + "entityKind" : "ParagraphNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]", + "childIndex" : 11, + "depth" : 4, + "layer" : 4, + "computedX" : 413.009, + "computedY" : 438.072, + "placementX" : 413.009, + "placementY" : 438.072, + "placementWidth" : 115.415, + "placementHeight" : 8.284, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 115.415, + "contentHeight" : 8.284, + "margin" : { + "top" : 12.605, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/SkillGroup_FrameworksLibraries[12]", + "entityName" : "SkillGroup_FrameworksLibraries", + "entityKind" : "SectionNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]", + "childIndex" : 12, + "depth" : 4, + "layer" : 4, + "computedX" : 413.009, + "computedY" : 379.952, + "placementX" : 413.009, + "placementY" : 379.952, + "placementWidth" : 150.584, + "placementHeight" : 51.219, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 150.584, + "contentHeight" : 51.219, + "margin" : { + "top" : 6.901, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/SkillGroup_FrameworksLibraries[12]/SkillRow_SpringBoot[0]", + "entityName" : "SkillRow_SpringBoot", + "entityKind" : "ShapeContainerNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/SkillGroup_FrameworksLibraries[12]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 413.009, + "computedY" : 422.197, + "placementX" : 413.009, + "placementY" : 422.197, + "placementWidth" : 150.584, + "placementHeight" : 8.974, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 150.584, + "contentHeight" : 8.974, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/SkillGroup_FrameworksLibraries[12]/SkillRow_SpringBoot[0]/SkillLabel_SpringBoot[0]", + "entityName" : "SkillLabel_SpringBoot", + "entityKind" : "ParagraphNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/SkillGroup_FrameworksLibraries[12]/SkillRow_SpringBoot[0]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 413.009, + "computedY" : 422.542, + "placementX" : 413.009, + "placementY" : 422.542, + "placementWidth" : 35.772, + "placementHeight" : 8.284, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 35.772, + "contentHeight" : 8.284, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/SkillGroup_FrameworksLibraries[12]/SkillRow_SpringBoot[0]/SkillTrack_SpringBoot[1]", + "entityName" : "SkillTrack_SpringBoot", + "entityKind" : "ShapeContainerNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/SkillGroup_FrameworksLibraries[12]/SkillRow_SpringBoot[0]", + "childIndex" : 1, + "depth" : 6, + "layer" : 6, + "computedX" : 496.16, + "computedY" : 425.812, + "placementX" : 496.16, + "placementY" : 425.812, + "placementWidth" : 67.434, + "placementHeight" : 1.744, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 67.434, + "contentHeight" : 1.744, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/SkillGroup_FrameworksLibraries[12]/SkillRow_SpringBoot[0]/SkillTrack_SpringBoot[1]/SkillFill_SpringBoot[0]", + "entityName" : "SkillFill_SpringBoot", + "entityKind" : "ShapeNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/SkillGroup_FrameworksLibraries[12]/SkillRow_SpringBoot[0]/SkillTrack_SpringBoot[1]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 496.16, + "computedY" : 425.812, + "placementX" : 496.16, + "placementY" : 425.812, + "placementWidth" : 50.575, + "placementHeight" : 1.744, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 50.575, + "contentHeight" : 1.744, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/SkillGroup_FrameworksLibraries[12]/SkillRow_Hibernate[1]", + "entityName" : "SkillRow_Hibernate", + "entityKind" : "ShapeContainerNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/SkillGroup_FrameworksLibraries[12]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 413.009, + "computedY" : 411.636, + "placementX" : 413.009, + "placementY" : 411.636, + "placementWidth" : 150.584, + "placementHeight" : 8.974, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 150.584, + "contentHeight" : 8.974, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/SkillGroup_FrameworksLibraries[12]/SkillRow_Hibernate[1]/SkillLabel_Hibernate[0]", + "entityName" : "SkillLabel_Hibernate", + "entityKind" : "ParagraphNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/SkillGroup_FrameworksLibraries[12]/SkillRow_Hibernate[1]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 413.009, + "computedY" : 411.981, + "placementX" : 413.009, + "placementY" : 411.981, + "placementWidth" : 30.36, + "placementHeight" : 8.284, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 30.36, + "contentHeight" : 8.284, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/SkillGroup_FrameworksLibraries[12]/SkillRow_Hibernate[1]/SkillTrack_Hibernate[1]", + "entityName" : "SkillTrack_Hibernate", + "entityKind" : "ShapeContainerNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/SkillGroup_FrameworksLibraries[12]/SkillRow_Hibernate[1]", + "childIndex" : 1, + "depth" : 6, + "layer" : 6, + "computedX" : 496.16, + "computedY" : 415.251, + "placementX" : 496.16, + "placementY" : 415.251, + "placementWidth" : 67.434, + "placementHeight" : 1.744, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 67.434, + "contentHeight" : 1.744, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/SkillGroup_FrameworksLibraries[12]/SkillRow_Hibernate[1]/SkillTrack_Hibernate[1]/SkillFill_Hibernate[0]", + "entityName" : "SkillFill_Hibernate", + "entityKind" : "ShapeNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/SkillGroup_FrameworksLibraries[12]/SkillRow_Hibernate[1]/SkillTrack_Hibernate[1]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 496.16, + "computedY" : 415.251, + "placementX" : 496.16, + "placementY" : 415.251, + "placementWidth" : 36.414, + "placementHeight" : 1.744, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 36.414, + "contentHeight" : 1.744, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/SkillGroup_FrameworksLibraries[12]/SkillRow_React[2]", + "entityName" : "SkillRow_React", + "entityKind" : "ShapeContainerNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/SkillGroup_FrameworksLibraries[12]", + "childIndex" : 2, + "depth" : 5, + "layer" : 5, + "computedX" : 413.009, + "computedY" : 401.074, + "placementX" : 413.009, + "placementY" : 401.074, + "placementWidth" : 150.584, + "placementHeight" : 8.974, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 150.584, + "contentHeight" : 8.974, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/SkillGroup_FrameworksLibraries[12]/SkillRow_React[2]/SkillLabel_React[0]", + "entityName" : "SkillLabel_React", + "entityKind" : "ParagraphNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/SkillGroup_FrameworksLibraries[12]/SkillRow_React[2]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 413.009, + "computedY" : 401.42, + "placementX" : 413.009, + "placementY" : 401.42, + "placementWidth" : 17.182, + "placementHeight" : 8.284, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 17.182, + "contentHeight" : 8.284, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/SkillGroup_FrameworksLibraries[12]/SkillRow_React[2]/SkillTrack_React[1]", + "entityName" : "SkillTrack_React", + "entityKind" : "ShapeContainerNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/SkillGroup_FrameworksLibraries[12]/SkillRow_React[2]", + "childIndex" : 1, + "depth" : 6, + "layer" : 6, + "computedX" : 496.16, + "computedY" : 404.69, + "placementX" : 496.16, + "placementY" : 404.69, + "placementWidth" : 67.434, + "placementHeight" : 1.744, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 67.434, + "contentHeight" : 1.744, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/SkillGroup_FrameworksLibraries[12]/SkillRow_React[2]/SkillTrack_React[1]/SkillFill_React[0]", + "entityName" : "SkillFill_React", + "entityKind" : "ShapeNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/SkillGroup_FrameworksLibraries[12]/SkillRow_React[2]/SkillTrack_React[1]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 496.16, + "computedY" : 404.69, + "placementX" : 496.16, + "placementY" : 404.69, + "placementWidth" : 50.575, + "placementHeight" : 1.744, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 50.575, + "contentHeight" : 1.744, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/SkillGroup_FrameworksLibraries[12]/SkillRow_Nodejs[3]", + "entityName" : "SkillRow_Nodejs", + "entityKind" : "ShapeContainerNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/SkillGroup_FrameworksLibraries[12]", + "childIndex" : 3, + "depth" : 5, + "layer" : 5, + "computedX" : 413.009, + "computedY" : 390.513, + "placementX" : 413.009, + "placementY" : 390.513, + "placementWidth" : 150.584, + "placementHeight" : 8.974, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 150.584, + "contentHeight" : 8.974, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/SkillGroup_FrameworksLibraries[12]/SkillRow_Nodejs[3]/SkillLabel_Nodejs[0]", + "entityName" : "SkillLabel_Nodejs", + "entityKind" : "ParagraphNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/SkillGroup_FrameworksLibraries[12]/SkillRow_Nodejs[3]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 413.009, + "computedY" : 390.858, + "placementX" : 413.009, + "placementY" : 390.858, + "placementWidth" : 22.974, + "placementHeight" : 8.284, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 22.974, + "contentHeight" : 8.284, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/SkillGroup_FrameworksLibraries[12]/SkillRow_Nodejs[3]/SkillTrack_Nodejs[1]", + "entityName" : "SkillTrack_Nodejs", + "entityKind" : "ShapeContainerNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/SkillGroup_FrameworksLibraries[12]/SkillRow_Nodejs[3]", + "childIndex" : 1, + "depth" : 6, + "layer" : 6, + "computedX" : 496.16, + "computedY" : 394.128, + "placementX" : 496.16, + "placementY" : 394.128, + "placementWidth" : 67.434, + "placementHeight" : 1.744, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 67.434, + "contentHeight" : 1.744, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/SkillGroup_FrameworksLibraries[12]/SkillRow_Nodejs[3]/SkillTrack_Nodejs[1]/SkillFill_Nodejs[0]", + "entityName" : "SkillFill_Nodejs", + "entityKind" : "ShapeNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/SkillGroup_FrameworksLibraries[12]/SkillRow_Nodejs[3]/SkillTrack_Nodejs[1]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 496.16, + "computedY" : 394.128, + "placementX" : 496.16, + "placementY" : 394.128, + "placementWidth" : 26.299, + "placementHeight" : 1.744, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 26.299, + "contentHeight" : 1.744, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/SkillGroup_FrameworksLibraries[12]/SkillRow_Express[4]", + "entityName" : "SkillRow_Express", + "entityKind" : "ShapeContainerNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/SkillGroup_FrameworksLibraries[12]", + "childIndex" : 4, + "depth" : 5, + "layer" : 5, + "computedX" : 413.009, + "computedY" : 379.952, + "placementX" : 413.009, + "placementY" : 379.952, + "placementWidth" : 150.584, + "placementHeight" : 8.974, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 150.584, + "contentHeight" : 8.974, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/SkillGroup_FrameworksLibraries[12]/SkillRow_Express[4]/SkillLabel_Express[0]", + "entityName" : "SkillLabel_Express", + "entityKind" : "ParagraphNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/SkillGroup_FrameworksLibraries[12]/SkillRow_Express[4]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 413.009, + "computedY" : 380.297, + "placementX" : 413.009, + "placementY" : 380.297, + "placementWidth" : 23.436, + "placementHeight" : 8.284, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 23.436, + "contentHeight" : 8.284, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/SkillGroup_FrameworksLibraries[12]/SkillRow_Express[4]/SkillTrack_Express[1]", + "entityName" : "SkillTrack_Express", + "entityKind" : "ShapeContainerNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/SkillGroup_FrameworksLibraries[12]/SkillRow_Express[4]", + "childIndex" : 1, + "depth" : 6, + "layer" : 6, + "computedX" : 496.16, + "computedY" : 383.567, + "placementX" : 496.16, + "placementY" : 383.567, + "placementWidth" : 67.434, + "placementHeight" : 1.744, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 67.434, + "contentHeight" : 1.744, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/SkillGroup_FrameworksLibraries[12]/SkillRow_Express[4]/SkillTrack_Express[1]/SkillFill_Express[0]", + "entityName" : "SkillFill_Express", + "entityKind" : "ShapeNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/SkillGroup_FrameworksLibraries[12]/SkillRow_Express[4]/SkillTrack_Express[1]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 496.16, + "computedY" : 383.567, + "placementX" : 496.16, + "placementY" : 383.567, + "placementWidth" : 50.575, + "placementHeight" : 1.744, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 50.575, + "contentHeight" : 1.744, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/SkillCaption_Databases[13]", + "entityName" : "SkillCaption_Databases", + "entityKind" : "ParagraphNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]", + "childIndex" : 13, + "depth" : 4, + "layer" : 4, + "computedX" : 413.009, + "computedY" : 359.064, + "placementX" : 413.009, + "placementY" : 359.064, + "placementWidth" : 49.012, + "placementHeight" : 8.284, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 49.012, + "contentHeight" : 8.284, + "margin" : { + "top" : 12.605, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/SkillGroup_Databases[14]", + "entityName" : "SkillGroup_Databases", + "entityKind" : "SectionNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]", + "childIndex" : 14, + "depth" : 4, + "layer" : 4, + "computedX" : 413.009, + "computedY" : 322.066, + "placementX" : 413.009, + "placementY" : 322.066, + "placementWidth" : 150.584, + "placementHeight" : 30.097, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 150.584, + "contentHeight" : 30.097, + "margin" : { + "top" : 6.901, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/SkillGroup_Databases[14]/SkillRow_MySQL[0]", + "entityName" : "SkillRow_MySQL", + "entityKind" : "ShapeContainerNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/SkillGroup_Databases[14]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 413.009, + "computedY" : 343.188, + "placementX" : 413.009, + "placementY" : 343.188, + "placementWidth" : 150.584, + "placementHeight" : 8.974, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 150.584, + "contentHeight" : 8.974, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/SkillGroup_Databases[14]/SkillRow_MySQL[0]/SkillLabel_MySQL[0]", + "entityName" : "SkillLabel_MySQL", + "entityKind" : "ParagraphNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/SkillGroup_Databases[14]/SkillRow_MySQL[0]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 413.009, + "computedY" : 343.534, + "placementX" : 413.009, + "placementY" : 343.534, + "placementWidth" : 22.801, + "placementHeight" : 8.284, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 22.801, + "contentHeight" : 8.284, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/SkillGroup_Databases[14]/SkillRow_MySQL[0]/SkillTrack_MySQL[1]", + "entityName" : "SkillTrack_MySQL", + "entityKind" : "ShapeContainerNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/SkillGroup_Databases[14]/SkillRow_MySQL[0]", + "childIndex" : 1, + "depth" : 6, + "layer" : 6, + "computedX" : 496.16, + "computedY" : 346.803, + "placementX" : 496.16, + "placementY" : 346.803, + "placementWidth" : 67.434, + "placementHeight" : 1.744, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 67.434, + "contentHeight" : 1.744, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/SkillGroup_Databases[14]/SkillRow_MySQL[0]/SkillTrack_MySQL[1]/SkillFill_MySQL[0]", + "entityName" : "SkillFill_MySQL", + "entityKind" : "ShapeNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/SkillGroup_Databases[14]/SkillRow_MySQL[0]/SkillTrack_MySQL[1]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 496.16, + "computedY" : 346.803, + "placementX" : 496.16, + "placementY" : 346.803, + "placementWidth" : 50.575, + "placementHeight" : 1.744, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 50.575, + "contentHeight" : 1.744, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/SkillGroup_Databases[14]/SkillRow_PostgreSQL[1]", + "entityName" : "SkillRow_PostgreSQL", + "entityKind" : "ShapeContainerNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/SkillGroup_Databases[14]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 413.009, + "computedY" : 332.627, + "placementX" : 413.009, + "placementY" : 332.627, + "placementWidth" : 150.584, + "placementHeight" : 8.974, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 150.584, + "contentHeight" : 8.974, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/SkillGroup_Databases[14]/SkillRow_PostgreSQL[1]/SkillLabel_PostgreSQL[0]", + "entityName" : "SkillLabel_PostgreSQL", + "entityKind" : "ParagraphNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/SkillGroup_Databases[14]/SkillRow_PostgreSQL[1]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 413.009, + "computedY" : 332.972, + "placementX" : 413.009, + "placementY" : 332.972, + "placementWidth" : 36.104, + "placementHeight" : 8.284, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 36.104, + "contentHeight" : 8.284, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/SkillGroup_Databases[14]/SkillRow_PostgreSQL[1]/SkillTrack_PostgreSQL[1]", + "entityName" : "SkillTrack_PostgreSQL", + "entityKind" : "ShapeContainerNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/SkillGroup_Databases[14]/SkillRow_PostgreSQL[1]", + "childIndex" : 1, + "depth" : 6, + "layer" : 6, + "computedX" : 496.16, + "computedY" : 336.242, + "placementX" : 496.16, + "placementY" : 336.242, + "placementWidth" : 67.434, + "placementHeight" : 1.744, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 67.434, + "contentHeight" : 1.744, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/SkillGroup_Databases[14]/SkillRow_PostgreSQL[1]/SkillTrack_PostgreSQL[1]/SkillFill_PostgreSQL[0]", + "entityName" : "SkillFill_PostgreSQL", + "entityKind" : "ShapeNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/SkillGroup_Databases[14]/SkillRow_PostgreSQL[1]/SkillTrack_PostgreSQL[1]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 496.16, + "computedY" : 336.242, + "placementX" : 496.16, + "placementY" : 336.242, + "placementWidth" : 50.575, + "placementHeight" : 1.744, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 50.575, + "contentHeight" : 1.744, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/SkillGroup_Databases[14]/SkillRow_MongoDB[2]", + "entityName" : "SkillRow_MongoDB", + "entityKind" : "ShapeContainerNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/SkillGroup_Databases[14]", + "childIndex" : 2, + "depth" : 5, + "layer" : 5, + "computedX" : 413.009, + "computedY" : 322.066, + "placementX" : 413.009, + "placementY" : 322.066, + "placementWidth" : 150.584, + "placementHeight" : 8.974, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 150.584, + "contentHeight" : 8.974, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/SkillGroup_Databases[14]/SkillRow_MongoDB[2]/SkillLabel_MongoDB[0]", + "entityName" : "SkillLabel_MongoDB", + "entityKind" : "ParagraphNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/SkillGroup_Databases[14]/SkillRow_MongoDB[2]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 413.009, + "computedY" : 322.411, + "placementX" : 413.009, + "placementY" : 322.411, + "placementWidth" : 31.403, + "placementHeight" : 8.284, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 31.403, + "contentHeight" : 8.284, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/SkillGroup_Databases[14]/SkillRow_MongoDB[2]/SkillTrack_MongoDB[1]", + "entityName" : "SkillTrack_MongoDB", + "entityKind" : "ShapeContainerNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/SkillGroup_Databases[14]/SkillRow_MongoDB[2]", + "childIndex" : 1, + "depth" : 6, + "layer" : 6, + "computedX" : 496.16, + "computedY" : 325.681, + "placementX" : 496.16, + "placementY" : 325.681, + "placementWidth" : 67.434, + "placementHeight" : 1.744, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 67.434, + "contentHeight" : 1.744, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/SkillGroup_Databases[14]/SkillRow_MongoDB[2]/SkillTrack_MongoDB[1]/SkillFill_MongoDB[0]", + "entityName" : "SkillFill_MongoDB", + "entityKind" : "ShapeNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/SkillGroup_Databases[14]/SkillRow_MongoDB[2]/SkillTrack_MongoDB[1]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 496.16, + "computedY" : 325.681, + "placementX" : 496.16, + "placementY" : 325.681, + "placementWidth" : 50.575, + "placementHeight" : 1.744, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 50.575, + "contentHeight" : 1.744, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/SkillCaption_ToolsTechnologies[15]", + "entityName" : "SkillCaption_ToolsTechnologies", + "entityKind" : "ParagraphNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]", + "childIndex" : 15, + "depth" : 4, + "layer" : 4, + "computedX" : 413.009, + "computedY" : 301.178, + "placementX" : 413.009, + "placementY" : 301.178, + "placementWidth" : 105.596, + "placementHeight" : 8.284, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 105.596, + "contentHeight" : 8.284, + "margin" : { + "top" : 12.605, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/SkillGroup_ToolsTechnologies[16]", + "entityName" : "SkillGroup_ToolsTechnologies", + "entityKind" : "SectionNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]", + "childIndex" : 16, + "depth" : 4, + "layer" : 4, + "computedX" : 413.009, + "computedY" : 243.057, + "placementX" : 413.009, + "placementY" : 243.057, + "placementWidth" : 150.584, + "placementHeight" : 51.219, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 150.584, + "contentHeight" : 51.219, + "margin" : { + "top" : 6.901, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/SkillGroup_ToolsTechnologies[16]/SkillRow_Docker[0]", + "entityName" : "SkillRow_Docker", + "entityKind" : "ShapeContainerNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/SkillGroup_ToolsTechnologies[16]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 413.009, + "computedY" : 285.302, + "placementX" : 413.009, + "placementY" : 285.302, + "placementWidth" : 150.584, + "placementHeight" : 8.974, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 150.584, + "contentHeight" : 8.974, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/SkillGroup_ToolsTechnologies[16]/SkillRow_Docker[0]/SkillLabel_Docker[0]", + "entityName" : "SkillLabel_Docker", + "entityKind" : "ParagraphNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/SkillGroup_ToolsTechnologies[16]/SkillRow_Docker[0]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 413.009, + "computedY" : 285.647, + "placementX" : 413.009, + "placementY" : 285.647, + "placementWidth" : 22.132, + "placementHeight" : 8.284, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 22.132, + "contentHeight" : 8.284, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/SkillGroup_ToolsTechnologies[16]/SkillRow_Docker[0]/SkillTrack_Docker[1]", + "entityName" : "SkillTrack_Docker", + "entityKind" : "ShapeContainerNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/SkillGroup_ToolsTechnologies[16]/SkillRow_Docker[0]", + "childIndex" : 1, + "depth" : 6, + "layer" : 6, + "computedX" : 496.16, + "computedY" : 288.917, + "placementX" : 496.16, + "placementY" : 288.917, + "placementWidth" : 67.434, + "placementHeight" : 1.744, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 67.434, + "contentHeight" : 1.744, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/SkillGroup_ToolsTechnologies[16]/SkillRow_Docker[0]/SkillTrack_Docker[1]/SkillFill_Docker[0]", + "entityName" : "SkillFill_Docker", + "entityKind" : "ShapeNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/SkillGroup_ToolsTechnologies[16]/SkillRow_Docker[0]/SkillTrack_Docker[1]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 496.16, + "computedY" : 288.917, + "placementX" : 496.16, + "placementY" : 288.917, + "placementWidth" : 50.575, + "placementHeight" : 1.744, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 50.575, + "contentHeight" : 1.744, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/SkillGroup_ToolsTechnologies[16]/SkillRow_Kubernetes[1]", + "entityName" : "SkillRow_Kubernetes", + "entityKind" : "ShapeContainerNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/SkillGroup_ToolsTechnologies[16]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 413.009, + "computedY" : 274.741, + "placementX" : 413.009, + "placementY" : 274.741, + "placementWidth" : 150.584, + "placementHeight" : 8.974, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 150.584, + "contentHeight" : 8.974, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/SkillGroup_ToolsTechnologies[16]/SkillRow_Kubernetes[1]/SkillLabel_Kubernetes[0]", + "entityName" : "SkillLabel_Kubernetes", + "entityKind" : "ParagraphNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/SkillGroup_ToolsTechnologies[16]/SkillRow_Kubernetes[1]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 413.009, + "computedY" : 275.086, + "placementX" : 413.009, + "placementY" : 275.086, + "placementWidth" : 35.061, + "placementHeight" : 8.284, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 35.061, + "contentHeight" : 8.284, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/SkillGroup_ToolsTechnologies[16]/SkillRow_Kubernetes[1]/SkillTrack_Kubernetes[1]", + "entityName" : "SkillTrack_Kubernetes", + "entityKind" : "ShapeContainerNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/SkillGroup_ToolsTechnologies[16]/SkillRow_Kubernetes[1]", + "childIndex" : 1, + "depth" : 6, + "layer" : 6, + "computedX" : 496.16, + "computedY" : 278.356, + "placementX" : 496.16, + "placementY" : 278.356, + "placementWidth" : 67.434, + "placementHeight" : 1.744, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 67.434, + "contentHeight" : 1.744, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/SkillGroup_ToolsTechnologies[16]/SkillRow_Kubernetes[1]/SkillTrack_Kubernetes[1]/SkillFill_Kubernetes[0]", + "entityName" : "SkillFill_Kubernetes", + "entityKind" : "ShapeNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/SkillGroup_ToolsTechnologies[16]/SkillRow_Kubernetes[1]/SkillTrack_Kubernetes[1]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 496.16, + "computedY" : 278.356, + "placementX" : 496.16, + "placementY" : 278.356, + "placementWidth" : 50.575, + "placementHeight" : 1.744, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 50.575, + "contentHeight" : 1.744, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/SkillGroup_ToolsTechnologies[16]/SkillRow_Jenkins[2]", + "entityName" : "SkillRow_Jenkins", + "entityKind" : "ShapeContainerNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/SkillGroup_ToolsTechnologies[16]", + "childIndex" : 2, + "depth" : 5, + "layer" : 5, + "computedX" : 413.009, + "computedY" : 264.18, + "placementX" : 413.009, + "placementY" : 264.18, + "placementWidth" : 150.584, + "placementHeight" : 8.974, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 150.584, + "contentHeight" : 8.974, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/SkillGroup_ToolsTechnologies[16]/SkillRow_Jenkins[2]/SkillLabel_Jenkins[0]", + "entityName" : "SkillLabel_Jenkins", + "entityKind" : "ParagraphNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/SkillGroup_ToolsTechnologies[16]/SkillRow_Jenkins[2]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 413.009, + "computedY" : 264.525, + "placementX" : 413.009, + "placementY" : 264.525, + "placementWidth" : 22.422, + "placementHeight" : 8.284, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 22.422, + "contentHeight" : 8.284, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/SkillGroup_ToolsTechnologies[16]/SkillRow_Jenkins[2]/SkillTrack_Jenkins[1]", + "entityName" : "SkillTrack_Jenkins", + "entityKind" : "ShapeContainerNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/SkillGroup_ToolsTechnologies[16]/SkillRow_Jenkins[2]", + "childIndex" : 1, + "depth" : 6, + "layer" : 6, + "computedX" : 496.16, + "computedY" : 267.795, + "placementX" : 496.16, + "placementY" : 267.795, + "placementWidth" : 67.434, + "placementHeight" : 1.744, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 67.434, + "contentHeight" : 1.744, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/SkillGroup_ToolsTechnologies[16]/SkillRow_Jenkins[2]/SkillTrack_Jenkins[1]/SkillFill_Jenkins[0]", + "entityName" : "SkillFill_Jenkins", + "entityKind" : "ShapeNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/SkillGroup_ToolsTechnologies[16]/SkillRow_Jenkins[2]/SkillTrack_Jenkins[1]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 496.16, + "computedY" : 267.795, + "placementX" : 496.16, + "placementY" : 267.795, + "placementWidth" : 50.575, + "placementHeight" : 1.744, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 50.575, + "contentHeight" : 1.744, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/SkillGroup_ToolsTechnologies[16]/SkillRow_Git[3]", + "entityName" : "SkillRow_Git", + "entityKind" : "ShapeContainerNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/SkillGroup_ToolsTechnologies[16]", + "childIndex" : 3, + "depth" : 5, + "layer" : 5, + "computedX" : 413.009, + "computedY" : 253.619, + "placementX" : 413.009, + "placementY" : 253.619, + "placementWidth" : 150.584, + "placementHeight" : 8.974, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 150.584, + "contentHeight" : 8.974, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/SkillGroup_ToolsTechnologies[16]/SkillRow_Git[3]/SkillLabel_Git[0]", + "entityName" : "SkillLabel_Git", + "entityKind" : "ParagraphNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/SkillGroup_ToolsTechnologies[16]/SkillRow_Git[3]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 413.009, + "computedY" : 253.964, + "placementX" : 413.009, + "placementY" : 253.964, + "placementWidth" : 9.181, + "placementHeight" : 8.284, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 9.181, + "contentHeight" : 8.284, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/SkillGroup_ToolsTechnologies[16]/SkillRow_Git[3]/SkillTrack_Git[1]", + "entityName" : "SkillTrack_Git", + "entityKind" : "ShapeContainerNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/SkillGroup_ToolsTechnologies[16]/SkillRow_Git[3]", + "childIndex" : 1, + "depth" : 6, + "layer" : 6, + "computedX" : 496.16, + "computedY" : 257.234, + "placementX" : 496.16, + "placementY" : 257.234, + "placementWidth" : 67.434, + "placementHeight" : 1.744, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 67.434, + "contentHeight" : 1.744, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/SkillGroup_ToolsTechnologies[16]/SkillRow_Git[3]/SkillTrack_Git[1]/SkillFill_Git[0]", + "entityName" : "SkillFill_Git", + "entityKind" : "ShapeNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/SkillGroup_ToolsTechnologies[16]/SkillRow_Git[3]/SkillTrack_Git[1]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 496.16, + "computedY" : 257.234, + "placementX" : 496.16, + "placementY" : 257.234, + "placementWidth" : 26.299, + "placementHeight" : 1.744, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 26.299, + "contentHeight" : 1.744, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/SkillGroup_ToolsTechnologies[16]/SkillRow_AWS[4]", + "entityName" : "SkillRow_AWS", + "entityKind" : "ShapeContainerNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/SkillGroup_ToolsTechnologies[16]", + "childIndex" : 4, + "depth" : 5, + "layer" : 5, + "computedX" : 413.009, + "computedY" : 243.057, + "placementX" : 413.009, + "placementY" : 243.057, + "placementWidth" : 150.584, + "placementHeight" : 8.974, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 150.584, + "contentHeight" : 8.974, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/SkillGroup_ToolsTechnologies[16]/SkillRow_AWS[4]/SkillLabel_AWS[0]", + "entityName" : "SkillLabel_AWS", + "entityKind" : "ParagraphNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/SkillGroup_ToolsTechnologies[16]/SkillRow_AWS[4]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 413.009, + "computedY" : 243.403, + "placementX" : 413.009, + "placementY" : 243.403, + "placementWidth" : 15.574, + "placementHeight" : 8.284, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 15.574, + "contentHeight" : 8.284, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/SkillGroup_ToolsTechnologies[16]/SkillRow_AWS[4]/SkillTrack_AWS[1]", + "entityName" : "SkillTrack_AWS", + "entityKind" : "ShapeContainerNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/SkillGroup_ToolsTechnologies[16]/SkillRow_AWS[4]", + "childIndex" : 1, + "depth" : 6, + "layer" : 6, + "computedX" : 496.16, + "computedY" : 246.673, + "placementX" : 496.16, + "placementY" : 246.673, + "placementWidth" : 67.434, + "placementHeight" : 1.744, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 67.434, + "contentHeight" : 1.744, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/SkillGroup_ToolsTechnologies[16]/SkillRow_AWS[4]/SkillTrack_AWS[1]/SkillFill_AWS[0]", + "entityName" : "SkillFill_AWS", + "entityKind" : "ShapeNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/SkillGroup_ToolsTechnologies[16]/SkillRow_AWS[4]/SkillTrack_AWS[1]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 496.16, + "computedY" : 246.673, + "placementX" : 496.16, + "placementY" : 246.673, + "placementWidth" : 50.575, + "placementHeight" : 1.744, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 50.575, + "contentHeight" : 1.744, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/SkillCaption_SoftSkills[17]", + "entityName" : "SkillCaption_SoftSkills", + "entityKind" : "ParagraphNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]", + "childIndex" : 17, + "depth" : 4, + "layer" : 4, + "computedX" : 413.009, + "computedY" : 222.169, + "placementX" : 413.009, + "placementY" : 222.169, + "placementWidth" : 52.046, + "placementHeight" : 8.284, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 52.046, + "contentHeight" : 8.284, + "margin" : { + "top" : 12.605, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/SoftSkill_ProblemSolvingCommunication[18]", + "entityName" : "SoftSkill_ProblemSolvingCommunication", + "entityKind" : "ParagraphNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]", + "childIndex" : 18, + "depth" : 4, + "layer" : 4, + "computedX" : 413.009, + "computedY" : 208.748, + "placementX" : 413.009, + "placementY" : 208.748, + "placementWidth" : 127.875, + "placementHeight" : 9.689, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 127.875, + "contentHeight" : 9.689, + "margin" : { + "top" : 3.732, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/SoftSkill_TeamworkAdaptability[19]", + "entityName" : "SoftSkill_TeamworkAdaptability", + "entityKind" : "ParagraphNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]", + "childIndex" : 19, + "depth" : 4, + "layer" : 4, + "computedX" : 413.009, + "computedY" : 199.06, + "placementX" : 413.009, + "placementY" : 199.06, + "placementWidth" : 94.893, + "placementHeight" : 9.689, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 94.893, + "contentHeight" : 9.689, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]/SoftSkill_LeadershipTimeManagement[20]", + "entityName" : "SoftSkill_LeadershipTimeManagement", + "entityKind" : "ParagraphNode", + "parentPath" : "SerifHeadlineCv[0]/BodyColumns[3]/AsideColumn[1]", + "childIndex" : 20, + "depth" : 4, + "layer" : 4, + "computedX" : 413.009, + "computedY" : 189.371, + "placementX" : 413.009, + "placementY" : 189.371, + "placementWidth" : 119.535, + "placementHeight" : 9.689, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 119.535, + "contentHeight" : 9.689, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/Certifications[4]", + "entityName" : "Certifications", + "entityKind" : "SectionNode", + "parentPath" : "SerifHeadlineCv[0]", + "childIndex" : 4, + "depth" : 2, + "layer" : 2, + "computedX" : 31.682, + "computedY" : 129.267, + "placementX" : 31.682, + "placementY" : 129.267, + "placementWidth" : 531.911, + "placementHeight" : 54.674, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 531.911, + "contentHeight" : 54.674, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 19.43, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/Certifications[4]/SectionHeading_Certifications[0]", + "entityName" : "SectionHeading_Certifications", + "entityKind" : "ShapeContainerNode", + "parentPath" : "SerifHeadlineCv[0]/Certifications[4]", + "childIndex" : 0, + "depth" : 3, + "layer" : 3, + "computedX" : 31.682, + "computedY" : 171.346, + "placementX" : 31.682, + "placementY" : 171.346, + "placementWidth" : 531.911, + "placementHeight" : 12.595, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 531.911, + "contentHeight" : 12.595, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/Certifications[4]/SectionHeading_Certifications[0]/HeadingDash_Certifications[0]", + "entityName" : "HeadingDash_Certifications", + "entityKind" : "LineNode", + "parentPath" : "SerifHeadlineCv[0]/Certifications[4]/SectionHeading_Certifications[0]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 14.533, + "computedY" : 176.772, + "placementX" : 14.533, + "placementY" : 176.772, + "placementWidth" : 12.789, + "placementHeight" : 1.744, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 12.789, + "contentHeight" : 1.744, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/Certifications[4]/SectionHeading_Certifications[0]/SectionTitle_Certifications[1]", + "entityName" : "SectionTitle_Certifications", + "entityKind" : "ParagraphNode", + "parentPath" : "SerifHeadlineCv[0]/Certifications[4]/SectionHeading_Certifications[0]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 36.623, + "computedY" : 171.831, + "placementX" : 36.623, + "placementY" : 171.831, + "placementWidth" : 98.603, + "placementHeight" : 11.626, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 98.603, + "contentHeight" : 11.626, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/Certifications[4]/SectionHeading_Certifications[0]/HeadingTail_Certifications[2]", + "entityName" : "HeadingTail_Certifications", + "entityKind" : "LineNode", + "parentPath" : "SerifHeadlineCv[0]/Certifications[4]/SectionHeading_Certifications[0]", + "childIndex" : 2, + "depth" : 4, + "layer" : 4, + "computedX" : 146.764, + "computedY" : 177.144, + "placementX" : 146.764, + "placementY" : 177.144, + "placementWidth" : 416.83, + "placementHeight" : 1.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 416.83, + "contentHeight" : 1.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/Certifications[4]/CertificationsBand[1]", + "entityName" : "CertificationsBand", + "entityKind" : "LayerStackNode", + "parentPath" : "SerifHeadlineCv[0]/Certifications[4]", + "childIndex" : 1, + "depth" : 3, + "layer" : 3, + "computedX" : 31.682, + "computedY" : 129.267, + "placementX" : 31.682, + "placementY" : 129.267, + "placementWidth" : 531.911, + "placementHeight" : 27.904, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 531.911, + "contentHeight" : 27.904, + "margin" : { + "top" : 14.176, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/Certifications[4]/CertificationsBand[1]/Certification_AWSCertifiedDeveloperAssociate[0]", + "entityName" : "Certification_AWSCertifiedDeveloperAssociate", + "entityKind" : "SectionNode", + "parentPath" : "SerifHeadlineCv[0]/Certifications[4]/CertificationsBand[1]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 31.682, + "computedY" : 130.967, + "placementX" : 31.682, + "placementY" : 130.967, + "placementWidth" : 99.685, + "placementHeight" : 26.204, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 99.685, + "contentHeight" : 26.204, + "margin" : { + "top" : 0.0, + "right" : 431.342, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 31.391 + } + }, { + "path" : "SerifHeadlineCv[0]/Certifications[4]/CertificationsBand[1]/Certification_AWSCertifiedDeveloperAssociate[0]/CertificationTitle_AWSCertifiedDeveloperAssociate[0]", + "entityName" : "CertificationTitle_AWSCertifiedDeveloperAssociate", + "entityKind" : "ParagraphNode", + "parentPath" : "SerifHeadlineCv[0]/Certifications[4]/CertificationsBand[1]/Certification_AWSCertifiedDeveloperAssociate[0]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 63.074, + "computedY" : 139.251, + "placementX" : 63.074, + "placementY" : 139.251, + "placementWidth" : 68.294, + "placementHeight" : 17.92, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 68.294, + "contentHeight" : 17.92, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/Certifications[4]/CertificationsBand[1]/Certification_AWSCertifiedDeveloperAssociate[0]/CertificationIssuer_AWSCertifiedDeveloperAssociate[1]", + "entityName" : "CertificationIssuer_AWSCertifiedDeveloperAssociate", + "entityKind" : "ParagraphNode", + "parentPath" : "SerifHeadlineCv[0]/Certifications[4]/CertificationsBand[1]/Certification_AWSCertifiedDeveloperAssociate[0]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 63.074, + "computedY" : 130.967, + "placementX" : 63.074, + "placementY" : 130.967, + "placementWidth" : 19.26, + "placementHeight" : 8.284, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 19.26, + "contentHeight" : 8.284, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/Certifications[4]/CertificationsBand[1]/Certification_OracleCertifiedProfessional[1]", + "entityName" : "Certification_OracleCertifiedProfessional", + "entityKind" : "SectionNode", + "parentPath" : "SerifHeadlineCv[0]/Certifications[4]/CertificationsBand[1]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 138.064, + "computedY" : 130.967, + "placementX" : 138.064, + "placementY" : 130.967, + "placementWidth" : 97.655, + "placementHeight" : 26.204, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 97.655, + "contentHeight" : 26.204, + "margin" : { + "top" : 0.0, + "right" : 324.96, + "bottom" : 0.0, + "left" : 106.382 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 39.53 + } + }, { + "path" : "SerifHeadlineCv[0]/Certifications[4]/CertificationsBand[1]/Certification_OracleCertifiedProfessional[1]/CertificationTitle_OracleCertifiedProfessional[0]", + "entityName" : "CertificationTitle_OracleCertifiedProfessional", + "entityKind" : "ParagraphNode", + "parentPath" : "SerifHeadlineCv[0]/Certifications[4]/CertificationsBand[1]/Certification_OracleCertifiedProfessional[1]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 177.594, + "computedY" : 139.251, + "placementX" : 177.594, + "placementY" : 139.251, + "placementWidth" : 48.309, + "placementHeight" : 17.92, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 48.309, + "contentHeight" : 17.92, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/Certifications[4]/CertificationsBand[1]/Certification_OracleCertifiedProfessional[1]/CertificationIssuer_OracleCertifiedProfessional[1]", + "entityName" : "CertificationIssuer_OracleCertifiedProfessional", + "entityKind" : "ParagraphNode", + "parentPath" : "SerifHeadlineCv[0]/Certifications[4]/CertificationsBand[1]/Certification_OracleCertifiedProfessional[1]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 177.594, + "computedY" : 130.967, + "placementX" : 177.594, + "placementY" : 130.967, + "placementWidth" : 58.125, + "placementHeight" : 8.284, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 58.125, + "contentHeight" : 8.284, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/Certifications[4]/CertificationsBand[1]/Certification_DockerCertifiedAssociate[2]", + "entityName" : "Certification_DockerCertifiedAssociate", + "entityKind" : "SectionNode", + "parentPath" : "SerifHeadlineCv[0]/Certifications[4]/CertificationsBand[1]", + "childIndex" : 2, + "depth" : 4, + "layer" : 4, + "computedX" : 244.447, + "computedY" : 130.967, + "placementX" : 244.447, + "placementY" : 130.967, + "placementWidth" : 89.924, + "placementHeight" : 26.204, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 89.924, + "contentHeight" : 26.204, + "margin" : { + "top" : 0.0, + "right" : 218.578, + "bottom" : 0.0, + "left" : 212.765 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 39.53 + } + }, { + "path" : "SerifHeadlineCv[0]/Certifications[4]/CertificationsBand[1]/Certification_DockerCertifiedAssociate[2]/CertificationTitle_DockerCertifiedAssociate[0]", + "entityName" : "CertificationTitle_DockerCertifiedAssociate", + "entityKind" : "ParagraphNode", + "parentPath" : "SerifHeadlineCv[0]/Certifications[4]/CertificationsBand[1]/Certification_DockerCertifiedAssociate[2]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 283.977, + "computedY" : 139.251, + "placementX" : 283.977, + "placementY" : 139.251, + "placementWidth" : 50.394, + "placementHeight" : 17.92, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 50.394, + "contentHeight" : 17.92, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/Certifications[4]/CertificationsBand[1]/Certification_DockerCertifiedAssociate[2]/CertificationIssuer_DockerCertifiedAssociate[1]", + "entityName" : "CertificationIssuer_DockerCertifiedAssociate", + "entityKind" : "ParagraphNode", + "parentPath" : "SerifHeadlineCv[0]/Certifications[4]/CertificationsBand[1]/Certification_DockerCertifiedAssociate[2]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 283.977, + "computedY" : 130.967, + "placementX" : 283.977, + "placementY" : 130.967, + "placementWidth" : 18.225, + "placementHeight" : 8.284, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 18.225, + "contentHeight" : 8.284, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/Certifications[4]/CertificationsBand[1]/Certification_SpringProfessionalCertification[3]", + "entityName" : "Certification_SpringProfessionalCertification", + "entityKind" : "SectionNode", + "parentPath" : "SerifHeadlineCv[0]/Certifications[4]/CertificationsBand[1]", + "childIndex" : 3, + "depth" : 4, + "layer" : 4, + "computedX" : 350.829, + "computedY" : 130.967, + "placementX" : 350.829, + "placementY" : 130.967, + "placementWidth" : 97.634, + "placementHeight" : 26.204, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 97.634, + "contentHeight" : 26.204, + "margin" : { + "top" : 0.0, + "right" : 112.196, + "bottom" : 0.0, + "left" : 319.147 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 39.53 + } + }, { + "path" : "SerifHeadlineCv[0]/Certifications[4]/CertificationsBand[1]/Certification_SpringProfessionalCertification[3]/CertificationTitle_SpringProfessionalCertification[0]", + "entityName" : "CertificationTitle_SpringProfessionalCertification", + "entityKind" : "ParagraphNode", + "parentPath" : "SerifHeadlineCv[0]/Certifications[4]/CertificationsBand[1]/Certification_SpringProfessionalCertification[3]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 390.359, + "computedY" : 139.251, + "placementX" : 390.359, + "placementY" : 139.251, + "placementWidth" : 58.104, + "placementHeight" : 17.92, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 58.104, + "contentHeight" : 17.92, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/Certifications[4]/CertificationsBand[1]/Certification_SpringProfessionalCertification[3]/CertificationIssuer_SpringProfessionalCertification[1]", + "entityName" : "CertificationIssuer_SpringProfessionalCertification", + "entityKind" : "ParagraphNode", + "parentPath" : "SerifHeadlineCv[0]/Certifications[4]/CertificationsBand[1]/Certification_SpringProfessionalCertification[3]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 390.359, + "computedY" : 130.967, + "placementX" : 390.359, + "placementY" : 130.967, + "placementWidth" : 29.787, + "placementHeight" : 8.284, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 29.787, + "contentHeight" : 8.284, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/Certifications[4]/CertificationsBand[1]/Certification_KubernetesFundamentals[4]", + "entityName" : "Certification_KubernetesFundamentals", + "entityKind" : "SectionNode", + "parentPath" : "SerifHeadlineCv[0]/Certifications[4]/CertificationsBand[1]", + "childIndex" : 4, + "depth" : 4, + "layer" : 4, + "computedX" : 457.211, + "computedY" : 130.967, + "placementX" : 457.211, + "placementY" : 130.967, + "placementWidth" : 96.054, + "placementHeight" : 26.204, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 96.054, + "contentHeight" : 26.204, + "margin" : { + "top" : 0.0, + "right" : 5.813, + "bottom" : 0.0, + "left" : 425.529 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 39.53 + } + }, { + "path" : "SerifHeadlineCv[0]/Certifications[4]/CertificationsBand[1]/Certification_KubernetesFundamentals[4]/CertificationTitle_KubernetesFundamentals[0]", + "entityName" : "CertificationTitle_KubernetesFundamentals", + "entityKind" : "ParagraphNode", + "parentPath" : "SerifHeadlineCv[0]/Certifications[4]/CertificationsBand[1]/Certification_KubernetesFundamentals[4]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 496.741, + "computedY" : 139.251, + "placementX" : 496.741, + "placementY" : 139.251, + "placementWidth" : 42.614, + "placementHeight" : 17.92, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 42.614, + "contentHeight" : 17.92, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/Certifications[4]/CertificationsBand[1]/Certification_KubernetesFundamentals[4]/CertificationIssuer_KubernetesFundamentals[1]", + "entityName" : "CertificationIssuer_KubernetesFundamentals", + "entityKind" : "ParagraphNode", + "parentPath" : "SerifHeadlineCv[0]/Certifications[4]/CertificationsBand[1]/Certification_KubernetesFundamentals[4]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 496.741, + "computedY" : 130.967, + "placementX" : 496.741, + "placementY" : 130.967, + "placementWidth" : 56.524, + "placementHeight" : 8.284, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 56.524, + "contentHeight" : 8.284, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/Certifications[4]/CertificationsBand[1]/CertificationsGlyph_0[5]", + "entityName" : "CertificationsGlyph_0", + "entityKind" : "SectionNode", + "parentPath" : "SerifHeadlineCv[0]/Certifications[4]/CertificationsBand[1]", + "childIndex" : 5, + "depth" : 4, + "layer" : 4, + "computedX" : 29.357, + "computedY" : 129.267, + "placementX" : 29.357, + "placementY" : 129.267, + "placementWidth" : 27.904, + "placementHeight" : 27.904, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 27.904, + "contentHeight" : 27.904, + "margin" : { + "top" : 0.0, + "right" : 425.529, + "bottom" : 0.0, + "left" : -2.325 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/Certifications[4]/CertificationsBand[1]/CertificationsGlyph_0[5]/CertificationPlate_AWSCertifiedDeveloperAssociate[0]", + "entityName" : "CertificationPlate_AWSCertifiedDeveloperAssociate", + "entityKind" : "ShapeContainerNode", + "parentPath" : "SerifHeadlineCv[0]/Certifications[4]/CertificationsBand[1]/CertificationsGlyph_0[5]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 29.357, + "computedY" : 129.267, + "placementX" : 29.357, + "placementY" : 129.267, + "placementWidth" : 27.904, + "placementHeight" : 27.904, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 27.904, + "contentHeight" : 27.904, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/Certifications[4]/CertificationsBand[1]/CertificationsGlyph_0[5]/CertificationPlate_AWSCertifiedDeveloperAssociate[0]/CertificationPlate_AWSCertifiedDeveloperAssociateGlyph[0]", + "entityName" : "CertificationPlate_AWSCertifiedDeveloperAssociateGlyph", + "entityKind" : "ImageNode", + "parentPath" : "SerifHeadlineCv[0]/Certifications[4]/CertificationsBand[1]/CertificationsGlyph_0[5]/CertificationPlate_AWSCertifiedDeveloperAssociate[0]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 35.159, + "computedY" : 135.069, + "placementX" : 35.159, + "placementY" : 135.069, + "placementWidth" : 16.3, + "placementHeight" : 16.3, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 16.3, + "contentHeight" : 16.3, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/Certifications[4]/CertificationsBand[1]/CertificationsGlyph_1[6]", + "entityName" : "CertificationsGlyph_1", + "entityKind" : "SectionNode", + "parentPath" : "SerifHeadlineCv[0]/Certifications[4]/CertificationsBand[1]", + "childIndex" : 6, + "depth" : 4, + "layer" : 4, + "computedX" : 143.878, + "computedY" : 129.267, + "placementX" : 143.878, + "placementY" : 129.267, + "placementWidth" : 27.904, + "placementHeight" : 27.904, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 27.904, + "contentHeight" : 27.904, + "margin" : { + "top" : 0.0, + "right" : 319.147, + "bottom" : 0.0, + "left" : 112.196 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/Certifications[4]/CertificationsBand[1]/CertificationsGlyph_1[6]/CertificationPlate_OracleCertifiedProfessional[0]", + "entityName" : "CertificationPlate_OracleCertifiedProfessional", + "entityKind" : "ShapeContainerNode", + "parentPath" : "SerifHeadlineCv[0]/Certifications[4]/CertificationsBand[1]/CertificationsGlyph_1[6]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 143.878, + "computedY" : 129.267, + "placementX" : 143.878, + "placementY" : 129.267, + "placementWidth" : 27.904, + "placementHeight" : 27.904, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 27.904, + "contentHeight" : 27.904, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/Certifications[4]/CertificationsBand[1]/CertificationsGlyph_1[6]/CertificationPlate_OracleCertifiedProfessional[0]/CertificationPlate_OracleCertifiedProfessionalGlyph[0]", + "entityName" : "CertificationPlate_OracleCertifiedProfessionalGlyph", + "entityKind" : "ImageNode", + "parentPath" : "SerifHeadlineCv[0]/Certifications[4]/CertificationsBand[1]/CertificationsGlyph_1[6]/CertificationPlate_OracleCertifiedProfessional[0]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 149.679, + "computedY" : 135.069, + "placementX" : 149.679, + "placementY" : 135.069, + "placementWidth" : 16.3, + "placementHeight" : 16.3, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 16.3, + "contentHeight" : 16.3, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/Certifications[4]/CertificationsBand[1]/CertificationsGlyph_2[7]", + "entityName" : "CertificationsGlyph_2", + "entityKind" : "SectionNode", + "parentPath" : "SerifHeadlineCv[0]/Certifications[4]/CertificationsBand[1]", + "childIndex" : 7, + "depth" : 4, + "layer" : 4, + "computedX" : 250.26, + "computedY" : 129.267, + "placementX" : 250.26, + "placementY" : 129.267, + "placementWidth" : 27.904, + "placementHeight" : 27.904, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 27.904, + "contentHeight" : 27.904, + "margin" : { + "top" : 0.0, + "right" : 212.765, + "bottom" : 0.0, + "left" : 218.578 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/Certifications[4]/CertificationsBand[1]/CertificationsGlyph_2[7]/CertificationPlate_DockerCertifiedAssociate[0]", + "entityName" : "CertificationPlate_DockerCertifiedAssociate", + "entityKind" : "ShapeContainerNode", + "parentPath" : "SerifHeadlineCv[0]/Certifications[4]/CertificationsBand[1]/CertificationsGlyph_2[7]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 250.26, + "computedY" : 129.267, + "placementX" : 250.26, + "placementY" : 129.267, + "placementWidth" : 27.904, + "placementHeight" : 27.904, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 27.904, + "contentHeight" : 27.904, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/Certifications[4]/CertificationsBand[1]/CertificationsGlyph_2[7]/CertificationPlate_DockerCertifiedAssociate[0]/CertificationPlate_DockerCertifiedAssociateGlyph[0]", + "entityName" : "CertificationPlate_DockerCertifiedAssociateGlyph", + "entityKind" : "ImageNode", + "parentPath" : "SerifHeadlineCv[0]/Certifications[4]/CertificationsBand[1]/CertificationsGlyph_2[7]/CertificationPlate_DockerCertifiedAssociate[0]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 256.062, + "computedY" : 135.069, + "placementX" : 256.062, + "placementY" : 135.069, + "placementWidth" : 16.3, + "placementHeight" : 16.3, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 16.3, + "contentHeight" : 16.3, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/Certifications[4]/CertificationsBand[1]/CertificationsGlyph_3[8]", + "entityName" : "CertificationsGlyph_3", + "entityKind" : "SectionNode", + "parentPath" : "SerifHeadlineCv[0]/Certifications[4]/CertificationsBand[1]", + "childIndex" : 8, + "depth" : 4, + "layer" : 4, + "computedX" : 356.642, + "computedY" : 129.267, + "placementX" : 356.642, + "placementY" : 129.267, + "placementWidth" : 27.904, + "placementHeight" : 27.904, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 27.904, + "contentHeight" : 27.904, + "margin" : { + "top" : 0.0, + "right" : 106.382, + "bottom" : 0.0, + "left" : 324.96 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/Certifications[4]/CertificationsBand[1]/CertificationsGlyph_3[8]/CertificationPlate_SpringProfessionalCertification[0]", + "entityName" : "CertificationPlate_SpringProfessionalCertification", + "entityKind" : "ShapeContainerNode", + "parentPath" : "SerifHeadlineCv[0]/Certifications[4]/CertificationsBand[1]/CertificationsGlyph_3[8]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 356.642, + "computedY" : 129.267, + "placementX" : 356.642, + "placementY" : 129.267, + "placementWidth" : 27.904, + "placementHeight" : 27.904, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 27.904, + "contentHeight" : 27.904, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/Certifications[4]/CertificationsBand[1]/CertificationsGlyph_3[8]/CertificationPlate_SpringProfessionalCertification[0]/CertificationPlate_SpringProfessionalCertificationGlyph[0]", + "entityName" : "CertificationPlate_SpringProfessionalCertificationGlyph", + "entityKind" : "ImageNode", + "parentPath" : "SerifHeadlineCv[0]/Certifications[4]/CertificationsBand[1]/CertificationsGlyph_3[8]/CertificationPlate_SpringProfessionalCertification[0]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 362.444, + "computedY" : 135.069, + "placementX" : 362.444, + "placementY" : 135.069, + "placementWidth" : 16.3, + "placementHeight" : 16.3, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 16.3, + "contentHeight" : 16.3, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/Certifications[4]/CertificationsBand[1]/CertificationsGlyph_4[9]", + "entityName" : "CertificationsGlyph_4", + "entityKind" : "SectionNode", + "parentPath" : "SerifHeadlineCv[0]/Certifications[4]/CertificationsBand[1]", + "childIndex" : 9, + "depth" : 4, + "layer" : 4, + "computedX" : 463.024, + "computedY" : 129.267, + "placementX" : 463.024, + "placementY" : 129.267, + "placementWidth" : 27.904, + "placementHeight" : 27.904, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 27.904, + "contentHeight" : 27.904, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 431.342 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/Certifications[4]/CertificationsBand[1]/CertificationsGlyph_4[9]/CertificationPlate_KubernetesFundamentals[0]", + "entityName" : "CertificationPlate_KubernetesFundamentals", + "entityKind" : "ShapeContainerNode", + "parentPath" : "SerifHeadlineCv[0]/Certifications[4]/CertificationsBand[1]/CertificationsGlyph_4[9]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 463.024, + "computedY" : 129.267, + "placementX" : 463.024, + "placementY" : 129.267, + "placementWidth" : 27.904, + "placementHeight" : 27.904, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 27.904, + "contentHeight" : 27.904, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/Certifications[4]/CertificationsBand[1]/CertificationsGlyph_4[9]/CertificationPlate_KubernetesFundamentals[0]/CertificationPlate_KubernetesFundamentalsGlyph[0]", + "entityName" : "CertificationPlate_KubernetesFundamentalsGlyph", + "entityKind" : "ImageNode", + "parentPath" : "SerifHeadlineCv[0]/Certifications[4]/CertificationsBand[1]/CertificationsGlyph_4[9]/CertificationPlate_KubernetesFundamentals[0]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 468.826, + "computedY" : 135.069, + "placementX" : 468.826, + "placementY" : 135.069, + "placementWidth" : 16.3, + "placementHeight" : 16.3, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 16.3, + "contentHeight" : 16.3, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/Certifications[4]/CertificationsBand[1]/CertificationsSeparator_1[10]", + "entityName" : "CertificationsSeparator_1", + "entityKind" : "SectionNode", + "parentPath" : "SerifHeadlineCv[0]/Certifications[4]/CertificationsBand[1]", + "childIndex" : 10, + "depth" : 4, + "layer" : 4, + "computedX" : 138.064, + "computedY" : 134.574, + "placementX" : 138.064, + "placementY" : 134.574, + "placementWidth" : 0.698, + "placementHeight" : 22.596, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 0.698, + "contentHeight" : 22.596, + "margin" : { + "top" : 0.0, + "right" : 424.831, + "bottom" : 0.0, + "left" : 106.382 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/Certifications[4]/CertificationsBand[1]/CertificationsSeparator_1[10]/CertificationsSeparatorLine_1[0]", + "entityName" : "CertificationsSeparatorLine_1", + "entityKind" : "LineNode", + "parentPath" : "SerifHeadlineCv[0]/Certifications[4]/CertificationsBand[1]/CertificationsSeparator_1[10]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 138.064, + "computedY" : 134.574, + "placementX" : 138.064, + "placementY" : 134.574, + "placementWidth" : 1.0, + "placementHeight" : 22.596, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 1.0, + "contentHeight" : 22.596, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/Certifications[4]/CertificationsBand[1]/CertificationsSeparator_2[11]", + "entityName" : "CertificationsSeparator_2", + "entityKind" : "SectionNode", + "parentPath" : "SerifHeadlineCv[0]/Certifications[4]/CertificationsBand[1]", + "childIndex" : 11, + "depth" : 4, + "layer" : 4, + "computedX" : 244.447, + "computedY" : 134.574, + "placementX" : 244.447, + "placementY" : 134.574, + "placementWidth" : 0.698, + "placementHeight" : 22.596, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 0.698, + "contentHeight" : 22.596, + "margin" : { + "top" : 0.0, + "right" : 318.449, + "bottom" : 0.0, + "left" : 212.765 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/Certifications[4]/CertificationsBand[1]/CertificationsSeparator_2[11]/CertificationsSeparatorLine_2[0]", + "entityName" : "CertificationsSeparatorLine_2", + "entityKind" : "LineNode", + "parentPath" : "SerifHeadlineCv[0]/Certifications[4]/CertificationsBand[1]/CertificationsSeparator_2[11]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 244.447, + "computedY" : 134.574, + "placementX" : 244.447, + "placementY" : 134.574, + "placementWidth" : 1.0, + "placementHeight" : 22.596, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 1.0, + "contentHeight" : 22.596, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/Certifications[4]/CertificationsBand[1]/CertificationsSeparator_3[12]", + "entityName" : "CertificationsSeparator_3", + "entityKind" : "SectionNode", + "parentPath" : "SerifHeadlineCv[0]/Certifications[4]/CertificationsBand[1]", + "childIndex" : 12, + "depth" : 4, + "layer" : 4, + "computedX" : 350.829, + "computedY" : 134.574, + "placementX" : 350.829, + "placementY" : 134.574, + "placementWidth" : 0.698, + "placementHeight" : 22.596, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 0.698, + "contentHeight" : 22.596, + "margin" : { + "top" : 0.0, + "right" : 212.067, + "bottom" : 0.0, + "left" : 319.147 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/Certifications[4]/CertificationsBand[1]/CertificationsSeparator_3[12]/CertificationsSeparatorLine_3[0]", + "entityName" : "CertificationsSeparatorLine_3", + "entityKind" : "LineNode", + "parentPath" : "SerifHeadlineCv[0]/Certifications[4]/CertificationsBand[1]/CertificationsSeparator_3[12]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 350.829, + "computedY" : 134.574, + "placementX" : 350.829, + "placementY" : 134.574, + "placementWidth" : 1.0, + "placementHeight" : 22.596, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 1.0, + "contentHeight" : 22.596, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/Certifications[4]/CertificationsBand[1]/CertificationsSeparator_4[13]", + "entityName" : "CertificationsSeparator_4", + "entityKind" : "SectionNode", + "parentPath" : "SerifHeadlineCv[0]/Certifications[4]/CertificationsBand[1]", + "childIndex" : 13, + "depth" : 4, + "layer" : 4, + "computedX" : 457.211, + "computedY" : 134.574, + "placementX" : 457.211, + "placementY" : 134.574, + "placementWidth" : 0.698, + "placementHeight" : 22.596, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 0.698, + "contentHeight" : 22.596, + "margin" : { + "top" : 0.0, + "right" : 105.685, + "bottom" : 0.0, + "left" : 425.529 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/Certifications[4]/CertificationsBand[1]/CertificationsSeparator_4[13]/CertificationsSeparatorLine_4[0]", + "entityName" : "CertificationsSeparatorLine_4", + "entityKind" : "LineNode", + "parentPath" : "SerifHeadlineCv[0]/Certifications[4]/CertificationsBand[1]/CertificationsSeparator_4[13]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 457.211, + "computedY" : 134.574, + "placementX" : 457.211, + "placementY" : 134.574, + "placementWidth" : 1.0, + "placementHeight" : 22.596, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 1.0, + "contentHeight" : 22.596, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/Achievements[5]", + "entityName" : "Achievements", + "entityKind" : "SectionNode", + "parentPath" : "SerifHeadlineCv[0]", + "childIndex" : 5, + "depth" : 2, + "layer" : 2, + "computedX" : 31.682, + "computedY" : 43.938, + "placementX" : 31.682, + "placementY" : 43.938, + "placementWidth" : 531.911, + "placementHeight" : 65.9, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 531.911, + "contentHeight" : 65.9, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/Achievements[5]/SectionHeading_Achievements[0]", + "entityName" : "SectionHeading_Achievements", + "entityKind" : "ShapeContainerNode", + "parentPath" : "SerifHeadlineCv[0]/Achievements[5]", + "childIndex" : 0, + "depth" : 3, + "layer" : 3, + "computedX" : 31.682, + "computedY" : 97.242, + "placementX" : 31.682, + "placementY" : 97.242, + "placementWidth" : 531.911, + "placementHeight" : 12.595, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 531.911, + "contentHeight" : 12.595, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/Achievements[5]/SectionHeading_Achievements[0]/HeadingDash_Achievements[0]", + "entityName" : "HeadingDash_Achievements", + "entityKind" : "LineNode", + "parentPath" : "SerifHeadlineCv[0]/Achievements[5]/SectionHeading_Achievements[0]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 14.533, + "computedY" : 102.668, + "placementX" : 14.533, + "placementY" : 102.668, + "placementWidth" : 12.789, + "placementHeight" : 1.744, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 12.789, + "contentHeight" : 1.744, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/Achievements[5]/SectionHeading_Achievements[0]/SectionTitle_Achievements[1]", + "entityName" : "SectionTitle_Achievements", + "entityKind" : "ParagraphNode", + "parentPath" : "SerifHeadlineCv[0]/Achievements[5]/SectionHeading_Achievements[0]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 36.623, + "computedY" : 97.727, + "placementX" : 36.623, + "placementY" : 97.727, + "placementWidth" : 92.449, + "placementHeight" : 11.626, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 92.449, + "contentHeight" : 11.626, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/Achievements[5]/SectionHeading_Achievements[0]/HeadingTail_Achievements[2]", + "entityName" : "HeadingTail_Achievements", + "entityKind" : "LineNode", + "parentPath" : "SerifHeadlineCv[0]/Achievements[5]/SectionHeading_Achievements[0]", + "childIndex" : 2, + "depth" : 4, + "layer" : 4, + "computedX" : 139.679, + "computedY" : 103.04, + "placementX" : 139.679, + "placementY" : 103.04, + "placementWidth" : 423.915, + "placementHeight" : 1.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 423.915, + "contentHeight" : 1.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/Achievements[5]/AchievementsBand[1]", + "entityName" : "AchievementsBand", + "entityKind" : "LayerStackNode", + "parentPath" : "SerifHeadlineCv[0]/Achievements[5]", + "childIndex" : 1, + "depth" : 3, + "layer" : 3, + "computedX" : 31.682, + "computedY" : 43.938, + "placementX" : 31.682, + "placementY" : 43.938, + "placementWidth" : 531.911, + "placementHeight" : 36.181, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 531.911, + "contentHeight" : 36.181, + "margin" : { + "top" : 17.123, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/Achievements[5]/AchievementsBand[1]/Achievement_EngineeroftheYear2023[0]", + "entityName" : "Achievement_EngineeroftheYear2023", + "entityKind" : "SectionNode", + "parentPath" : "SerifHeadlineCv[0]/Achievements[5]/AchievementsBand[1]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 31.682, + "computedY" : 43.938, + "placementX" : 31.682, + "placementY" : 43.938, + "placementWidth" : 169.868, + "placementHeight" : 36.181, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 169.868, + "contentHeight" : 36.181, + "margin" : { + "top" : 0.0, + "right" : 360.421, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 3.93, + "right" : 0.0, + "bottom" : 0.0, + "left" : 33.135 + } + }, { + "path" : "SerifHeadlineCv[0]/Achievements[5]/AchievementsBand[1]/Achievement_EngineeroftheYear2023[0]/AchievementTitle_EngineeroftheYear2023[0]", + "entityName" : "AchievementTitle_EngineeroftheYear2023", + "entityKind" : "ParagraphNode", + "parentPath" : "SerifHeadlineCv[0]/Achievements[5]/AchievementsBand[1]/Achievement_EngineeroftheYear2023[0]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 64.818, + "computedY" : 66.501, + "placementX" : 64.818, + "placementY" : 66.501, + "placementWidth" : 100.989, + "placementHeight" : 9.689, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 100.989, + "contentHeight" : 9.689, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 4.714, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/Achievements[5]/AchievementsBand[1]/Achievement_EngineeroftheYear2023[0]/AchievementBody_EngineeroftheYear2023[1]", + "entityName" : "AchievementBody_EngineeroftheYear2023", + "entityKind" : "ParagraphNode", + "parentPath" : "SerifHeadlineCv[0]/Achievements[5]/AchievementsBand[1]/Achievement_EngineeroftheYear2023[0]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 64.818, + "computedY" : 43.938, + "placementX" : 64.818, + "placementY" : 43.938, + "placementWidth" : 136.732, + "placementHeight" : 17.849, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 136.732, + "contentHeight" : 17.849, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/Achievements[5]/AchievementsBand[1]/Achievement_PerformanceOptimization[1]", + "entityName" : "Achievement_PerformanceOptimization", + "entityKind" : "SectionNode", + "parentPath" : "SerifHeadlineCv[0]/Achievements[5]/AchievementsBand[1]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 208.986, + "computedY" : 43.938, + "placementX" : 208.986, + "placementY" : 43.938, + "placementWidth" : 166.571, + "placementHeight" : 36.181, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 166.571, + "contentHeight" : 36.181, + "margin" : { + "top" : 0.0, + "right" : 183.117, + "bottom" : 0.0, + "left" : 177.304 + }, + "padding" : { + "top" : 3.93, + "right" : 0.0, + "bottom" : 0.0, + "left" : 40.111 + } + }, { + "path" : "SerifHeadlineCv[0]/Achievements[5]/AchievementsBand[1]/Achievement_PerformanceOptimization[1]/AchievementTitle_PerformanceOptimization[0]", + "entityName" : "AchievementTitle_PerformanceOptimization", + "entityKind" : "ParagraphNode", + "parentPath" : "SerifHeadlineCv[0]/Achievements[5]/AchievementsBand[1]/Achievement_PerformanceOptimization[1]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 249.097, + "computedY" : 66.501, + "placementX" : 249.097, + "placementY" : 66.501, + "placementWidth" : 95.507, + "placementHeight" : 9.689, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 95.507, + "contentHeight" : 9.689, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 4.714, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/Achievements[5]/AchievementsBand[1]/Achievement_PerformanceOptimization[1]/AchievementBody_PerformanceOptimization[1]", + "entityName" : "AchievementBody_PerformanceOptimization", + "entityKind" : "ParagraphNode", + "parentPath" : "SerifHeadlineCv[0]/Achievements[5]/AchievementsBand[1]/Achievement_PerformanceOptimization[1]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 249.097, + "computedY" : 43.938, + "placementX" : 249.097, + "placementY" : 43.938, + "placementWidth" : 126.46, + "placementHeight" : 17.849, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 126.46, + "contentHeight" : 17.849, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/Achievements[5]/AchievementsBand[1]/Achievement_SuccessfulMigration[2]", + "entityName" : "Achievement_SuccessfulMigration", + "entityKind" : "SectionNode", + "parentPath" : "SerifHeadlineCv[0]/Achievements[5]/AchievementsBand[1]", + "childIndex" : 2, + "depth" : 4, + "layer" : 4, + "computedX" : 386.29, + "computedY" : 43.938, + "placementX" : 386.29, + "placementY" : 43.938, + "placementWidth" : 170.361, + "placementHeight" : 36.181, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 170.361, + "contentHeight" : 36.181, + "margin" : { + "top" : 0.0, + "right" : 5.813, + "bottom" : 0.0, + "left" : 354.608 + }, + "padding" : { + "top" : 3.93, + "right" : 0.0, + "bottom" : 0.0, + "left" : 40.111 + } + }, { + "path" : "SerifHeadlineCv[0]/Achievements[5]/AchievementsBand[1]/Achievement_SuccessfulMigration[2]/AchievementTitle_SuccessfulMigration[0]", + "entityName" : "AchievementTitle_SuccessfulMigration", + "entityKind" : "ParagraphNode", + "parentPath" : "SerifHeadlineCv[0]/Achievements[5]/AchievementsBand[1]/Achievement_SuccessfulMigration[2]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 426.401, + "computedY" : 66.501, + "placementX" : 426.401, + "placementY" : 66.501, + "placementWidth" : 74.918, + "placementHeight" : 9.689, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 74.918, + "contentHeight" : 9.689, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 4.714, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/Achievements[5]/AchievementsBand[1]/Achievement_SuccessfulMigration[2]/AchievementBody_SuccessfulMigration[1]", + "entityName" : "AchievementBody_SuccessfulMigration", + "entityKind" : "ParagraphNode", + "parentPath" : "SerifHeadlineCv[0]/Achievements[5]/AchievementsBand[1]/Achievement_SuccessfulMigration[2]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 426.401, + "computedY" : 43.938, + "placementX" : 426.401, + "placementY" : 43.938, + "placementWidth" : 130.25, + "placementHeight" : 17.849, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 130.25, + "contentHeight" : 17.849, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/Achievements[5]/AchievementsBand[1]/AchievementsGlyph_0[3]", + "entityName" : "AchievementsGlyph_0", + "entityKind" : "SectionNode", + "parentPath" : "SerifHeadlineCv[0]/Achievements[5]/AchievementsBand[1]", + "childIndex" : 3, + "depth" : 4, + "layer" : 4, + "computedX" : 30.52, + "computedY" : 56.866, + "placementX" : 30.52, + "placementY" : 56.866, + "placementWidth" : 23.253, + "placementHeight" : 23.253, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 23.253, + "contentHeight" : 23.253, + "margin" : { + "top" : 0.0, + "right" : 354.608, + "bottom" : 0.0, + "left" : -1.163 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/Achievements[5]/AchievementsBand[1]/AchievementsGlyph_0[3]/AchievementGlyph_EngineeroftheYear2023[0]", + "entityName" : "AchievementGlyph_EngineeroftheYear2023", + "entityKind" : "ImageNode", + "parentPath" : "SerifHeadlineCv[0]/Achievements[5]/AchievementsBand[1]/AchievementsGlyph_0[3]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 30.52, + "computedY" : 56.866, + "placementX" : 30.52, + "placementY" : 56.866, + "placementWidth" : 23.253, + "placementHeight" : 23.253, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 23.253, + "contentHeight" : 23.253, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/Achievements[5]/AchievementsBand[1]/AchievementsGlyph_1[4]", + "entityName" : "AchievementsGlyph_1", + "entityKind" : "SectionNode", + "parentPath" : "SerifHeadlineCv[0]/Achievements[5]/AchievementsBand[1]", + "childIndex" : 4, + "depth" : 4, + "layer" : 4, + "computedX" : 214.799, + "computedY" : 56.866, + "placementX" : 214.799, + "placementY" : 56.866, + "placementWidth" : 23.253, + "placementHeight" : 23.253, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 23.253, + "contentHeight" : 23.253, + "margin" : { + "top" : 0.0, + "right" : 177.304, + "bottom" : 0.0, + "left" : 183.117 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/Achievements[5]/AchievementsBand[1]/AchievementsGlyph_1[4]/AchievementGlyph_PerformanceOptimization[0]", + "entityName" : "AchievementGlyph_PerformanceOptimization", + "entityKind" : "ImageNode", + "parentPath" : "SerifHeadlineCv[0]/Achievements[5]/AchievementsBand[1]/AchievementsGlyph_1[4]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 214.799, + "computedY" : 56.866, + "placementX" : 214.799, + "placementY" : 56.866, + "placementWidth" : 23.253, + "placementHeight" : 23.253, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 23.253, + "contentHeight" : 23.253, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/Achievements[5]/AchievementsBand[1]/AchievementsGlyph_2[5]", + "entityName" : "AchievementsGlyph_2", + "entityKind" : "SectionNode", + "parentPath" : "SerifHeadlineCv[0]/Achievements[5]/AchievementsBand[1]", + "childIndex" : 5, + "depth" : 4, + "layer" : 4, + "computedX" : 392.103, + "computedY" : 56.866, + "placementX" : 392.103, + "placementY" : 56.866, + "placementWidth" : 23.253, + "placementHeight" : 23.253, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 23.253, + "contentHeight" : 23.253, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 360.421 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/Achievements[5]/AchievementsBand[1]/AchievementsGlyph_2[5]/AchievementGlyph_SuccessfulMigration[0]", + "entityName" : "AchievementGlyph_SuccessfulMigration", + "entityKind" : "ImageNode", + "parentPath" : "SerifHeadlineCv[0]/Achievements[5]/AchievementsBand[1]/AchievementsGlyph_2[5]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 392.103, + "computedY" : 56.866, + "placementX" : 392.103, + "placementY" : 56.866, + "placementWidth" : 23.253, + "placementHeight" : 23.253, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 23.253, + "contentHeight" : 23.253, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/Achievements[5]/AchievementsBand[1]/AchievementsSeparator_1[6]", + "entityName" : "AchievementsSeparator_1", + "entityKind" : "SectionNode", + "parentPath" : "SerifHeadlineCv[0]/Achievements[5]/AchievementsBand[1]", + "childIndex" : 6, + "depth" : 4, + "layer" : 4, + "computedX" : 208.986, + "computedY" : 49.664, + "placementX" : 208.986, + "placementY" : 49.664, + "placementWidth" : 0.698, + "placementHeight" : 30.456, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 0.698, + "contentHeight" : 30.456, + "margin" : { + "top" : 0.0, + "right" : 353.91, + "bottom" : 0.0, + "left" : 177.304 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/Achievements[5]/AchievementsBand[1]/AchievementsSeparator_1[6]/AchievementsSeparatorLine_1[0]", + "entityName" : "AchievementsSeparatorLine_1", + "entityKind" : "LineNode", + "parentPath" : "SerifHeadlineCv[0]/Achievements[5]/AchievementsBand[1]/AchievementsSeparator_1[6]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 208.986, + "computedY" : 49.664, + "placementX" : 208.986, + "placementY" : 49.664, + "placementWidth" : 1.0, + "placementHeight" : 30.456, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 1.0, + "contentHeight" : 30.456, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/Achievements[5]/AchievementsBand[1]/AchievementsSeparator_2[7]", + "entityName" : "AchievementsSeparator_2", + "entityKind" : "SectionNode", + "parentPath" : "SerifHeadlineCv[0]/Achievements[5]/AchievementsBand[1]", + "childIndex" : 7, + "depth" : 4, + "layer" : 4, + "computedX" : 386.29, + "computedY" : 49.664, + "placementX" : 386.29, + "placementY" : 49.664, + "placementWidth" : 0.698, + "placementHeight" : 30.456, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 0.698, + "contentHeight" : 30.456, + "margin" : { + "top" : 0.0, + "right" : 176.606, + "bottom" : 0.0, + "left" : 354.608 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SerifHeadlineCv[0]/Achievements[5]/AchievementsBand[1]/AchievementsSeparator_2[7]/AchievementsSeparatorLine_2[0]", + "entityName" : "AchievementsSeparatorLine_2", + "entityKind" : "LineNode", + "parentPath" : "SerifHeadlineCv[0]/Achievements[5]/AchievementsBand[1]/AchievementsSeparator_2[7]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 386.29, + "computedY" : 49.664, + "placementX" : 386.29, + "placementY" : 49.664, + "placementWidth" : 1.0, + "placementHeight" : 30.456, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 1.0, + "contentHeight" : 30.456, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + } ] +} diff --git a/qa/src/test/resources/layout-snapshots/canonical-templates/cv-v2/slate_orange_layout.json b/qa/src/test/resources/layout-snapshots/canonical-templates/cv-v2/slate_orange_layout.json new file mode 100644 index 000000000..018394bb0 --- /dev/null +++ b/qa/src/test/resources/layout-snapshots/canonical-templates/cv-v2/slate_orange_layout.json @@ -0,0 +1,3287 @@ +{ + "formatVersion" : "2.0", + "canvas" : { + "pageWidth" : 595.276, + "pageHeight" : 841.89, + "innerWidth" : 595.276, + "innerHeight" : 841.89, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, + "totalPages" : 1, + "nodes" : [ { + "path" : "SlateOrangeCv[0]", + "entityName" : "SlateOrangeCv", + "entityKind" : "ContainerNode", + "parentPath" : null, + "childIndex" : 0, + "depth" : 1, + "layer" : 1, + "computedX" : 0.0, + "computedY" : 2.07, + "placementX" : 0.0, + "placementY" : 2.07, + "placementWidth" : 595.276, + "placementHeight" : 839.82, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 595.276, + "contentHeight" : 839.82, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SlateOrangeCv[0]/Masthead[0]", + "entityName" : "Masthead", + "entityKind" : "RowNode", + "parentPath" : "SlateOrangeCv[0]", + "childIndex" : 0, + "depth" : 2, + "layer" : 2, + "computedX" : 0.0, + "computedY" : 705.29, + "placementX" : 0.0, + "placementY" : 705.29, + "placementWidth" : 595.276, + "placementHeight" : 136.6, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 595.276, + "contentHeight" : 136.6, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SlateOrangeCv[0]/Masthead[0]/Monogram[0]", + "entityName" : "Monogram", + "entityKind" : "SectionNode", + "parentPath" : "SlateOrangeCv[0]/Masthead[0]", + "childIndex" : 0, + "depth" : 3, + "layer" : 3, + "computedX" : 0.0, + "computedY" : 745.39, + "placementX" : 0.0, + "placementY" : 745.39, + "placementWidth" : 85.8, + "placementHeight" : 96.5, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 85.8, + "contentHeight" : 96.5, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 35.8, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SlateOrangeCv[0]/Masthead[0]/Monogram[0]/Initials[0]", + "entityName" : "Initials", + "entityKind" : "ParagraphNode", + "parentPath" : "SlateOrangeCv[0]/Masthead[0]/Monogram[0]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 0.0, + "computedY" : 748.79, + "placementX" : 0.0, + "placementY" : 748.79, + "placementWidth" : 85.8, + "placementHeight" : 57.3, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 85.8, + "contentHeight" : 57.3, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 1.7, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SlateOrangeCv[0]/Masthead[0]/Monogram[0]/Align[1]", + "entityName" : null, + "entityKind" : "Align", + "parentPath" : "SlateOrangeCv[0]/Masthead[0]/Monogram[0]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 0.0, + "computedY" : 745.39, + "placementX" : 0.0, + "placementY" : 745.39, + "placementWidth" : 85.8, + "placementHeight" : 1.7, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 85.8, + "contentHeight" : 1.7, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SlateOrangeCv[0]/Masthead[0]/Monogram[0]/Align[1]/MonogramRule[0]", + "entityName" : "MonogramRule", + "entityKind" : "LineNode", + "parentPath" : "SlateOrangeCv[0]/Masthead[0]/Monogram[0]/Align[1]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 24.3, + "computedY" : 745.39, + "placementX" : 24.3, + "placementY" : 745.39, + "placementWidth" : 37.2, + "placementHeight" : 1.7, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 37.2, + "contentHeight" : 1.7, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SlateOrangeCv[0]/Masthead[0]/Identity[1]", + "entityName" : "Identity", + "entityKind" : "SectionNode", + "parentPath" : "SlateOrangeCv[0]/Masthead[0]", + "childIndex" : 1, + "depth" : 3, + "layer" : 3, + "computedX" : 85.8, + "computedY" : 728.135, + "placementX" : 85.8, + "placementY" : 728.135, + "placementWidth" : 268.949, + "placementHeight" : 113.755, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 268.949, + "contentHeight" : 113.755, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 27.4, + "right" : 0.0, + "bottom" : 0.0, + "left" : 22.0 + } + }, { + "path" : "SlateOrangeCv[0]/Masthead[0]/Identity[1]/Name[0]", + "entityName" : "Name", + "entityKind" : "ParagraphNode", + "parentPath" : "SlateOrangeCv[0]/Masthead[0]/Identity[1]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 107.8, + "computedY" : 768.077, + "placementX" : 107.8, + "placementY" : 768.077, + "placementWidth" : 236.196, + "placementHeight" : 46.413, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 236.196, + "contentHeight" : 46.413, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 1.5, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SlateOrangeCv[0]/Masthead[0]/Identity[1]/RoleLine[1]", + "entityName" : "RoleLine", + "entityKind" : "ParagraphNode", + "parentPath" : "SlateOrangeCv[0]/Masthead[0]/Identity[1]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 107.8, + "computedY" : 747.668, + "placementX" : 107.8, + "placementY" : 747.668, + "placementWidth" : 157.064, + "placementHeight" : 18.909, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 157.064, + "contentHeight" : 18.909, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 7.5, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SlateOrangeCv[0]/Masthead[0]/Identity[1]/Tagline[2]", + "entityName" : "Tagline", + "entityKind" : "ParagraphNode", + "parentPath" : "SlateOrangeCv[0]/Masthead[0]/Identity[1]", + "childIndex" : 2, + "depth" : 4, + "layer" : 4, + "computedX" : 107.8, + "computedY" : 728.135, + "placementX" : 107.8, + "placementY" : 728.135, + "placementWidth" : 246.949, + "placementHeight" : 12.033, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 246.949, + "contentHeight" : 12.033, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SlateOrangeCv[0]/Masthead[0]/MastheadHairline[2]", + "entityName" : "MastheadHairline", + "entityKind" : "SectionNode", + "parentPath" : "SlateOrangeCv[0]/Masthead[0]", + "childIndex" : 2, + "depth" : 3, + "layer" : 3, + "computedX" : 383.7, + "computedY" : 705.29, + "placementX" : 383.7, + "placementY" : 705.29, + "placementWidth" : 1.2, + "placementHeight" : 136.6, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 1.2, + "contentHeight" : 136.6, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 34.4, + "right" : 0.0, + "bottom" : 23.2, + "left" : 0.0 + } + }, { + "path" : "SlateOrangeCv[0]/Masthead[0]/MastheadHairline[2]/MastheadHairlineRule[0]", + "entityName" : "MastheadHairlineRule", + "entityKind" : "LineNode", + "parentPath" : "SlateOrangeCv[0]/Masthead[0]/MastheadHairline[2]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 383.7, + "computedY" : 728.49, + "placementX" : 383.7, + "placementY" : 728.49, + "placementWidth" : 1.2, + "placementHeight" : 79.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 1.2, + "contentHeight" : 79.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SlateOrangeCv[0]/Masthead[0]/Contact[3]", + "entityName" : "Contact", + "entityKind" : "SectionNode", + "parentPath" : "SlateOrangeCv[0]/Masthead[0]", + "childIndex" : 3, + "depth" : 3, + "layer" : 3, + "computedX" : 384.9, + "computedY" : 730.27, + "placementX" : 384.9, + "placementY" : 730.27, + "placementWidth" : 169.418, + "placementHeight" : 111.62, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 169.418, + "contentHeight" : 111.62, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 36.8, + "right" : 0.0, + "bottom" : 0.0, + "left" : 21.4 + } + }, { + "path" : "SlateOrangeCv[0]/Masthead[0]/Contact[3]/Contact_0_phone[0]", + "entityName" : "Contact_0_phone", + "entityKind" : "ParagraphNode", + "parentPath" : "SlateOrangeCv[0]/Masthead[0]/Contact[3]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 406.3, + "computedY" : 793.57, + "placementX" : 406.3, + "placementY" : 793.57, + "placementWidth" : 71.659, + "placementHeight" : 11.52, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 71.659, + "contentHeight" : 11.52, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 9.58, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SlateOrangeCv[0]/Masthead[0]/Contact[3]/Contact_1_email[1]", + "entityName" : "Contact_1_email", + "entityKind" : "ParagraphNode", + "parentPath" : "SlateOrangeCv[0]/Masthead[0]/Contact[3]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 406.3, + "computedY" : 772.47, + "placementX" : 406.3, + "placementY" : 772.47, + "placementWidth" : 119.15, + "placementHeight" : 11.52, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 119.15, + "contentHeight" : 11.52, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 9.58, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SlateOrangeCv[0]/Masthead[0]/Contact[3]/Contact_2_linkedin[2]", + "entityName" : "Contact_2_linkedin", + "entityKind" : "ParagraphNode", + "parentPath" : "SlateOrangeCv[0]/Masthead[0]/Contact[3]", + "childIndex" : 2, + "depth" : 4, + "layer" : 4, + "computedX" : 406.3, + "computedY" : 751.37, + "placementX" : 406.3, + "placementY" : 751.37, + "placementWidth" : 50.635, + "placementHeight" : 11.52, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 50.635, + "contentHeight" : 11.52, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 9.58, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SlateOrangeCv[0]/Masthead[0]/Contact[3]/Contact_3_location[3]", + "entityName" : "Contact_3_location", + "entityKind" : "ParagraphNode", + "parentPath" : "SlateOrangeCv[0]/Masthead[0]/Contact[3]", + "childIndex" : 3, + "depth" : 4, + "layer" : 4, + "computedX" : 406.3, + "computedY" : 730.27, + "placementX" : 406.3, + "placementY" : 730.27, + "placementWidth" : 148.018, + "placementHeight" : 11.52, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 148.018, + "contentHeight" : 11.52, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SlateOrangeCv[0]/Body[1]", + "entityName" : "Body", + "entityKind" : "RowNode", + "parentPath" : "SlateOrangeCv[0]", + "childIndex" : 1, + "depth" : 2, + "layer" : 2, + "computedX" : 0.0, + "computedY" : 2.07, + "placementX" : 0.0, + "placementY" : 2.07, + "placementWidth" : 595.276, + "placementHeight" : 703.22, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 595.276, + "contentHeight" : 703.22, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SlateOrangeCv[0]/Body[1]/Sidebar[0]", + "entityName" : "Sidebar", + "entityKind" : "SectionNode", + "parentPath" : "SlateOrangeCv[0]/Body[1]", + "childIndex" : 0, + "depth" : 3, + "layer" : 3, + "computedX" : 0.0, + "computedY" : 7.94, + "placementX" : 0.0, + "placementY" : 7.94, + "placementWidth" : 204.177, + "placementHeight" : 697.35, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 204.177, + "contentHeight" : 697.35, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 18.5, + "right" : 22.85, + "bottom" : 22.0, + "left" : 22.0 + } + }, { + "path" : "SlateOrangeCv[0]/Body[1]/Sidebar[0]/Competencies[0]", + "entityName" : "Competencies", + "entityKind" : "SectionNode", + "parentPath" : "SlateOrangeCv[0]/Body[1]/Sidebar[0]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 22.0, + "computedY" : 437.715, + "placementX" : 22.0, + "placementY" : 437.715, + "placementWidth" : 159.327, + "placementHeight" : 249.075, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 159.327, + "contentHeight" : 249.075, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SlateOrangeCv[0]/Body[1]/Sidebar[0]/Competencies[0]/CompetenciesHeading[0]", + "entityName" : "CompetenciesHeading", + "entityKind" : "ParagraphNode", + "parentPath" : "SlateOrangeCv[0]/Body[1]/Sidebar[0]/Competencies[0]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 22.0, + "computedY" : 672.465, + "placementX" : 22.0, + "placementY" : 672.465, + "placementWidth" : 96.538, + "placementHeight" : 14.325, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 96.538, + "contentHeight" : 14.325, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 3.5, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SlateOrangeCv[0]/Body[1]/Sidebar[0]/Competencies[0]/CompetenciesHeadingRule[1]", + "entityName" : "CompetenciesHeadingRule", + "entityKind" : "LineNode", + "parentPath" : "SlateOrangeCv[0]/Body[1]/Sidebar[0]/Competencies[0]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 22.0, + "computedY" : 667.965, + "placementX" : 22.0, + "placementY" : 667.965, + "placementWidth" : 159.327, + "placementHeight" : 1.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 159.327, + "contentHeight" : 1.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SlateOrangeCv[0]/Body[1]/Sidebar[0]/Competencies[0]/CompetencyTable[2]", + "entityName" : "CompetencyTable", + "entityKind" : "TableNode", + "parentPath" : "SlateOrangeCv[0]/Body[1]/Sidebar[0]/Competencies[0]", + "childIndex" : 2, + "depth" : 5, + "layer" : 5, + "computedX" : 22.0, + "computedY" : 437.715, + "placementX" : 22.0, + "placementY" : 437.715, + "placementWidth" : 159.317, + "placementHeight" : 220.75, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 159.317, + "contentHeight" : 220.75, + "margin" : { + "top" : 9.5, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SlateOrangeCv[0]/Body[1]/Sidebar[0]/Achievements[1]", + "entityName" : "Achievements", + "entityKind" : "SectionNode", + "parentPath" : "SlateOrangeCv[0]/Body[1]/Sidebar[0]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 22.0, + "computedY" : 242.11, + "placementX" : 22.0, + "placementY" : 242.11, + "placementWidth" : 159.327, + "placementHeight" : 172.005, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 159.327, + "contentHeight" : 172.005, + "margin" : { + "top" : 23.6, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SlateOrangeCv[0]/Body[1]/Sidebar[0]/Achievements[1]/AchievementsHeading[0]", + "entityName" : "AchievementsHeading", + "entityKind" : "ParagraphNode", + "parentPath" : "SlateOrangeCv[0]/Body[1]/Sidebar[0]/Achievements[1]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 22.0, + "computedY" : 399.79, + "placementX" : 22.0, + "placementY" : 399.79, + "placementWidth" : 116.675, + "placementHeight" : 14.325, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 116.675, + "contentHeight" : 14.325, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 3.5, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SlateOrangeCv[0]/Body[1]/Sidebar[0]/Achievements[1]/AchievementsHeadingRule[1]", + "entityName" : "AchievementsHeadingRule", + "entityKind" : "LineNode", + "parentPath" : "SlateOrangeCv[0]/Body[1]/Sidebar[0]/Achievements[1]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 22.0, + "computedY" : 395.29, + "placementX" : 22.0, + "placementY" : 395.29, + "placementWidth" : 159.327, + "placementHeight" : 1.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 159.327, + "contentHeight" : 1.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SlateOrangeCv[0]/Body[1]/Sidebar[0]/Achievements[1]/AchievementTable[2]", + "entityName" : "AchievementTable", + "entityKind" : "TableNode", + "parentPath" : "SlateOrangeCv[0]/Body[1]/Sidebar[0]/Achievements[1]", + "childIndex" : 2, + "depth" : 5, + "layer" : 5, + "computedX" : 22.0, + "computedY" : 242.11, + "placementX" : 22.0, + "placementY" : 242.11, + "placementWidth" : 159.317, + "placementHeight" : 143.68, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 159.317, + "contentHeight" : 143.68, + "margin" : { + "top" : 9.5, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SlateOrangeCv[0]/Body[1]/Sidebar[0]/Languages[2]", + "entityName" : "Languages", + "entityKind" : "SectionNode", + "parentPath" : "SlateOrangeCv[0]/Body[1]/Sidebar[0]", + "childIndex" : 2, + "depth" : 4, + "layer" : 4, + "computedX" : 22.0, + "computedY" : 153.125, + "placementX" : 22.0, + "placementY" : 153.125, + "placementWidth" : 159.327, + "placementHeight" : 71.685, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 159.327, + "contentHeight" : 71.685, + "margin" : { + "top" : 17.3, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SlateOrangeCv[0]/Body[1]/Sidebar[0]/Languages[2]/LanguagesHeading[0]", + "entityName" : "LanguagesHeading", + "entityKind" : "ParagraphNode", + "parentPath" : "SlateOrangeCv[0]/Body[1]/Sidebar[0]/Languages[2]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 22.0, + "computedY" : 210.485, + "placementX" : 22.0, + "placementY" : 210.485, + "placementWidth" : 55.663, + "placementHeight" : 14.325, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 55.663, + "contentHeight" : 14.325, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 3.5, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SlateOrangeCv[0]/Body[1]/Sidebar[0]/Languages[2]/LanguagesHeadingRule[1]", + "entityName" : "LanguagesHeadingRule", + "entityKind" : "LineNode", + "parentPath" : "SlateOrangeCv[0]/Body[1]/Sidebar[0]/Languages[2]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 22.0, + "computedY" : 205.985, + "placementX" : 22.0, + "placementY" : 205.985, + "placementWidth" : 159.327, + "placementHeight" : 1.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 159.327, + "contentHeight" : 1.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SlateOrangeCv[0]/Body[1]/Sidebar[0]/Languages[2]/LanguageTable[2]", + "entityName" : "LanguageTable", + "entityKind" : "TableNode", + "parentPath" : "SlateOrangeCv[0]/Body[1]/Sidebar[0]/Languages[2]", + "childIndex" : 2, + "depth" : 5, + "layer" : 5, + "computedX" : 22.0, + "computedY" : 153.125, + "placementX" : 22.0, + "placementY" : 153.125, + "placementWidth" : 159.317, + "placementHeight" : 43.36, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 159.317, + "contentHeight" : 43.36, + "margin" : { + "top" : 9.5, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SlateOrangeCv[0]/Body[1]/Sidebar[0]/Additional[3]", + "entityName" : "Additional", + "entityKind" : "SectionNode", + "parentPath" : "SlateOrangeCv[0]/Body[1]/Sidebar[0]", + "childIndex" : 3, + "depth" : 4, + "layer" : 4, + "computedX" : 22.0, + "computedY" : 29.94, + "placementX" : 22.0, + "placementY" : 29.94, + "placementWidth" : 159.327, + "placementHeight" : 99.285, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 159.327, + "contentHeight" : 99.285, + "margin" : { + "top" : 23.9, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SlateOrangeCv[0]/Body[1]/Sidebar[0]/Additional[3]/AdditionalHeading[0]", + "entityName" : "AdditionalHeading", + "entityKind" : "ParagraphNode", + "parentPath" : "SlateOrangeCv[0]/Body[1]/Sidebar[0]/Additional[3]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 22.0, + "computedY" : 114.9, + "placementX" : 22.0, + "placementY" : 114.9, + "placementWidth" : 126.337, + "placementHeight" : 14.325, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 126.337, + "contentHeight" : 14.325, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 3.5, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SlateOrangeCv[0]/Body[1]/Sidebar[0]/Additional[3]/AdditionalHeadingRule[1]", + "entityName" : "AdditionalHeadingRule", + "entityKind" : "LineNode", + "parentPath" : "SlateOrangeCv[0]/Body[1]/Sidebar[0]/Additional[3]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 22.0, + "computedY" : 110.4, + "placementX" : 22.0, + "placementY" : 110.4, + "placementWidth" : 159.327, + "placementHeight" : 1.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 159.327, + "contentHeight" : 1.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SlateOrangeCv[0]/Body[1]/Sidebar[0]/Additional[3]/AdditionalTable[2]", + "entityName" : "AdditionalTable", + "entityKind" : "TableNode", + "parentPath" : "SlateOrangeCv[0]/Body[1]/Sidebar[0]/Additional[3]", + "childIndex" : 2, + "depth" : 5, + "layer" : 5, + "computedX" : 22.0, + "computedY" : 29.94, + "placementX" : 22.0, + "placementY" : 29.94, + "placementWidth" : 159.317, + "placementHeight" : 70.96, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 159.317, + "contentHeight" : 70.96, + "margin" : { + "top" : 9.5, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SlateOrangeCv[0]/Body[1]/MainColumn[1]", + "entityName" : "MainColumn", + "entityKind" : "SectionNode", + "parentPath" : "SlateOrangeCv[0]/Body[1]", + "childIndex" : 1, + "depth" : 3, + "layer" : 3, + "computedX" : 204.177, + "computedY" : 2.07, + "placementX" : 204.177, + "placementY" : 2.07, + "placementWidth" : 391.099, + "placementHeight" : 703.22, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 391.099, + "contentHeight" : 703.22, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 18.5, + "right" : 22.0, + "bottom" : 22.0, + "left" : 22.85 + } + }, { + "path" : "SlateOrangeCv[0]/Body[1]/MainColumn[1]/Profile[0]", + "entityName" : "Profile", + "entityKind" : "SectionNode", + "parentPath" : "SlateOrangeCv[0]/Body[1]/MainColumn[1]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 227.027, + "computedY" : 588.365, + "placementX" : 227.027, + "placementY" : 588.365, + "placementWidth" : 346.249, + "placementHeight" : 98.425, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 346.249, + "contentHeight" : 98.425, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SlateOrangeCv[0]/Body[1]/MainColumn[1]/Profile[0]/ProfileHeading[0]", + "entityName" : "ProfileHeading", + "entityKind" : "ParagraphNode", + "parentPath" : "SlateOrangeCv[0]/Body[1]/MainColumn[1]/Profile[0]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 227.027, + "computedY" : 672.465, + "placementX" : 227.027, + "placementY" : 672.465, + "placementWidth" : 109.138, + "placementHeight" : 14.325, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 109.138, + "contentHeight" : 14.325, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 3.5, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SlateOrangeCv[0]/Body[1]/MainColumn[1]/Profile[0]/ProfileHeadingRule[1]", + "entityName" : "ProfileHeadingRule", + "entityKind" : "LineNode", + "parentPath" : "SlateOrangeCv[0]/Body[1]/MainColumn[1]/Profile[0]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 227.027, + "computedY" : 667.965, + "placementX" : 227.027, + "placementY" : 667.965, + "placementWidth" : 346.249, + "placementHeight" : 1.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 346.249, + "contentHeight" : 1.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SlateOrangeCv[0]/Body[1]/MainColumn[1]/Profile[0]/ProfileBody[2]", + "entityName" : "ProfileBody", + "entityKind" : "ParagraphNode", + "parentPath" : "SlateOrangeCv[0]/Body[1]/MainColumn[1]/Profile[0]", + "childIndex" : 2, + "depth" : 5, + "layer" : 5, + "computedX" : 227.027, + "computedY" : 588.365, + "placementX" : 227.027, + "placementY" : 588.365, + "placementWidth" : 333.296, + "placementHeight" : 66.6, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 333.296, + "contentHeight" : 66.6, + "margin" : { + "top" : 13.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SlateOrangeCv[0]/Body[1]/MainColumn[1]/Experience[1]", + "entityName" : "Experience", + "entityKind" : "SectionNode", + "parentPath" : "SlateOrangeCv[0]/Body[1]/MainColumn[1]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 227.027, + "computedY" : 114.63, + "placementX" : 227.027, + "placementY" : 114.63, + "placementWidth" : 346.249, + "placementHeight" : 450.735, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 346.249, + "contentHeight" : 450.735, + "margin" : { + "top" : 23.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SlateOrangeCv[0]/Body[1]/MainColumn[1]/Experience[1]/ExperienceHeading[0]", + "entityName" : "ExperienceHeading", + "entityKind" : "ParagraphNode", + "parentPath" : "SlateOrangeCv[0]/Body[1]/MainColumn[1]/Experience[1]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 227.027, + "computedY" : 551.04, + "placementX" : 227.027, + "placementY" : 551.04, + "placementWidth" : 126.563, + "placementHeight" : 14.325, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 126.563, + "contentHeight" : 14.325, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 3.5, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SlateOrangeCv[0]/Body[1]/MainColumn[1]/Experience[1]/ExperienceHeadingRule[1]", + "entityName" : "ExperienceHeadingRule", + "entityKind" : "LineNode", + "parentPath" : "SlateOrangeCv[0]/Body[1]/MainColumn[1]/Experience[1]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 227.027, + "computedY" : 546.54, + "placementX" : 227.027, + "placementY" : 546.54, + "placementWidth" : 346.249, + "placementHeight" : 1.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 346.249, + "contentHeight" : 1.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SlateOrangeCv[0]/Body[1]/MainColumn[1]/Experience[1]/ExperienceEntries[2]", + "entityName" : "ExperienceEntries", + "entityKind" : "LayerStackNode", + "parentPath" : "SlateOrangeCv[0]/Body[1]/MainColumn[1]/Experience[1]", + "childIndex" : 2, + "depth" : 5, + "layer" : 5, + "computedX" : 227.027, + "computedY" : 114.63, + "placementX" : 227.027, + "placementY" : 114.63, + "placementWidth" : 346.249, + "placementHeight" : 431.91, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 346.249, + "contentHeight" : 431.91, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SlateOrangeCv[0]/Body[1]/MainColumn[1]/Experience[1]/ExperienceEntries[2]/ExperienceEntriesHolder[0]", + "entityName" : "ExperienceEntriesHolder", + "entityKind" : "SectionNode", + "parentPath" : "SlateOrangeCv[0]/Body[1]/MainColumn[1]/Experience[1]/ExperienceEntries[2]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 224.227, + "computedY" : 114.63, + "placementX" : 224.227, + "placementY" : 114.63, + "placementWidth" : 349.049, + "placementHeight" : 418.91, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 349.049, + "contentHeight" : 418.91, + "margin" : { + "top" : 13.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : -2.8 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SlateOrangeCv[0]/Body[1]/MainColumn[1]/Experience[1]/ExperienceEntries[2]/ExperienceEntriesHolder[0]/SectionNode[0]", + "entityName" : null, + "entityKind" : "SectionNode", + "parentPath" : "SlateOrangeCv[0]/Body[1]/MainColumn[1]/Experience[1]/ExperienceEntries[2]/ExperienceEntriesHolder[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 224.227, + "computedY" : 114.63, + "placementX" : 224.227, + "placementY" : 114.63, + "placementWidth" : 349.049, + "placementHeight" : 418.91, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 349.049, + "contentHeight" : 418.91, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SlateOrangeCv[0]/Body[1]/MainColumn[1]/Experience[1]/ExperienceEntries[2]/ExperienceEntriesHolder[0]/SectionNode[0]/LayoutAnchorNode[0]", + "entityName" : null, + "entityKind" : "LayoutAnchorNode", + "parentPath" : "SlateOrangeCv[0]/Body[1]/MainColumn[1]/Experience[1]/ExperienceEntries[2]/ExperienceEntriesHolder[0]/SectionNode[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 224.227, + "computedY" : 427.46, + "placementX" : 224.227, + "placementY" : 427.46, + "placementWidth" : 349.049, + "placementHeight" : 106.08, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 349.049, + "contentHeight" : 106.08, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SlateOrangeCv[0]/Body[1]/MainColumn[1]/Experience[1]/ExperienceEntries[2]/ExperienceEntriesHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]", + "entityName" : null, + "entityKind" : "SectionNode", + "parentPath" : "SlateOrangeCv[0]/Body[1]/MainColumn[1]/Experience[1]/ExperienceEntries[2]/ExperienceEntriesHolder[0]/SectionNode[0]/LayoutAnchorNode[0]", + "childIndex" : 0, + "depth" : 9, + "layer" : 9, + "computedX" : 224.227, + "computedY" : 427.46, + "placementX" : 224.227, + "placementY" : 427.46, + "placementWidth" : 349.049, + "placementHeight" : 106.08, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 349.049, + "contentHeight" : 106.08, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 18.4, + "left" : 0.0 + } + }, { + "path" : "SlateOrangeCv[0]/Body[1]/MainColumn[1]/Experience[1]/ExperienceEntries[2]/ExperienceEntriesHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]", + "entityName" : null, + "entityKind" : "HorizontalBandsNode", + "parentPath" : "SlateOrangeCv[0]/Body[1]/MainColumn[1]/Experience[1]/ExperienceEntries[2]/ExperienceEntriesHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]", + "childIndex" : 0, + "depth" : 10, + "layer" : 10, + "computedX" : 224.227, + "computedY" : 445.86, + "placementX" : 224.227, + "placementY" : 445.86, + "placementWidth" : 349.049, + "placementHeight" : 87.68, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 349.049, + "contentHeight" : 87.68, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SlateOrangeCv[0]/Body[1]/MainColumn[1]/Experience[1]/ExperienceEntries[2]/ExperienceEntriesHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]", + "entityName" : null, + "entityKind" : "RowNode", + "parentPath" : "SlateOrangeCv[0]/Body[1]/MainColumn[1]/Experience[1]/ExperienceEntries[2]/ExperienceEntriesHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]", + "childIndex" : 0, + "depth" : 11, + "layer" : 11, + "computedX" : 224.227, + "computedY" : 445.86, + "placementX" : 224.227, + "placementY" : 445.86, + "placementWidth" : 349.049, + "placementHeight" : 87.68, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 349.049, + "contentHeight" : 87.68, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SlateOrangeCv[0]/Body[1]/MainColumn[1]/Experience[1]/ExperienceEntries[2]/ExperienceEntriesHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]", + "entityName" : null, + "entityKind" : "SectionNode", + "parentPath" : "SlateOrangeCv[0]/Body[1]/MainColumn[1]/Experience[1]/ExperienceEntries[2]/ExperienceEntriesHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]", + "childIndex" : 0, + "depth" : 12, + "layer" : 12, + "computedX" : 224.227, + "computedY" : 526.82, + "placementX" : 224.227, + "placementY" : 526.82, + "placementWidth" : 5.6, + "placementHeight" : 6.72, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 5.6, + "contentHeight" : 6.72, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SlateOrangeCv[0]/Body[1]/MainColumn[1]/Experience[1]/ExperienceEntries[2]/ExperienceEntriesHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]", + "entityName" : null, + "entityKind" : "Align", + "parentPath" : "SlateOrangeCv[0]/Body[1]/MainColumn[1]/Experience[1]/ExperienceEntries[2]/ExperienceEntriesHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]", + "childIndex" : 0, + "depth" : 13, + "layer" : 13, + "computedX" : 224.227, + "computedY" : 526.82, + "placementX" : 224.227, + "placementY" : 526.82, + "placementWidth" : 5.6, + "placementHeight" : 6.72, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 5.6, + "contentHeight" : 6.72, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SlateOrangeCv[0]/Body[1]/MainColumn[1]/Experience[1]/ExperienceEntries[2]/ExperienceEntriesHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]/LayoutAnchorNode[0]", + "entityName" : null, + "entityKind" : "LayoutAnchorNode", + "parentPath" : "SlateOrangeCv[0]/Body[1]/MainColumn[1]/Experience[1]/ExperienceEntries[2]/ExperienceEntriesHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]", + "childIndex" : 0, + "depth" : 14, + "layer" : 14, + "computedX" : 224.227, + "computedY" : 526.82, + "placementX" : 224.227, + "placementY" : 526.82, + "placementWidth" : 5.6, + "placementHeight" : 6.72, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 5.6, + "contentHeight" : 6.72, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SlateOrangeCv[0]/Body[1]/MainColumn[1]/Experience[1]/ExperienceEntries[2]/ExperienceEntriesHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]/LayoutAnchorNode[0]/marker[0]", + "entityName" : "marker", + "entityKind" : "CanvasLayerNode", + "parentPath" : "SlateOrangeCv[0]/Body[1]/MainColumn[1]/Experience[1]/ExperienceEntries[2]/ExperienceEntriesHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]/LayoutAnchorNode[0]", + "childIndex" : 0, + "depth" : 15, + "layer" : 15, + "computedX" : 224.227, + "computedY" : 526.82, + "placementX" : 224.227, + "placementY" : 526.82, + "placementWidth" : 5.6, + "placementHeight" : 6.72, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 5.6, + "contentHeight" : 6.72, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SlateOrangeCv[0]/Body[1]/MainColumn[1]/Experience[1]/ExperienceEntries[2]/ExperienceEntriesHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]/LayoutAnchorNode[0]/marker[0]/SectionNode[0]", + "entityName" : null, + "entityKind" : "SectionNode", + "parentPath" : "SlateOrangeCv[0]/Body[1]/MainColumn[1]/Experience[1]/ExperienceEntries[2]/ExperienceEntriesHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]/LayoutAnchorNode[0]/marker[0]", + "childIndex" : 0, + "depth" : 16, + "layer" : 16, + "computedX" : 224.227, + "computedY" : 526.82, + "placementX" : 224.227, + "placementY" : 526.82, + "placementWidth" : 5.6, + "placementHeight" : 6.72, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 5.6, + "contentHeight" : 6.72, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SlateOrangeCv[0]/Body[1]/MainColumn[1]/Experience[1]/ExperienceEntries[2]/ExperienceEntriesHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]/LayoutAnchorNode[0]/marker[0]/SectionNode[0]/Marker_0[0]", + "entityName" : "Marker_0", + "entityKind" : "ParagraphNode", + "parentPath" : "SlateOrangeCv[0]/Body[1]/MainColumn[1]/Experience[1]/ExperienceEntries[2]/ExperienceEntriesHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]/LayoutAnchorNode[0]/marker[0]/SectionNode[0]", + "childIndex" : 0, + "depth" : 17, + "layer" : 17, + "computedX" : 224.227, + "computedY" : 526.82, + "placementX" : 224.227, + "placementY" : 526.82, + "placementWidth" : 5.6, + "placementHeight" : 6.72, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 5.6, + "contentHeight" : 6.72, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SlateOrangeCv[0]/Body[1]/MainColumn[1]/Experience[1]/ExperienceEntries[2]/ExperienceEntriesHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]", + "entityName" : null, + "entityKind" : "SectionNode", + "parentPath" : "SlateOrangeCv[0]/Body[1]/MainColumn[1]/Experience[1]/ExperienceEntries[2]/ExperienceEntriesHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]", + "childIndex" : 1, + "depth" : 12, + "layer" : 12, + "computedX" : 240.027, + "computedY" : 445.86, + "placementX" : 240.027, + "placementY" : 445.86, + "placementWidth" : 333.239, + "placementHeight" : 87.68, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 333.239, + "contentHeight" : 87.68, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SlateOrangeCv[0]/Body[1]/MainColumn[1]/Experience[1]/ExperienceEntries[2]/ExperienceEntriesHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]/RoleLine_0[0]", + "entityName" : "RoleLine_0", + "entityKind" : "LayerStackNode", + "parentPath" : "SlateOrangeCv[0]/Body[1]/MainColumn[1]/Experience[1]/ExperienceEntries[2]/ExperienceEntriesHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]", + "childIndex" : 0, + "depth" : 13, + "layer" : 13, + "computedX" : 240.027, + "computedY" : 521.12, + "placementX" : 240.027, + "placementY" : 521.12, + "placementWidth" : 333.239, + "placementHeight" : 12.42, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 333.239, + "contentHeight" : 12.42, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SlateOrangeCv[0]/Body[1]/MainColumn[1]/Experience[1]/ExperienceEntries[2]/ExperienceEntriesHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]/RoleLine_0[0]/RoleTable_0[0]", + "entityName" : "RoleTable_0", + "entityKind" : "TableNode", + "parentPath" : "SlateOrangeCv[0]/Body[1]/MainColumn[1]/Experience[1]/ExperienceEntries[2]/ExperienceEntriesHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]/RoleLine_0[0]", + "childIndex" : 0, + "depth" : 14, + "layer" : 14, + "computedX" : 240.027, + "computedY" : 521.12, + "placementX" : 240.027, + "placementY" : 521.12, + "placementWidth" : 333.239, + "placementHeight" : 12.42, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 333.239, + "contentHeight" : 12.42, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SlateOrangeCv[0]/Body[1]/MainColumn[1]/Experience[1]/ExperienceEntries[2]/ExperienceEntriesHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]/Employer_0[1]", + "entityName" : "Employer_0", + "entityKind" : "ParagraphNode", + "parentPath" : "SlateOrangeCv[0]/Body[1]/MainColumn[1]/Experience[1]/ExperienceEntries[2]/ExperienceEntriesHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]", + "childIndex" : 1, + "depth" : 13, + "layer" : 13, + "computedX" : 240.027, + "computedY" : 508.2, + "placementX" : 240.027, + "placementY" : 508.2, + "placementWidth" : 116.025, + "placementHeight" : 10.92, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 116.025, + "contentHeight" : 10.92, + "margin" : { + "top" : 2.0, + "right" : 0.0, + "bottom" : 6.6, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SlateOrangeCv[0]/Body[1]/MainColumn[1]/Experience[1]/ExperienceEntries[2]/ExperienceEntriesHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]/Highlights_0[2]", + "entityName" : "Highlights_0", + "entityKind" : "ListNode", + "parentPath" : "SlateOrangeCv[0]/Body[1]/MainColumn[1]/Experience[1]/ExperienceEntries[2]/ExperienceEntriesHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]", + "childIndex" : 2, + "depth" : 13, + "layer" : 13, + "computedX" : 240.027, + "computedY" : 445.86, + "placementX" : 240.027, + "placementY" : 445.86, + "placementWidth" : 319.311, + "placementHeight" : 55.74, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 319.311, + "contentHeight" : 55.74, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SlateOrangeCv[0]/Body[1]/MainColumn[1]/Experience[1]/ExperienceEntries[2]/ExperienceEntriesHolder[0]/SectionNode[0]/LayoutAnchorNode[1]", + "entityName" : null, + "entityKind" : "LayoutAnchorNode", + "parentPath" : "SlateOrangeCv[0]/Body[1]/MainColumn[1]/Experience[1]/ExperienceEntries[2]/ExperienceEntriesHolder[0]/SectionNode[0]", + "childIndex" : 1, + "depth" : 8, + "layer" : 8, + "computedX" : 224.227, + "computedY" : 321.38, + "placementX" : 224.227, + "placementY" : 321.38, + "placementWidth" : 349.049, + "placementHeight" : 106.08, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 349.049, + "contentHeight" : 106.08, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SlateOrangeCv[0]/Body[1]/MainColumn[1]/Experience[1]/ExperienceEntries[2]/ExperienceEntriesHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]", + "entityName" : null, + "entityKind" : "SectionNode", + "parentPath" : "SlateOrangeCv[0]/Body[1]/MainColumn[1]/Experience[1]/ExperienceEntries[2]/ExperienceEntriesHolder[0]/SectionNode[0]/LayoutAnchorNode[1]", + "childIndex" : 0, + "depth" : 9, + "layer" : 9, + "computedX" : 224.227, + "computedY" : 321.38, + "placementX" : 224.227, + "placementY" : 321.38, + "placementWidth" : 349.049, + "placementHeight" : 106.08, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 349.049, + "contentHeight" : 106.08, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 18.4, + "left" : 0.0 + } + }, { + "path" : "SlateOrangeCv[0]/Body[1]/MainColumn[1]/Experience[1]/ExperienceEntries[2]/ExperienceEntriesHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]", + "entityName" : null, + "entityKind" : "HorizontalBandsNode", + "parentPath" : "SlateOrangeCv[0]/Body[1]/MainColumn[1]/Experience[1]/ExperienceEntries[2]/ExperienceEntriesHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]", + "childIndex" : 0, + "depth" : 10, + "layer" : 10, + "computedX" : 224.227, + "computedY" : 339.78, + "placementX" : 224.227, + "placementY" : 339.78, + "placementWidth" : 349.049, + "placementHeight" : 87.68, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 349.049, + "contentHeight" : 87.68, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SlateOrangeCv[0]/Body[1]/MainColumn[1]/Experience[1]/ExperienceEntries[2]/ExperienceEntriesHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]", + "entityName" : null, + "entityKind" : "RowNode", + "parentPath" : "SlateOrangeCv[0]/Body[1]/MainColumn[1]/Experience[1]/ExperienceEntries[2]/ExperienceEntriesHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]", + "childIndex" : 0, + "depth" : 11, + "layer" : 11, + "computedX" : 224.227, + "computedY" : 339.78, + "placementX" : 224.227, + "placementY" : 339.78, + "placementWidth" : 349.049, + "placementHeight" : 87.68, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 349.049, + "contentHeight" : 87.68, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SlateOrangeCv[0]/Body[1]/MainColumn[1]/Experience[1]/ExperienceEntries[2]/ExperienceEntriesHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]", + "entityName" : null, + "entityKind" : "SectionNode", + "parentPath" : "SlateOrangeCv[0]/Body[1]/MainColumn[1]/Experience[1]/ExperienceEntries[2]/ExperienceEntriesHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]", + "childIndex" : 0, + "depth" : 12, + "layer" : 12, + "computedX" : 224.227, + "computedY" : 420.74, + "placementX" : 224.227, + "placementY" : 420.74, + "placementWidth" : 5.6, + "placementHeight" : 6.72, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 5.6, + "contentHeight" : 6.72, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SlateOrangeCv[0]/Body[1]/MainColumn[1]/Experience[1]/ExperienceEntries[2]/ExperienceEntriesHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]", + "entityName" : null, + "entityKind" : "Align", + "parentPath" : "SlateOrangeCv[0]/Body[1]/MainColumn[1]/Experience[1]/ExperienceEntries[2]/ExperienceEntriesHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]", + "childIndex" : 0, + "depth" : 13, + "layer" : 13, + "computedX" : 224.227, + "computedY" : 420.74, + "placementX" : 224.227, + "placementY" : 420.74, + "placementWidth" : 5.6, + "placementHeight" : 6.72, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 5.6, + "contentHeight" : 6.72, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SlateOrangeCv[0]/Body[1]/MainColumn[1]/Experience[1]/ExperienceEntries[2]/ExperienceEntriesHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]/LayoutAnchorNode[0]", + "entityName" : null, + "entityKind" : "LayoutAnchorNode", + "parentPath" : "SlateOrangeCv[0]/Body[1]/MainColumn[1]/Experience[1]/ExperienceEntries[2]/ExperienceEntriesHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]", + "childIndex" : 0, + "depth" : 14, + "layer" : 14, + "computedX" : 224.227, + "computedY" : 420.74, + "placementX" : 224.227, + "placementY" : 420.74, + "placementWidth" : 5.6, + "placementHeight" : 6.72, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 5.6, + "contentHeight" : 6.72, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SlateOrangeCv[0]/Body[1]/MainColumn[1]/Experience[1]/ExperienceEntries[2]/ExperienceEntriesHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]/LayoutAnchorNode[0]/marker[0]", + "entityName" : "marker", + "entityKind" : "CanvasLayerNode", + "parentPath" : "SlateOrangeCv[0]/Body[1]/MainColumn[1]/Experience[1]/ExperienceEntries[2]/ExperienceEntriesHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]/LayoutAnchorNode[0]", + "childIndex" : 0, + "depth" : 15, + "layer" : 15, + "computedX" : 224.227, + "computedY" : 420.74, + "placementX" : 224.227, + "placementY" : 420.74, + "placementWidth" : 5.6, + "placementHeight" : 6.72, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 5.6, + "contentHeight" : 6.72, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SlateOrangeCv[0]/Body[1]/MainColumn[1]/Experience[1]/ExperienceEntries[2]/ExperienceEntriesHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]/LayoutAnchorNode[0]/marker[0]/SectionNode[0]", + "entityName" : null, + "entityKind" : "SectionNode", + "parentPath" : "SlateOrangeCv[0]/Body[1]/MainColumn[1]/Experience[1]/ExperienceEntries[2]/ExperienceEntriesHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]/LayoutAnchorNode[0]/marker[0]", + "childIndex" : 0, + "depth" : 16, + "layer" : 16, + "computedX" : 224.227, + "computedY" : 420.74, + "placementX" : 224.227, + "placementY" : 420.74, + "placementWidth" : 5.6, + "placementHeight" : 6.72, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 5.6, + "contentHeight" : 6.72, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SlateOrangeCv[0]/Body[1]/MainColumn[1]/Experience[1]/ExperienceEntries[2]/ExperienceEntriesHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]/LayoutAnchorNode[0]/marker[0]/SectionNode[0]/Marker_1[0]", + "entityName" : "Marker_1", + "entityKind" : "ParagraphNode", + "parentPath" : "SlateOrangeCv[0]/Body[1]/MainColumn[1]/Experience[1]/ExperienceEntries[2]/ExperienceEntriesHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]/LayoutAnchorNode[0]/marker[0]/SectionNode[0]", + "childIndex" : 0, + "depth" : 17, + "layer" : 17, + "computedX" : 224.227, + "computedY" : 420.74, + "placementX" : 224.227, + "placementY" : 420.74, + "placementWidth" : 5.6, + "placementHeight" : 6.72, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 5.6, + "contentHeight" : 6.72, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SlateOrangeCv[0]/Body[1]/MainColumn[1]/Experience[1]/ExperienceEntries[2]/ExperienceEntriesHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]", + "entityName" : null, + "entityKind" : "SectionNode", + "parentPath" : "SlateOrangeCv[0]/Body[1]/MainColumn[1]/Experience[1]/ExperienceEntries[2]/ExperienceEntriesHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]", + "childIndex" : 1, + "depth" : 12, + "layer" : 12, + "computedX" : 240.027, + "computedY" : 339.78, + "placementX" : 240.027, + "placementY" : 339.78, + "placementWidth" : 333.239, + "placementHeight" : 87.68, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 333.239, + "contentHeight" : 87.68, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SlateOrangeCv[0]/Body[1]/MainColumn[1]/Experience[1]/ExperienceEntries[2]/ExperienceEntriesHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]/RoleLine_1[0]", + "entityName" : "RoleLine_1", + "entityKind" : "LayerStackNode", + "parentPath" : "SlateOrangeCv[0]/Body[1]/MainColumn[1]/Experience[1]/ExperienceEntries[2]/ExperienceEntriesHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]", + "childIndex" : 0, + "depth" : 13, + "layer" : 13, + "computedX" : 240.027, + "computedY" : 415.04, + "placementX" : 240.027, + "placementY" : 415.04, + "placementWidth" : 333.239, + "placementHeight" : 12.42, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 333.239, + "contentHeight" : 12.42, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SlateOrangeCv[0]/Body[1]/MainColumn[1]/Experience[1]/ExperienceEntries[2]/ExperienceEntriesHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]/RoleLine_1[0]/RoleTable_1[0]", + "entityName" : "RoleTable_1", + "entityKind" : "TableNode", + "parentPath" : "SlateOrangeCv[0]/Body[1]/MainColumn[1]/Experience[1]/ExperienceEntries[2]/ExperienceEntriesHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]/RoleLine_1[0]", + "childIndex" : 0, + "depth" : 14, + "layer" : 14, + "computedX" : 240.027, + "computedY" : 415.04, + "placementX" : 240.027, + "placementY" : 415.04, + "placementWidth" : 333.239, + "placementHeight" : 12.42, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 333.239, + "contentHeight" : 12.42, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SlateOrangeCv[0]/Body[1]/MainColumn[1]/Experience[1]/ExperienceEntries[2]/ExperienceEntriesHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]/Employer_1[1]", + "entityName" : "Employer_1", + "entityKind" : "ParagraphNode", + "parentPath" : "SlateOrangeCv[0]/Body[1]/MainColumn[1]/Experience[1]/ExperienceEntries[2]/ExperienceEntriesHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]", + "childIndex" : 1, + "depth" : 13, + "layer" : 13, + "computedX" : 240.027, + "computedY" : 402.12, + "placementX" : 240.027, + "placementY" : 402.12, + "placementWidth" : 106.379, + "placementHeight" : 10.92, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 106.379, + "contentHeight" : 10.92, + "margin" : { + "top" : 2.0, + "right" : 0.0, + "bottom" : 6.6, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SlateOrangeCv[0]/Body[1]/MainColumn[1]/Experience[1]/ExperienceEntries[2]/ExperienceEntriesHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]/Highlights_1[2]", + "entityName" : "Highlights_1", + "entityKind" : "ListNode", + "parentPath" : "SlateOrangeCv[0]/Body[1]/MainColumn[1]/Experience[1]/ExperienceEntries[2]/ExperienceEntriesHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]", + "childIndex" : 2, + "depth" : 13, + "layer" : 13, + "computedX" : 240.027, + "computedY" : 339.78, + "placementX" : 240.027, + "placementY" : 339.78, + "placementWidth" : 320.307, + "placementHeight" : 55.74, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 320.307, + "contentHeight" : 55.74, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SlateOrangeCv[0]/Body[1]/MainColumn[1]/Experience[1]/ExperienceEntries[2]/ExperienceEntriesHolder[0]/SectionNode[0]/LayoutAnchorNode[2]", + "entityName" : null, + "entityKind" : "LayoutAnchorNode", + "parentPath" : "SlateOrangeCv[0]/Body[1]/MainColumn[1]/Experience[1]/ExperienceEntries[2]/ExperienceEntriesHolder[0]/SectionNode[0]", + "childIndex" : 2, + "depth" : 8, + "layer" : 8, + "computedX" : 224.227, + "computedY" : 202.31, + "placementX" : 224.227, + "placementY" : 202.31, + "placementWidth" : 349.049, + "placementHeight" : 119.07, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 349.049, + "contentHeight" : 119.07, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SlateOrangeCv[0]/Body[1]/MainColumn[1]/Experience[1]/ExperienceEntries[2]/ExperienceEntriesHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]", + "entityName" : null, + "entityKind" : "SectionNode", + "parentPath" : "SlateOrangeCv[0]/Body[1]/MainColumn[1]/Experience[1]/ExperienceEntries[2]/ExperienceEntriesHolder[0]/SectionNode[0]/LayoutAnchorNode[2]", + "childIndex" : 0, + "depth" : 9, + "layer" : 9, + "computedX" : 224.227, + "computedY" : 202.31, + "placementX" : 224.227, + "placementY" : 202.31, + "placementWidth" : 349.049, + "placementHeight" : 119.07, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 349.049, + "contentHeight" : 119.07, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 18.4, + "left" : 0.0 + } + }, { + "path" : "SlateOrangeCv[0]/Body[1]/MainColumn[1]/Experience[1]/ExperienceEntries[2]/ExperienceEntriesHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]", + "entityName" : null, + "entityKind" : "HorizontalBandsNode", + "parentPath" : "SlateOrangeCv[0]/Body[1]/MainColumn[1]/Experience[1]/ExperienceEntries[2]/ExperienceEntriesHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]", + "childIndex" : 0, + "depth" : 10, + "layer" : 10, + "computedX" : 224.227, + "computedY" : 220.71, + "placementX" : 224.227, + "placementY" : 220.71, + "placementWidth" : 349.049, + "placementHeight" : 100.67, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 349.049, + "contentHeight" : 100.67, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SlateOrangeCv[0]/Body[1]/MainColumn[1]/Experience[1]/ExperienceEntries[2]/ExperienceEntriesHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]", + "entityName" : null, + "entityKind" : "RowNode", + "parentPath" : "SlateOrangeCv[0]/Body[1]/MainColumn[1]/Experience[1]/ExperienceEntries[2]/ExperienceEntriesHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]", + "childIndex" : 0, + "depth" : 11, + "layer" : 11, + "computedX" : 224.227, + "computedY" : 220.71, + "placementX" : 224.227, + "placementY" : 220.71, + "placementWidth" : 349.049, + "placementHeight" : 100.67, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 349.049, + "contentHeight" : 100.67, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SlateOrangeCv[0]/Body[1]/MainColumn[1]/Experience[1]/ExperienceEntries[2]/ExperienceEntriesHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]", + "entityName" : null, + "entityKind" : "SectionNode", + "parentPath" : "SlateOrangeCv[0]/Body[1]/MainColumn[1]/Experience[1]/ExperienceEntries[2]/ExperienceEntriesHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]", + "childIndex" : 0, + "depth" : 12, + "layer" : 12, + "computedX" : 224.227, + "computedY" : 314.66, + "placementX" : 224.227, + "placementY" : 314.66, + "placementWidth" : 5.6, + "placementHeight" : 6.72, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 5.6, + "contentHeight" : 6.72, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SlateOrangeCv[0]/Body[1]/MainColumn[1]/Experience[1]/ExperienceEntries[2]/ExperienceEntriesHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]", + "entityName" : null, + "entityKind" : "Align", + "parentPath" : "SlateOrangeCv[0]/Body[1]/MainColumn[1]/Experience[1]/ExperienceEntries[2]/ExperienceEntriesHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]", + "childIndex" : 0, + "depth" : 13, + "layer" : 13, + "computedX" : 224.227, + "computedY" : 314.66, + "placementX" : 224.227, + "placementY" : 314.66, + "placementWidth" : 5.6, + "placementHeight" : 6.72, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 5.6, + "contentHeight" : 6.72, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SlateOrangeCv[0]/Body[1]/MainColumn[1]/Experience[1]/ExperienceEntries[2]/ExperienceEntriesHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]/LayoutAnchorNode[0]", + "entityName" : null, + "entityKind" : "LayoutAnchorNode", + "parentPath" : "SlateOrangeCv[0]/Body[1]/MainColumn[1]/Experience[1]/ExperienceEntries[2]/ExperienceEntriesHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]", + "childIndex" : 0, + "depth" : 14, + "layer" : 14, + "computedX" : 224.227, + "computedY" : 314.66, + "placementX" : 224.227, + "placementY" : 314.66, + "placementWidth" : 5.6, + "placementHeight" : 6.72, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 5.6, + "contentHeight" : 6.72, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SlateOrangeCv[0]/Body[1]/MainColumn[1]/Experience[1]/ExperienceEntries[2]/ExperienceEntriesHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]/LayoutAnchorNode[0]/marker[0]", + "entityName" : "marker", + "entityKind" : "CanvasLayerNode", + "parentPath" : "SlateOrangeCv[0]/Body[1]/MainColumn[1]/Experience[1]/ExperienceEntries[2]/ExperienceEntriesHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]/LayoutAnchorNode[0]", + "childIndex" : 0, + "depth" : 15, + "layer" : 15, + "computedX" : 224.227, + "computedY" : 314.66, + "placementX" : 224.227, + "placementY" : 314.66, + "placementWidth" : 5.6, + "placementHeight" : 6.72, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 5.6, + "contentHeight" : 6.72, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SlateOrangeCv[0]/Body[1]/MainColumn[1]/Experience[1]/ExperienceEntries[2]/ExperienceEntriesHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]/LayoutAnchorNode[0]/marker[0]/SectionNode[0]", + "entityName" : null, + "entityKind" : "SectionNode", + "parentPath" : "SlateOrangeCv[0]/Body[1]/MainColumn[1]/Experience[1]/ExperienceEntries[2]/ExperienceEntriesHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]/LayoutAnchorNode[0]/marker[0]", + "childIndex" : 0, + "depth" : 16, + "layer" : 16, + "computedX" : 224.227, + "computedY" : 314.66, + "placementX" : 224.227, + "placementY" : 314.66, + "placementWidth" : 5.6, + "placementHeight" : 6.72, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 5.6, + "contentHeight" : 6.72, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SlateOrangeCv[0]/Body[1]/MainColumn[1]/Experience[1]/ExperienceEntries[2]/ExperienceEntriesHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]/LayoutAnchorNode[0]/marker[0]/SectionNode[0]/Marker_2[0]", + "entityName" : "Marker_2", + "entityKind" : "ParagraphNode", + "parentPath" : "SlateOrangeCv[0]/Body[1]/MainColumn[1]/Experience[1]/ExperienceEntries[2]/ExperienceEntriesHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]/LayoutAnchorNode[0]/marker[0]/SectionNode[0]", + "childIndex" : 0, + "depth" : 17, + "layer" : 17, + "computedX" : 224.227, + "computedY" : 314.66, + "placementX" : 224.227, + "placementY" : 314.66, + "placementWidth" : 5.6, + "placementHeight" : 6.72, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 5.6, + "contentHeight" : 6.72, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SlateOrangeCv[0]/Body[1]/MainColumn[1]/Experience[1]/ExperienceEntries[2]/ExperienceEntriesHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]", + "entityName" : null, + "entityKind" : "SectionNode", + "parentPath" : "SlateOrangeCv[0]/Body[1]/MainColumn[1]/Experience[1]/ExperienceEntries[2]/ExperienceEntriesHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]", + "childIndex" : 1, + "depth" : 12, + "layer" : 12, + "computedX" : 240.027, + "computedY" : 220.71, + "placementX" : 240.027, + "placementY" : 220.71, + "placementWidth" : 333.239, + "placementHeight" : 100.67, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 333.239, + "contentHeight" : 100.67, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SlateOrangeCv[0]/Body[1]/MainColumn[1]/Experience[1]/ExperienceEntries[2]/ExperienceEntriesHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]/RoleLine_2[0]", + "entityName" : "RoleLine_2", + "entityKind" : "LayerStackNode", + "parentPath" : "SlateOrangeCv[0]/Body[1]/MainColumn[1]/Experience[1]/ExperienceEntries[2]/ExperienceEntriesHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]", + "childIndex" : 0, + "depth" : 13, + "layer" : 13, + "computedX" : 240.027, + "computedY" : 308.96, + "placementX" : 240.027, + "placementY" : 308.96, + "placementWidth" : 333.239, + "placementHeight" : 12.42, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 333.239, + "contentHeight" : 12.42, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SlateOrangeCv[0]/Body[1]/MainColumn[1]/Experience[1]/ExperienceEntries[2]/ExperienceEntriesHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]/RoleLine_2[0]/RoleTable_2[0]", + "entityName" : "RoleTable_2", + "entityKind" : "TableNode", + "parentPath" : "SlateOrangeCv[0]/Body[1]/MainColumn[1]/Experience[1]/ExperienceEntries[2]/ExperienceEntriesHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]/RoleLine_2[0]", + "childIndex" : 0, + "depth" : 14, + "layer" : 14, + "computedX" : 240.027, + "computedY" : 308.96, + "placementX" : 240.027, + "placementY" : 308.96, + "placementWidth" : 333.239, + "placementHeight" : 12.42, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 333.239, + "contentHeight" : 12.42, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SlateOrangeCv[0]/Body[1]/MainColumn[1]/Experience[1]/ExperienceEntries[2]/ExperienceEntriesHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]/Employer_2[1]", + "entityName" : "Employer_2", + "entityKind" : "ParagraphNode", + "parentPath" : "SlateOrangeCv[0]/Body[1]/MainColumn[1]/Experience[1]/ExperienceEntries[2]/ExperienceEntriesHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]", + "childIndex" : 1, + "depth" : 13, + "layer" : 13, + "computedX" : 240.027, + "computedY" : 296.04, + "placementX" : 240.027, + "placementY" : 296.04, + "placementWidth" : 100.528, + "placementHeight" : 10.92, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 100.528, + "contentHeight" : 10.92, + "margin" : { + "top" : 2.0, + "right" : 0.0, + "bottom" : 6.6, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SlateOrangeCv[0]/Body[1]/MainColumn[1]/Experience[1]/ExperienceEntries[2]/ExperienceEntriesHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]/Highlights_2[2]", + "entityName" : "Highlights_2", + "entityKind" : "ListNode", + "parentPath" : "SlateOrangeCv[0]/Body[1]/MainColumn[1]/Experience[1]/ExperienceEntries[2]/ExperienceEntriesHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]", + "childIndex" : 2, + "depth" : 13, + "layer" : 13, + "computedX" : 240.027, + "computedY" : 220.71, + "placementX" : 240.027, + "placementY" : 220.71, + "placementWidth" : 307.972, + "placementHeight" : 68.73, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 307.972, + "contentHeight" : 68.73, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SlateOrangeCv[0]/Body[1]/MainColumn[1]/Experience[1]/ExperienceEntries[2]/ExperienceEntriesHolder[0]/SectionNode[0]/LayoutAnchorNode[3]", + "entityName" : null, + "entityKind" : "LayoutAnchorNode", + "parentPath" : "SlateOrangeCv[0]/Body[1]/MainColumn[1]/Experience[1]/ExperienceEntries[2]/ExperienceEntriesHolder[0]/SectionNode[0]", + "childIndex" : 3, + "depth" : 8, + "layer" : 8, + "computedX" : 224.227, + "computedY" : 114.63, + "placementX" : 224.227, + "placementY" : 114.63, + "placementWidth" : 349.049, + "placementHeight" : 87.68, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 349.049, + "contentHeight" : 87.68, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SlateOrangeCv[0]/Body[1]/MainColumn[1]/Experience[1]/ExperienceEntries[2]/ExperienceEntriesHolder[0]/SectionNode[0]/LayoutAnchorNode[3]/SectionNode[0]", + "entityName" : null, + "entityKind" : "SectionNode", + "parentPath" : "SlateOrangeCv[0]/Body[1]/MainColumn[1]/Experience[1]/ExperienceEntries[2]/ExperienceEntriesHolder[0]/SectionNode[0]/LayoutAnchorNode[3]", + "childIndex" : 0, + "depth" : 9, + "layer" : 9, + "computedX" : 224.227, + "computedY" : 114.63, + "placementX" : 224.227, + "placementY" : 114.63, + "placementWidth" : 349.049, + "placementHeight" : 87.68, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 349.049, + "contentHeight" : 87.68, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SlateOrangeCv[0]/Body[1]/MainColumn[1]/Experience[1]/ExperienceEntries[2]/ExperienceEntriesHolder[0]/SectionNode[0]/LayoutAnchorNode[3]/SectionNode[0]/HorizontalBandsNode[0]", + "entityName" : null, + "entityKind" : "HorizontalBandsNode", + "parentPath" : "SlateOrangeCv[0]/Body[1]/MainColumn[1]/Experience[1]/ExperienceEntries[2]/ExperienceEntriesHolder[0]/SectionNode[0]/LayoutAnchorNode[3]/SectionNode[0]", + "childIndex" : 0, + "depth" : 10, + "layer" : 10, + "computedX" : 224.227, + "computedY" : 114.63, + "placementX" : 224.227, + "placementY" : 114.63, + "placementWidth" : 349.049, + "placementHeight" : 87.68, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 349.049, + "contentHeight" : 87.68, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SlateOrangeCv[0]/Body[1]/MainColumn[1]/Experience[1]/ExperienceEntries[2]/ExperienceEntriesHolder[0]/SectionNode[0]/LayoutAnchorNode[3]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]", + "entityName" : null, + "entityKind" : "RowNode", + "parentPath" : "SlateOrangeCv[0]/Body[1]/MainColumn[1]/Experience[1]/ExperienceEntries[2]/ExperienceEntriesHolder[0]/SectionNode[0]/LayoutAnchorNode[3]/SectionNode[0]/HorizontalBandsNode[0]", + "childIndex" : 0, + "depth" : 11, + "layer" : 11, + "computedX" : 224.227, + "computedY" : 114.63, + "placementX" : 224.227, + "placementY" : 114.63, + "placementWidth" : 349.049, + "placementHeight" : 87.68, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 349.049, + "contentHeight" : 87.68, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SlateOrangeCv[0]/Body[1]/MainColumn[1]/Experience[1]/ExperienceEntries[2]/ExperienceEntriesHolder[0]/SectionNode[0]/LayoutAnchorNode[3]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]", + "entityName" : null, + "entityKind" : "SectionNode", + "parentPath" : "SlateOrangeCv[0]/Body[1]/MainColumn[1]/Experience[1]/ExperienceEntries[2]/ExperienceEntriesHolder[0]/SectionNode[0]/LayoutAnchorNode[3]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]", + "childIndex" : 0, + "depth" : 12, + "layer" : 12, + "computedX" : 224.227, + "computedY" : 195.59, + "placementX" : 224.227, + "placementY" : 195.59, + "placementWidth" : 5.6, + "placementHeight" : 6.72, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 5.6, + "contentHeight" : 6.72, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SlateOrangeCv[0]/Body[1]/MainColumn[1]/Experience[1]/ExperienceEntries[2]/ExperienceEntriesHolder[0]/SectionNode[0]/LayoutAnchorNode[3]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]", + "entityName" : null, + "entityKind" : "Align", + "parentPath" : "SlateOrangeCv[0]/Body[1]/MainColumn[1]/Experience[1]/ExperienceEntries[2]/ExperienceEntriesHolder[0]/SectionNode[0]/LayoutAnchorNode[3]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]", + "childIndex" : 0, + "depth" : 13, + "layer" : 13, + "computedX" : 224.227, + "computedY" : 195.59, + "placementX" : 224.227, + "placementY" : 195.59, + "placementWidth" : 5.6, + "placementHeight" : 6.72, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 5.6, + "contentHeight" : 6.72, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SlateOrangeCv[0]/Body[1]/MainColumn[1]/Experience[1]/ExperienceEntries[2]/ExperienceEntriesHolder[0]/SectionNode[0]/LayoutAnchorNode[3]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]/LayoutAnchorNode[0]", + "entityName" : null, + "entityKind" : "LayoutAnchorNode", + "parentPath" : "SlateOrangeCv[0]/Body[1]/MainColumn[1]/Experience[1]/ExperienceEntries[2]/ExperienceEntriesHolder[0]/SectionNode[0]/LayoutAnchorNode[3]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]", + "childIndex" : 0, + "depth" : 14, + "layer" : 14, + "computedX" : 224.227, + "computedY" : 195.59, + "placementX" : 224.227, + "placementY" : 195.59, + "placementWidth" : 5.6, + "placementHeight" : 6.72, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 5.6, + "contentHeight" : 6.72, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SlateOrangeCv[0]/Body[1]/MainColumn[1]/Experience[1]/ExperienceEntries[2]/ExperienceEntriesHolder[0]/SectionNode[0]/LayoutAnchorNode[3]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]/LayoutAnchorNode[0]/marker[0]", + "entityName" : "marker", + "entityKind" : "CanvasLayerNode", + "parentPath" : "SlateOrangeCv[0]/Body[1]/MainColumn[1]/Experience[1]/ExperienceEntries[2]/ExperienceEntriesHolder[0]/SectionNode[0]/LayoutAnchorNode[3]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]/LayoutAnchorNode[0]", + "childIndex" : 0, + "depth" : 15, + "layer" : 15, + "computedX" : 224.227, + "computedY" : 195.59, + "placementX" : 224.227, + "placementY" : 195.59, + "placementWidth" : 5.6, + "placementHeight" : 6.72, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 5.6, + "contentHeight" : 6.72, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SlateOrangeCv[0]/Body[1]/MainColumn[1]/Experience[1]/ExperienceEntries[2]/ExperienceEntriesHolder[0]/SectionNode[0]/LayoutAnchorNode[3]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]/LayoutAnchorNode[0]/marker[0]/SectionNode[0]", + "entityName" : null, + "entityKind" : "SectionNode", + "parentPath" : "SlateOrangeCv[0]/Body[1]/MainColumn[1]/Experience[1]/ExperienceEntries[2]/ExperienceEntriesHolder[0]/SectionNode[0]/LayoutAnchorNode[3]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]/LayoutAnchorNode[0]/marker[0]", + "childIndex" : 0, + "depth" : 16, + "layer" : 16, + "computedX" : 224.227, + "computedY" : 195.59, + "placementX" : 224.227, + "placementY" : 195.59, + "placementWidth" : 5.6, + "placementHeight" : 6.72, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 5.6, + "contentHeight" : 6.72, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SlateOrangeCv[0]/Body[1]/MainColumn[1]/Experience[1]/ExperienceEntries[2]/ExperienceEntriesHolder[0]/SectionNode[0]/LayoutAnchorNode[3]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]/LayoutAnchorNode[0]/marker[0]/SectionNode[0]/Marker_3[0]", + "entityName" : "Marker_3", + "entityKind" : "ParagraphNode", + "parentPath" : "SlateOrangeCv[0]/Body[1]/MainColumn[1]/Experience[1]/ExperienceEntries[2]/ExperienceEntriesHolder[0]/SectionNode[0]/LayoutAnchorNode[3]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]/LayoutAnchorNode[0]/marker[0]/SectionNode[0]", + "childIndex" : 0, + "depth" : 17, + "layer" : 17, + "computedX" : 224.227, + "computedY" : 195.59, + "placementX" : 224.227, + "placementY" : 195.59, + "placementWidth" : 5.6, + "placementHeight" : 6.72, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 5.6, + "contentHeight" : 6.72, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SlateOrangeCv[0]/Body[1]/MainColumn[1]/Experience[1]/ExperienceEntries[2]/ExperienceEntriesHolder[0]/SectionNode[0]/LayoutAnchorNode[3]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]", + "entityName" : null, + "entityKind" : "SectionNode", + "parentPath" : "SlateOrangeCv[0]/Body[1]/MainColumn[1]/Experience[1]/ExperienceEntries[2]/ExperienceEntriesHolder[0]/SectionNode[0]/LayoutAnchorNode[3]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]", + "childIndex" : 1, + "depth" : 12, + "layer" : 12, + "computedX" : 240.027, + "computedY" : 114.63, + "placementX" : 240.027, + "placementY" : 114.63, + "placementWidth" : 333.239, + "placementHeight" : 87.68, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 333.239, + "contentHeight" : 87.68, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SlateOrangeCv[0]/Body[1]/MainColumn[1]/Experience[1]/ExperienceEntries[2]/ExperienceEntriesHolder[0]/SectionNode[0]/LayoutAnchorNode[3]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]/RoleLine_3[0]", + "entityName" : "RoleLine_3", + "entityKind" : "LayerStackNode", + "parentPath" : "SlateOrangeCv[0]/Body[1]/MainColumn[1]/Experience[1]/ExperienceEntries[2]/ExperienceEntriesHolder[0]/SectionNode[0]/LayoutAnchorNode[3]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]", + "childIndex" : 0, + "depth" : 13, + "layer" : 13, + "computedX" : 240.027, + "computedY" : 189.89, + "placementX" : 240.027, + "placementY" : 189.89, + "placementWidth" : 333.239, + "placementHeight" : 12.42, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 333.239, + "contentHeight" : 12.42, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SlateOrangeCv[0]/Body[1]/MainColumn[1]/Experience[1]/ExperienceEntries[2]/ExperienceEntriesHolder[0]/SectionNode[0]/LayoutAnchorNode[3]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]/RoleLine_3[0]/RoleTable_3[0]", + "entityName" : "RoleTable_3", + "entityKind" : "TableNode", + "parentPath" : "SlateOrangeCv[0]/Body[1]/MainColumn[1]/Experience[1]/ExperienceEntries[2]/ExperienceEntriesHolder[0]/SectionNode[0]/LayoutAnchorNode[3]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]/RoleLine_3[0]", + "childIndex" : 0, + "depth" : 14, + "layer" : 14, + "computedX" : 240.027, + "computedY" : 189.89, + "placementX" : 240.027, + "placementY" : 189.89, + "placementWidth" : 333.239, + "placementHeight" : 12.42, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 333.239, + "contentHeight" : 12.42, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SlateOrangeCv[0]/Body[1]/MainColumn[1]/Experience[1]/ExperienceEntries[2]/ExperienceEntriesHolder[0]/SectionNode[0]/LayoutAnchorNode[3]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]/Employer_3[1]", + "entityName" : "Employer_3", + "entityKind" : "ParagraphNode", + "parentPath" : "SlateOrangeCv[0]/Body[1]/MainColumn[1]/Experience[1]/ExperienceEntries[2]/ExperienceEntriesHolder[0]/SectionNode[0]/LayoutAnchorNode[3]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]", + "childIndex" : 1, + "depth" : 13, + "layer" : 13, + "computedX" : 240.027, + "computedY" : 176.97, + "placementX" : 240.027, + "placementY" : 176.97, + "placementWidth" : 151.251, + "placementHeight" : 10.92, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 151.251, + "contentHeight" : 10.92, + "margin" : { + "top" : 2.0, + "right" : 0.0, + "bottom" : 6.6, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SlateOrangeCv[0]/Body[1]/MainColumn[1]/Experience[1]/ExperienceEntries[2]/ExperienceEntriesHolder[0]/SectionNode[0]/LayoutAnchorNode[3]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]/Highlights_3[2]", + "entityName" : "Highlights_3", + "entityKind" : "ListNode", + "parentPath" : "SlateOrangeCv[0]/Body[1]/MainColumn[1]/Experience[1]/ExperienceEntries[2]/ExperienceEntriesHolder[0]/SectionNode[0]/LayoutAnchorNode[3]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]", + "childIndex" : 2, + "depth" : 13, + "layer" : 13, + "computedX" : 240.027, + "computedY" : 114.63, + "placementX" : 240.027, + "placementY" : 114.63, + "placementWidth" : 316.795, + "placementHeight" : 55.74, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 316.795, + "contentHeight" : 55.74, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SlateOrangeCv[0]/Body[1]/MainColumn[1]/Credentials[2]", + "entityName" : "Credentials", + "entityKind" : "SectionNode", + "parentPath" : "SlateOrangeCv[0]/Body[1]/MainColumn[1]", + "childIndex" : 2, + "depth" : 4, + "layer" : 4, + "computedX" : 227.027, + "computedY" : 24.07, + "placementX" : 227.027, + "placementY" : 24.07, + "placementWidth" : 346.249, + "placementHeight" : 78.56, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 346.249, + "contentHeight" : 78.56, + "margin" : { + "top" : 12.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SlateOrangeCv[0]/Body[1]/MainColumn[1]/Credentials[2]/CredentialsRule[0]", + "entityName" : "CredentialsRule", + "entityKind" : "LineNode", + "parentPath" : "SlateOrangeCv[0]/Body[1]/MainColumn[1]/Credentials[2]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 227.027, + "computedY" : 101.63, + "placementX" : 227.027, + "placementY" : 101.63, + "placementWidth" : 346.249, + "placementHeight" : 1.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 346.249, + "contentHeight" : 1.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SlateOrangeCv[0]/Body[1]/MainColumn[1]/Credentials[2]/CredentialTable[1]", + "entityName" : "CredentialTable", + "entityKind" : "TableNode", + "parentPath" : "SlateOrangeCv[0]/Body[1]/MainColumn[1]/Credentials[2]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 227.027, + "computedY" : 24.07, + "placementX" : 227.027, + "placementY" : 24.07, + "placementWidth" : 346.239, + "placementHeight" : 65.46, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 346.239, + "contentHeight" : 65.46, + "margin" : { + "top" : 12.1, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + } ] +} diff --git a/qa/src/test/resources/layout-snapshots/canonical-templates/cv-v2/teal_pulse_layout.json b/qa/src/test/resources/layout-snapshots/canonical-templates/cv-v2/teal_pulse_layout.json new file mode 100644 index 000000000..0c6a6e2cc --- /dev/null +++ b/qa/src/test/resources/layout-snapshots/canonical-templates/cv-v2/teal_pulse_layout.json @@ -0,0 +1,2897 @@ +{ + "formatVersion" : "2.0", + "canvas" : { + "pageWidth" : 595.28, + "pageHeight" : 769.6, + "innerWidth" : 595.28, + "innerHeight" : 769.6, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, + "totalPages" : 1, + "nodes" : [ { + "path" : "TealPulseCv[0]", + "entityName" : "TealPulseCv", + "entityKind" : "ContainerNode", + "parentPath" : null, + "childIndex" : 0, + "depth" : 1, + "layer" : 1, + "computedX" : 0.0, + "computedY" : 2.0, + "placementX" : 0.0, + "placementY" : 2.0, + "placementWidth" : 595.28, + "placementHeight" : 767.601, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 595.28, + "contentHeight" : 767.601, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 18.35, + "right" : 28.604, + "bottom" : 7.556, + "left" : 28.604 + } + }, { + "path" : "TealPulseCv[0]/Masthead[0]", + "entityName" : "Masthead", + "entityKind" : "RowNode", + "parentPath" : "TealPulseCv[0]", + "childIndex" : 0, + "depth" : 2, + "layer" : 2, + "computedX" : 28.604, + "computedY" : 676.575, + "placementX" : 28.604, + "placementY" : 676.575, + "placementWidth" : 538.073, + "placementHeight" : 74.676, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 538.073, + "contentHeight" : 74.676, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 9.175, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TealPulseCv[0]/Masthead[0]/BrandMark[0]", + "entityName" : "BrandMark", + "entityKind" : "ShapeContainerNode", + "parentPath" : "TealPulseCv[0]/Masthead[0]", + "childIndex" : 0, + "depth" : 3, + "layer" : 3, + "computedX" : 28.604, + "computedY" : 685.579, + "placementX" : 28.604, + "placementY" : 685.579, + "placementWidth" : 71.239, + "placementHeight" : 56.668, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 71.239, + "contentHeight" : 56.668, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TealPulseCv[0]/Masthead[0]/BrandMark[0]/Icon_brandheart[0]", + "entityName" : "Icon_brandheart", + "entityKind" : "SectionNode", + "parentPath" : "TealPulseCv[0]/Masthead[0]/BrandMark[0]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 28.604, + "computedY" : 675.363, + "placementX" : 28.604, + "placementY" : 675.363, + "placementWidth" : 71.239, + "placementHeight" : 77.1, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 71.239, + "contentHeight" : 77.1, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TealPulseCv[0]/Masthead[0]/BrandMark[0]/Icon_brandheart[0]/SvgIcon[0]", + "entityName" : "SvgIcon", + "entityKind" : "LayerStackNode", + "parentPath" : "TealPulseCv[0]/Masthead[0]/BrandMark[0]/Icon_brandheart[0]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 28.604, + "computedY" : 675.363, + "placementX" : 28.604, + "placementY" : 675.363, + "placementWidth" : 71.239, + "placementHeight" : 77.1, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 71.239, + "contentHeight" : 77.1, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TealPulseCv[0]/Masthead[0]/BrandMark[0]/Icon_brandheart[0]/SvgIcon[0]/SvgLayer0[0]", + "entityName" : "SvgLayer0", + "entityKind" : "Path", + "parentPath" : "TealPulseCv[0]/Masthead[0]/BrandMark[0]/Icon_brandheart[0]/SvgIcon[0]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 28.604, + "computedY" : 675.363, + "placementX" : 28.604, + "placementY" : 675.363, + "placementWidth" : 77.1, + "placementHeight" : 77.1, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 77.1, + "contentHeight" : 77.1, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TealPulseCv[0]/Masthead[0]/BrandMark[0]/BrandPulse[1]", + "entityName" : "BrandPulse", + "entityKind" : "Path", + "parentPath" : "TealPulseCv[0]/Masthead[0]/BrandMark[0]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 17.81, + "computedY" : 688.278, + "placementX" : 17.81, + "placementY" : 688.278, + "placementWidth" : 92.827, + "placementHeight" : 44.255, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 92.827, + "contentHeight" : 44.255, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TealPulseCv[0]/Masthead[0]/Identity[1]", + "entityName" : "Identity", + "entityKind" : "SectionNode", + "parentPath" : "TealPulseCv[0]/Masthead[0]", + "childIndex" : 1, + "depth" : 3, + "layer" : 3, + "computedX" : 120.891, + "computedY" : 676.575, + "placementX" : 120.891, + "placementY" : 676.575, + "placementWidth" : 340.363, + "placementHeight" : 74.676, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 340.363, + "contentHeight" : 74.676, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TealPulseCv[0]/Masthead[0]/Identity[1]/DisplayName[0]", + "entityName" : "DisplayName", + "entityKind" : "ParagraphNode", + "parentPath" : "TealPulseCv[0]/Masthead[0]/Identity[1]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 120.891, + "computedY" : 695.951, + "placementX" : 120.891, + "placementY" : 695.951, + "placementWidth" : 340.363, + "placementHeight" : 55.3, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 340.363, + "contentHeight" : 55.3, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TealPulseCv[0]/Masthead[0]/Identity[1]/ProfessionalTitle[1]", + "entityName" : "ProfessionalTitle", + "entityKind" : "ParagraphNode", + "parentPath" : "TealPulseCv[0]/Masthead[0]/Identity[1]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 120.891, + "computedY" : 676.575, + "placementX" : 120.891, + "placementY" : 676.575, + "placementWidth" : 205.712, + "placementHeight" : 19.376, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 205.712, + "contentHeight" : 19.376, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TealPulseCv[0]/ContactStrip[1]", + "entityName" : "ContactStrip", + "entityKind" : "RowNode", + "parentPath" : "TealPulseCv[0]", + "childIndex" : 1, + "depth" : 2, + "layer" : 2, + "computedX" : 28.604, + "computedY" : 646.892, + "placementX" : 28.604, + "placementY" : 646.892, + "placementWidth" : 538.073, + "placementHeight" : 20.508, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 538.073, + "contentHeight" : 20.508, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 12.413, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TealPulseCv[0]/ContactStrip[1]/Contact_0_location[0]", + "entityName" : "Contact_0_location", + "entityKind" : "ParagraphNode", + "parentPath" : "TealPulseCv[0]/ContactStrip[1]", + "childIndex" : 0, + "depth" : 3, + "layer" : 3, + "computedX" : 28.604, + "computedY" : 651.505, + "placementX" : 28.604, + "placementY" : 651.505, + "placementWidth" : 120.885, + "placementHeight" : 15.6, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 120.885, + "contentHeight" : 15.6, + "margin" : { + "top" : -4.318, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TealPulseCv[0]/ContactStrip[1]/ContactSeparator_1[1]", + "entityName" : "ContactSeparator_1", + "entityKind" : "LineNode", + "parentPath" : "TealPulseCv[0]/ContactStrip[1]", + "childIndex" : 1, + "depth" : 3, + "layer" : 3, + "computedX" : 178.536, + "computedY" : 646.892, + "placementX" : 178.536, + "placementY" : 646.892, + "placementWidth" : 1.0, + "placementHeight" : 20.508, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 1.0, + "contentHeight" : 20.508, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TealPulseCv[0]/ContactStrip[1]/Contact_1_email[2]", + "entityName" : "Contact_1_email", + "entityKind" : "ParagraphNode", + "parentPath" : "TealPulseCv[0]/ContactStrip[1]", + "childIndex" : 2, + "depth" : 3, + "layer" : 3, + "computedX" : 208.584, + "computedY" : 651.205, + "placementX" : 208.584, + "placementY" : 651.205, + "placementWidth" : 111.22, + "placementHeight" : 16.2, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 111.22, + "contentHeight" : 16.2, + "margin" : { + "top" : -4.318, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TealPulseCv[0]/ContactStrip[1]/ContactSeparator_2[3]", + "entityName" : "ContactSeparator_2", + "entityKind" : "LineNode", + "parentPath" : "TealPulseCv[0]/ContactStrip[1]", + "childIndex" : 3, + "depth" : 3, + "layer" : 3, + "computedX" : 348.852, + "computedY" : 646.892, + "placementX" : 348.852, + "placementY" : 646.892, + "placementWidth" : 1.0, + "placementHeight" : 20.508, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 1.0, + "contentHeight" : 20.508, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TealPulseCv[0]/ContactStrip[1]/Contact_2_phone[4]", + "entityName" : "Contact_2_phone", + "entityKind" : "ParagraphNode", + "parentPath" : "TealPulseCv[0]/ContactStrip[1]", + "childIndex" : 4, + "depth" : 3, + "layer" : 3, + "computedX" : 378.9, + "computedY" : 652.155, + "placementX" : 378.9, + "placementY" : 652.155, + "placementWidth" : 81.613, + "placementHeight" : 14.3, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 81.613, + "contentHeight" : 14.3, + "margin" : { + "top" : -4.318, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TealPulseCv[0]/ContactStrip[1]/ContactSeparator_3[5]", + "entityName" : "ContactSeparator_3", + "entityKind" : "LineNode", + "parentPath" : "TealPulseCv[0]/ContactStrip[1]", + "childIndex" : 5, + "depth" : 3, + "layer" : 3, + "computedX" : 489.561, + "computedY" : 646.892, + "placementX" : 489.561, + "placementY" : 646.892, + "placementWidth" : 1.0, + "placementHeight" : 20.508, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 1.0, + "contentHeight" : 20.508, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TealPulseCv[0]/ContactStrip[1]/Contact_3_linkedin[6]", + "entityName" : "Contact_3_linkedin", + "entityKind" : "ParagraphNode", + "parentPath" : "TealPulseCv[0]/ContactStrip[1]", + "childIndex" : 6, + "depth" : 3, + "layer" : 3, + "computedX" : 519.608, + "computedY" : 652.505, + "placementX" : 519.608, + "placementY" : 652.505, + "placementWidth" : 47.068, + "placementHeight" : 13.6, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 47.068, + "contentHeight" : 13.6, + "margin" : { + "top" : -4.318, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TealPulseCv[0]/MastheadRule[2]", + "entityName" : "MastheadRule", + "entityKind" : "LineNode", + "parentPath" : "TealPulseCv[0]", + "childIndex" : 2, + "depth" : 2, + "layer" : 2, + "computedX" : 28.604, + "computedY" : 633.13, + "placementX" : 28.604, + "placementY" : 633.13, + "placementWidth" : 538.073, + "placementHeight" : 1.349, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 538.073, + "contentHeight" : 1.349, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 15.651, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TealPulseCv[0]/BodyGrid[3]", + "entityName" : "BodyGrid", + "entityKind" : "RowNode", + "parentPath" : "TealPulseCv[0]", + "childIndex" : 3, + "depth" : 2, + "layer" : 2, + "computedX" : 28.604, + "computedY" : 177.009, + "placementX" : 28.604, + "placementY" : 177.009, + "placementWidth" : 538.073, + "placementHeight" : 440.47, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 538.073, + "contentHeight" : 440.47, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 16.191, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TealPulseCv[0]/BodyGrid[3]/Sidebar[0]", + "entityName" : "Sidebar", + "entityKind" : "SectionNode", + "parentPath" : "TealPulseCv[0]/BodyGrid[3]", + "childIndex" : 0, + "depth" : 3, + "layer" : 3, + "computedX" : 28.604, + "computedY" : 239.229, + "placementX" : 28.604, + "placementY" : 239.229, + "placementWidth" : 118.648, + "placementHeight" : 378.25, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 118.648, + "contentHeight" : 378.25, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 4.857, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TealPulseCv[0]/BodyGrid[3]/Sidebar[0]/CompetenciesDash[0]", + "entityName" : "CompetenciesDash", + "entityKind" : "LineNode", + "parentPath" : "TealPulseCv[0]/BodyGrid[3]/Sidebar[0]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 28.604, + "computedY" : 611.002, + "placementX" : 28.604, + "placementY" : 611.002, + "placementWidth" : 29.143, + "placementHeight" : 1.619, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 29.143, + "contentHeight" : 1.619, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 7.556, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TealPulseCv[0]/BodyGrid[3]/Sidebar[0]/CompetenciesHeading[1]", + "entityName" : "CompetenciesHeading", + "entityKind" : "ParagraphNode", + "parentPath" : "TealPulseCv[0]/BodyGrid[3]/Sidebar[0]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 28.604, + "computedY" : 590.967, + "placementX" : 28.604, + "placementY" : 590.967, + "placementWidth" : 110.685, + "placementHeight" : 12.48, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 110.685, + "contentHeight" : 12.48, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 18.889, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TealPulseCv[0]/BodyGrid[3]/Sidebar[0]/CompetencyItems[2]", + "entityName" : "CompetencyItems", + "entityKind" : "ListNode", + "parentPath" : "TealPulseCv[0]/BodyGrid[3]/Sidebar[0]", + "childIndex" : 2, + "depth" : 4, + "layer" : 4, + "computedX" : 28.604, + "computedY" : 267.832, + "placementX" : 28.604, + "placementY" : 267.832, + "placementWidth" : 118.648, + "placementHeight" : 304.245, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 118.648, + "contentHeight" : 304.245, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TealPulseCv[0]/BodyGrid[3]/Sidebar[0]/CompetenciesClosingRule[3]", + "entityName" : "CompetenciesClosingRule", + "entityKind" : "LineNode", + "parentPath" : "TealPulseCv[0]/BodyGrid[3]/Sidebar[0]", + "childIndex" : 3, + "depth" : 4, + "layer" : 4, + "computedX" : 28.604, + "computedY" : 239.229, + "placementX" : 28.604, + "placementY" : 239.229, + "placementWidth" : 116.573, + "placementHeight" : 1.619, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 116.573, + "contentHeight" : 1.619, + "margin" : { + "top" : 26.985, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TealPulseCv[0]/BodyGrid[3]/Main[1]", + "entityName" : "Main", + "entityKind" : "SectionNode", + "parentPath" : "TealPulseCv[0]/BodyGrid[3]", + "childIndex" : 1, + "depth" : 3, + "layer" : 3, + "computedX" : 171.37, + "computedY" : 177.009, + "placementX" : 171.37, + "placementY" : 177.009, + "placementWidth" : 395.306, + "placementHeight" : 440.47, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 395.306, + "contentHeight" : 440.47, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 17.27 + } + }, { + "path" : "TealPulseCv[0]/BodyGrid[3]/Main[1]/SectionHeader_PROFESSIONALSUMMARY[0]", + "entityName" : "SectionHeader_PROFESSIONALSUMMARY", + "entityKind" : "ShapeContainerNode", + "parentPath" : "TealPulseCv[0]/BodyGrid[3]/Main[1]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 188.641, + "computedY" : 586.177, + "placementX" : 188.641, + "placementY" : 586.177, + "placementWidth" : 378.036, + "placementHeight" : 31.302, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 378.036, + "contentHeight" : 31.302, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 10.794, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TealPulseCv[0]/BodyGrid[3]/Main[1]/SectionHeader_PROFESSIONALSUMMARY[0]/Badge_PROFESSIONALSUMMARY[0]", + "entityName" : "Badge_PROFESSIONALSUMMARY", + "entityKind" : "ShapeContainerNode", + "parentPath" : "TealPulseCv[0]/BodyGrid[3]/Main[1]/SectionHeader_PROFESSIONALSUMMARY[0]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 188.641, + "computedY" : 586.177, + "placementX" : 188.641, + "placementY" : 586.177, + "placementWidth" : 31.302, + "placementHeight" : 31.302, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 31.302, + "contentHeight" : 31.302, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TealPulseCv[0]/BodyGrid[3]/Main[1]/SectionHeader_PROFESSIONALSUMMARY[0]/Badge_PROFESSIONALSUMMARY[0]/Icon_summary[0]", + "entityName" : "Icon_summary", + "entityKind" : "SectionNode", + "parentPath" : "TealPulseCv[0]/BodyGrid[3]/Main[1]/SectionHeader_PROFESSIONALSUMMARY[0]/Badge_PROFESSIONALSUMMARY[0]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 194.292, + "computedY" : 591.828, + "placementX" : 194.292, + "placementY" : 591.828, + "placementWidth" : 20.0, + "placementHeight" : 20.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 20.0, + "contentHeight" : 20.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TealPulseCv[0]/BodyGrid[3]/Main[1]/SectionHeader_PROFESSIONALSUMMARY[0]/Badge_PROFESSIONALSUMMARY[0]/Icon_summary[0]/SvgIcon[0]", + "entityName" : "SvgIcon", + "entityKind" : "LayerStackNode", + "parentPath" : "TealPulseCv[0]/BodyGrid[3]/Main[1]/SectionHeader_PROFESSIONALSUMMARY[0]/Badge_PROFESSIONALSUMMARY[0]/Icon_summary[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 194.292, + "computedY" : 591.828, + "placementX" : 194.292, + "placementY" : 591.828, + "placementWidth" : 20.0, + "placementHeight" : 20.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 20.0, + "contentHeight" : 20.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TealPulseCv[0]/BodyGrid[3]/Main[1]/SectionHeader_PROFESSIONALSUMMARY[0]/Badge_PROFESSIONALSUMMARY[0]/Icon_summary[0]/SvgIcon[0]/SvgLayer0[0]", + "entityName" : "SvgLayer0", + "entityKind" : "Path", + "parentPath" : "TealPulseCv[0]/BodyGrid[3]/Main[1]/SectionHeader_PROFESSIONALSUMMARY[0]/Badge_PROFESSIONALSUMMARY[0]/Icon_summary[0]/SvgIcon[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 194.292, + "computedY" : 591.828, + "placementX" : 194.292, + "placementY" : 591.828, + "placementWidth" : 20.0, + "placementHeight" : 20.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 20.0, + "contentHeight" : 20.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TealPulseCv[0]/BodyGrid[3]/Main[1]/SectionHeader_PROFESSIONALSUMMARY[0]/SectionHeaderRule_PROFESSIONALSUMMARY[1]", + "entityName" : "SectionHeaderRule_PROFESSIONALSUMMARY", + "entityKind" : "ShapeNode", + "parentPath" : "TealPulseCv[0]/BodyGrid[3]/Main[1]/SectionHeader_PROFESSIONALSUMMARY[0]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 229.118, + "computedY" : 601.153, + "placementX" : 229.118, + "placementY" : 601.153, + "placementWidth" : 337.559, + "placementHeight" : 1.349, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 337.559, + "contentHeight" : 1.349, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TealPulseCv[0]/BodyGrid[3]/Main[1]/SectionHeader_PROFESSIONALSUMMARY[0]/SectionHeading_PROFESSIONALSUMMARY[2]", + "entityName" : "SectionHeading_PROFESSIONALSUMMARY", + "entityKind" : "ParagraphNode", + "parentPath" : "TealPulseCv[0]/BodyGrid[3]/Main[1]/SectionHeader_PROFESSIONALSUMMARY[0]", + "childIndex" : 2, + "depth" : 5, + "layer" : 5, + "computedX" : 229.118, + "computedY" : 594.208, + "placementX" : 229.118, + "placementY" : 594.208, + "placementWidth" : 144.93, + "placementHeight" : 15.24, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 144.93, + "contentHeight" : 15.24, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TealPulseCv[0]/BodyGrid[3]/Main[1]/SummaryText[1]", + "entityName" : "SummaryText", + "entityKind" : "ParagraphNode", + "parentPath" : "TealPulseCv[0]/BodyGrid[3]/Main[1]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 188.641, + "computedY" : 477.181, + "placementX" : 188.641, + "placementY" : 477.181, + "placementWidth" : 317.12, + "placementHeight" : 98.202, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 317.12, + "contentHeight" : 98.202, + "margin" : { + "top" : 0.0, + "right" : 56.668, + "bottom" : 15.651, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TealPulseCv[0]/BodyGrid[3]/Main[1]/SectionHeader_PROFESSIONALEXPERIENCE[2]", + "entityName" : "SectionHeader_PROFESSIONALEXPERIENCE", + "entityKind" : "ShapeContainerNode", + "parentPath" : "TealPulseCv[0]/BodyGrid[3]/Main[1]", + "childIndex" : 2, + "depth" : 4, + "layer" : 4, + "computedX" : 188.641, + "computedY" : 430.228, + "placementX" : 188.641, + "placementY" : 430.228, + "placementWidth" : 378.036, + "placementHeight" : 31.302, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 378.036, + "contentHeight" : 31.302, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 7.556, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TealPulseCv[0]/BodyGrid[3]/Main[1]/SectionHeader_PROFESSIONALEXPERIENCE[2]/Badge_PROFESSIONALEXPERIENCE[0]", + "entityName" : "Badge_PROFESSIONALEXPERIENCE", + "entityKind" : "ShapeContainerNode", + "parentPath" : "TealPulseCv[0]/BodyGrid[3]/Main[1]/SectionHeader_PROFESSIONALEXPERIENCE[2]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 188.641, + "computedY" : 430.228, + "placementX" : 188.641, + "placementY" : 430.228, + "placementWidth" : 31.302, + "placementHeight" : 31.302, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 31.302, + "contentHeight" : 31.302, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TealPulseCv[0]/BodyGrid[3]/Main[1]/SectionHeader_PROFESSIONALEXPERIENCE[2]/Badge_PROFESSIONALEXPERIENCE[0]/Icon_experience[0]", + "entityName" : "Icon_experience", + "entityKind" : "SectionNode", + "parentPath" : "TealPulseCv[0]/BodyGrid[3]/Main[1]/SectionHeader_PROFESSIONALEXPERIENCE[2]/Badge_PROFESSIONALEXPERIENCE[0]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 194.292, + "computedY" : 435.879, + "placementX" : 194.292, + "placementY" : 435.879, + "placementWidth" : 20.0, + "placementHeight" : 20.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 20.0, + "contentHeight" : 20.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TealPulseCv[0]/BodyGrid[3]/Main[1]/SectionHeader_PROFESSIONALEXPERIENCE[2]/Badge_PROFESSIONALEXPERIENCE[0]/Icon_experience[0]/SvgIcon[0]", + "entityName" : "SvgIcon", + "entityKind" : "LayerStackNode", + "parentPath" : "TealPulseCv[0]/BodyGrid[3]/Main[1]/SectionHeader_PROFESSIONALEXPERIENCE[2]/Badge_PROFESSIONALEXPERIENCE[0]/Icon_experience[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 194.292, + "computedY" : 435.879, + "placementX" : 194.292, + "placementY" : 435.879, + "placementWidth" : 20.0, + "placementHeight" : 20.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 20.0, + "contentHeight" : 20.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TealPulseCv[0]/BodyGrid[3]/Main[1]/SectionHeader_PROFESSIONALEXPERIENCE[2]/Badge_PROFESSIONALEXPERIENCE[0]/Icon_experience[0]/SvgIcon[0]/SvgLayer0[0]", + "entityName" : "SvgLayer0", + "entityKind" : "Path", + "parentPath" : "TealPulseCv[0]/BodyGrid[3]/Main[1]/SectionHeader_PROFESSIONALEXPERIENCE[2]/Badge_PROFESSIONALEXPERIENCE[0]/Icon_experience[0]/SvgIcon[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 194.292, + "computedY" : 435.879, + "placementX" : 194.292, + "placementY" : 435.879, + "placementWidth" : 20.0, + "placementHeight" : 20.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 20.0, + "contentHeight" : 20.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TealPulseCv[0]/BodyGrid[3]/Main[1]/SectionHeader_PROFESSIONALEXPERIENCE[2]/Badge_PROFESSIONALEXPERIENCE[0]/Icon_experience[0]/SvgIcon[0]/SvgLayer1[1]", + "entityName" : "SvgLayer1", + "entityKind" : "Path", + "parentPath" : "TealPulseCv[0]/BodyGrid[3]/Main[1]/SectionHeader_PROFESSIONALEXPERIENCE[2]/Badge_PROFESSIONALEXPERIENCE[0]/Icon_experience[0]/SvgIcon[0]", + "childIndex" : 1, + "depth" : 8, + "layer" : 8, + "computedX" : 194.292, + "computedY" : 435.879, + "placementX" : 194.292, + "placementY" : 435.879, + "placementWidth" : 20.0, + "placementHeight" : 20.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 20.0, + "contentHeight" : 20.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TealPulseCv[0]/BodyGrid[3]/Main[1]/SectionHeader_PROFESSIONALEXPERIENCE[2]/SectionHeaderRule_PROFESSIONALEXPERIENCE[1]", + "entityName" : "SectionHeaderRule_PROFESSIONALEXPERIENCE", + "entityKind" : "ShapeNode", + "parentPath" : "TealPulseCv[0]/BodyGrid[3]/Main[1]/SectionHeader_PROFESSIONALEXPERIENCE[2]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 229.118, + "computedY" : 445.204, + "placementX" : 229.118, + "placementY" : 445.204, + "placementWidth" : 337.559, + "placementHeight" : 1.349, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 337.559, + "contentHeight" : 1.349, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TealPulseCv[0]/BodyGrid[3]/Main[1]/SectionHeader_PROFESSIONALEXPERIENCE[2]/SectionHeading_PROFESSIONALEXPERIENCE[2]", + "entityName" : "SectionHeading_PROFESSIONALEXPERIENCE", + "entityKind" : "ParagraphNode", + "parentPath" : "TealPulseCv[0]/BodyGrid[3]/Main[1]/SectionHeader_PROFESSIONALEXPERIENCE[2]", + "childIndex" : 2, + "depth" : 5, + "layer" : 5, + "computedX" : 229.118, + "computedY" : 438.259, + "placementX" : 229.118, + "placementY" : 438.259, + "placementWidth" : 160.457, + "placementHeight" : 15.24, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 160.457, + "contentHeight" : 15.24, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TealPulseCv[0]/BodyGrid[3]/Main[1]/ExperienceHeadline_0[3]", + "entityName" : "ExperienceHeadline_0", + "entityKind" : "ParagraphNode", + "parentPath" : "TealPulseCv[0]/BodyGrid[3]/Main[1]", + "childIndex" : 3, + "depth" : 4, + "layer" : 4, + "computedX" : 188.641, + "computedY" : 411.992, + "placementX" : 188.641, + "placementY" : 411.992, + "placementWidth" : 325.998, + "placementHeight" : 10.68, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 325.998, + "contentHeight" : 10.68, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 7.016, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TealPulseCv[0]/BodyGrid[3]/Main[1]/ExperienceHighlights_0[4]", + "entityName" : "ExperienceHighlights_0", + "entityKind" : "SectionNode", + "parentPath" : "TealPulseCv[0]/BodyGrid[3]/Main[1]", + "childIndex" : 4, + "depth" : 4, + "layer" : 4, + "computedX" : 188.641, + "computedY" : 338.962, + "placementX" : 188.641, + "placementY" : 338.962, + "placementWidth" : 322.623, + "placementHeight" : 66.014, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 322.623, + "contentHeight" : 66.014, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 18.35, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TealPulseCv[0]/BodyGrid[3]/Main[1]/ExperienceHighlights_0[4]/Highlights_0[0]", + "entityName" : "Highlights_0", + "entityKind" : "ListNode", + "parentPath" : "TealPulseCv[0]/BodyGrid[3]/Main[1]/ExperienceHighlights_0[4]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 188.641, + "computedY" : 338.962, + "placementX" : 188.641, + "placementY" : 338.962, + "placementWidth" : 322.623, + "placementHeight" : 66.014, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 322.623, + "contentHeight" : 66.014, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TealPulseCv[0]/BodyGrid[3]/Main[1]/ExperienceHeadline_1[5]", + "entityName" : "ExperienceHeadline_1", + "entityKind" : "ParagraphNode", + "parentPath" : "TealPulseCv[0]/BodyGrid[3]/Main[1]", + "childIndex" : 5, + "depth" : 4, + "layer" : 4, + "computedX" : 188.641, + "computedY" : 309.932, + "placementX" : 188.641, + "placementY" : 309.932, + "placementWidth" : 256.133, + "placementHeight" : 10.68, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 256.133, + "contentHeight" : 10.68, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 7.016, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TealPulseCv[0]/BodyGrid[3]/Main[1]/ExperienceHighlights_1[6]", + "entityName" : "ExperienceHighlights_1", + "entityKind" : "SectionNode", + "parentPath" : "TealPulseCv[0]/BodyGrid[3]/Main[1]", + "childIndex" : 6, + "depth" : 4, + "layer" : 4, + "computedX" : 188.641, + "computedY" : 250.957, + "placementX" : 188.641, + "placementY" : 250.957, + "placementWidth" : 352.538, + "placementHeight" : 51.959, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 352.538, + "contentHeight" : 51.959, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 18.35, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TealPulseCv[0]/BodyGrid[3]/Main[1]/ExperienceHighlights_1[6]/Highlights_1[0]", + "entityName" : "Highlights_1", + "entityKind" : "ListNode", + "parentPath" : "TealPulseCv[0]/BodyGrid[3]/Main[1]/ExperienceHighlights_1[6]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 188.641, + "computedY" : 250.957, + "placementX" : 188.641, + "placementY" : 250.957, + "placementWidth" : 352.538, + "placementHeight" : 51.959, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 352.538, + "contentHeight" : 51.959, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TealPulseCv[0]/BodyGrid[3]/Main[1]/ExperienceHeadline_2[7]", + "entityName" : "ExperienceHeadline_2", + "entityKind" : "ParagraphNode", + "parentPath" : "TealPulseCv[0]/BodyGrid[3]/Main[1]", + "childIndex" : 7, + "depth" : 4, + "layer" : 4, + "computedX" : 188.641, + "computedY" : 221.928, + "placementX" : 188.641, + "placementY" : 221.928, + "placementWidth" : 357.282, + "placementHeight" : 10.68, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 357.282, + "contentHeight" : 10.68, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 7.016, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TealPulseCv[0]/BodyGrid[3]/Main[1]/ExperienceHighlights_2[8]", + "entityName" : "ExperienceHighlights_2", + "entityKind" : "SectionNode", + "parentPath" : "TealPulseCv[0]/BodyGrid[3]/Main[1]", + "childIndex" : 8, + "depth" : 4, + "layer" : 4, + "computedX" : 188.641, + "computedY" : 177.009, + "placementX" : 188.641, + "placementY" : 177.009, + "placementWidth" : 322.134, + "placementHeight" : 37.903, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 322.134, + "contentHeight" : 37.903, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TealPulseCv[0]/BodyGrid[3]/Main[1]/ExperienceHighlights_2[8]/Highlights_2[0]", + "entityName" : "Highlights_2", + "entityKind" : "ListNode", + "parentPath" : "TealPulseCv[0]/BodyGrid[3]/Main[1]/ExperienceHighlights_2[8]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 188.641, + "computedY" : 177.009, + "placementX" : 188.641, + "placementY" : 177.009, + "placementWidth" : 322.134, + "placementHeight" : 37.903, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 322.134, + "contentHeight" : 37.903, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TealPulseCv[0]/ClosingBandRule[4]", + "entityName" : "ClosingBandRule", + "entityKind" : "LineNode", + "parentPath" : "TealPulseCv[0]", + "childIndex" : 4, + "depth" : 2, + "layer" : 2, + "computedX" : 28.604, + "computedY" : 159.469, + "placementX" : 28.604, + "placementY" : 159.469, + "placementWidth" : 538.073, + "placementHeight" : 1.349, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 538.073, + "contentHeight" : 1.349, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 14.572, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TealPulseCv[0]/ClosingBand[5]", + "entityName" : "ClosingBand", + "entityKind" : "RowNode", + "parentPath" : "TealPulseCv[0]", + "childIndex" : 5, + "depth" : 2, + "layer" : 2, + "computedX" : 28.604, + "computedY" : 43.334, + "placementX" : 28.604, + "placementY" : 43.334, + "placementWidth" : 538.073, + "placementHeight" : 101.564, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 538.073, + "contentHeight" : 101.564, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 11.873, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TealPulseCv[0]/ClosingBand[5]/EducationColumn[0]", + "entityName" : "EducationColumn", + "entityKind" : "SectionNode", + "parentPath" : "TealPulseCv[0]/ClosingBand[5]", + "childIndex" : 0, + "depth" : 3, + "layer" : 3, + "computedX" : 28.604, + "computedY" : 60.866, + "placementX" : 28.604, + "placementY" : 60.866, + "placementWidth" : 121.179, + "placementHeight" : 84.031, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 121.179, + "contentHeight" : 84.031, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TealPulseCv[0]/ClosingBand[5]/EducationColumn[0]/ColumnHeader_EDUCATION[0]", + "entityName" : "ColumnHeader_EDUCATION", + "entityKind" : "ShapeContainerNode", + "parentPath" : "TealPulseCv[0]/ClosingBand[5]/EducationColumn[0]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 28.604, + "computedY" : 117.373, + "placementX" : 28.604, + "placementY" : 117.373, + "placementWidth" : 121.179, + "placementHeight" : 27.524, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 121.179, + "contentHeight" : 27.524, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 11.873, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TealPulseCv[0]/ClosingBand[5]/EducationColumn[0]/ColumnHeader_EDUCATION[0]/Badge_EDUCATION[0]", + "entityName" : "Badge_EDUCATION", + "entityKind" : "ShapeContainerNode", + "parentPath" : "TealPulseCv[0]/ClosingBand[5]/EducationColumn[0]/ColumnHeader_EDUCATION[0]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 28.604, + "computedY" : 117.373, + "placementX" : 28.604, + "placementY" : 117.373, + "placementWidth" : 27.524, + "placementHeight" : 27.524, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 27.524, + "contentHeight" : 27.524, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TealPulseCv[0]/ClosingBand[5]/EducationColumn[0]/ColumnHeader_EDUCATION[0]/Badge_EDUCATION[0]/Icon_education[0]", + "entityName" : "Icon_education", + "entityKind" : "SectionNode", + "parentPath" : "TealPulseCv[0]/ClosingBand[5]/EducationColumn[0]/ColumnHeader_EDUCATION[0]/Badge_EDUCATION[0]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 33.616, + "computedY" : 122.385, + "placementX" : 33.616, + "placementY" : 122.385, + "placementWidth" : 17.5, + "placementHeight" : 17.5, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 17.5, + "contentHeight" : 17.5, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TealPulseCv[0]/ClosingBand[5]/EducationColumn[0]/ColumnHeader_EDUCATION[0]/Badge_EDUCATION[0]/Icon_education[0]/SvgIcon[0]", + "entityName" : "SvgIcon", + "entityKind" : "LayerStackNode", + "parentPath" : "TealPulseCv[0]/ClosingBand[5]/EducationColumn[0]/ColumnHeader_EDUCATION[0]/Badge_EDUCATION[0]/Icon_education[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 33.616, + "computedY" : 122.385, + "placementX" : 33.616, + "placementY" : 122.385, + "placementWidth" : 17.5, + "placementHeight" : 17.5, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 17.5, + "contentHeight" : 17.5, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TealPulseCv[0]/ClosingBand[5]/EducationColumn[0]/ColumnHeader_EDUCATION[0]/Badge_EDUCATION[0]/Icon_education[0]/SvgIcon[0]/SvgLayer0[0]", + "entityName" : "SvgLayer0", + "entityKind" : "Path", + "parentPath" : "TealPulseCv[0]/ClosingBand[5]/EducationColumn[0]/ColumnHeader_EDUCATION[0]/Badge_EDUCATION[0]/Icon_education[0]/SvgIcon[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 33.616, + "computedY" : 122.385, + "placementX" : 33.616, + "placementY" : 122.385, + "placementWidth" : 17.5, + "placementHeight" : 17.5, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 17.5, + "contentHeight" : 17.5, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TealPulseCv[0]/ClosingBand[5]/EducationColumn[0]/ColumnHeader_EDUCATION[0]/ColumnHeadingStack_EDUCATION[1]", + "entityName" : "ColumnHeadingStack_EDUCATION", + "entityKind" : "SectionNode", + "parentPath" : "TealPulseCv[0]/ClosingBand[5]/EducationColumn[0]/ColumnHeader_EDUCATION[0]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 65.842, + "computedY" : 115.526, + "placementX" : 65.842, + "placementY" : 115.526, + "placementWidth" : 83.94, + "placementHeight" : 18.266, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 83.94, + "contentHeight" : 18.266, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TealPulseCv[0]/ClosingBand[5]/EducationColumn[0]/ColumnHeader_EDUCATION[0]/ColumnHeadingStack_EDUCATION[1]/ColumnHeading_EDUCATION[0]", + "entityName" : "ColumnHeading_EDUCATION", + "entityKind" : "ParagraphNode", + "parentPath" : "TealPulseCv[0]/ClosingBand[5]/EducationColumn[0]/ColumnHeader_EDUCATION[0]/ColumnHeadingStack_EDUCATION[1]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 65.842, + "computedY" : 122.272, + "placementX" : 65.842, + "placementY" : 122.272, + "placementWidth" : 51.84, + "placementHeight" : 11.52, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 51.84, + "contentHeight" : 11.52, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TealPulseCv[0]/ClosingBand[5]/EducationColumn[0]/ColumnHeader_EDUCATION[0]/ColumnHeadingStack_EDUCATION[1]/ColumnHeadingRule_EDUCATION[1]", + "entityName" : "ColumnHeadingRule_EDUCATION", + "entityKind" : "LineNode", + "parentPath" : "TealPulseCv[0]/ClosingBand[5]/EducationColumn[0]/ColumnHeader_EDUCATION[0]/ColumnHeadingStack_EDUCATION[1]", + "childIndex" : 1, + "depth" : 6, + "layer" : 6, + "computedX" : 65.842, + "computedY" : 115.526, + "placementX" : 65.842, + "placementY" : 115.526, + "placementWidth" : 83.94, + "placementHeight" : 1.619, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 83.94, + "contentHeight" : 1.619, + "margin" : { + "top" : 5.127, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TealPulseCv[0]/ClosingBand[5]/EducationColumn[0]/EducationDegree_0[1]", + "entityName" : "EducationDegree_0", + "entityKind" : "ParagraphNode", + "parentPath" : "TealPulseCv[0]/ClosingBand[5]/EducationColumn[0]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 28.604, + "computedY" : 92.84, + "placementX" : 28.604, + "placementY" : 92.84, + "placementWidth" : 118.804, + "placementHeight" : 12.66, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 118.804, + "contentHeight" : 12.66, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 5.667, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TealPulseCv[0]/ClosingBand[5]/EducationColumn[0]/EducationInstitution_0[2]", + "entityName" : "EducationInstitution_0", + "entityKind" : "ParagraphNode", + "parentPath" : "TealPulseCv[0]/ClosingBand[5]/EducationColumn[0]", + "childIndex" : 2, + "depth" : 4, + "layer" : 4, + "computedX" : 28.604, + "computedY" : 76.853, + "placementX" : 28.604, + "placementY" : 76.853, + "placementWidth" : 95.907, + "placementHeight" : 10.32, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 95.907, + "contentHeight" : 10.32, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 5.667, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TealPulseCv[0]/ClosingBand[5]/EducationColumn[0]/EducationDates_0[3]", + "entityName" : "EducationDates_0", + "entityKind" : "ParagraphNode", + "parentPath" : "TealPulseCv[0]/ClosingBand[5]/EducationColumn[0]", + "childIndex" : 3, + "depth" : 4, + "layer" : 4, + "computedX" : 28.604, + "computedY" : 60.866, + "placementX" : 28.604, + "placementY" : 60.866, + "placementWidth" : 44.892, + "placementHeight" : 10.32, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 44.892, + "contentHeight" : 10.32, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TealPulseCv[0]/ClosingBand[5]/CertificationsColumn[1]", + "entityName" : "CertificationsColumn", + "entityKind" : "SectionNode", + "parentPath" : "TealPulseCv[0]/ClosingBand[5]", + "childIndex" : 1, + "depth" : 3, + "layer" : 3, + "computedX" : 171.37, + "computedY" : 43.334, + "placementX" : 171.37, + "placementY" : 43.334, + "placementWidth" : 198.536, + "placementHeight" : 101.564, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 198.536, + "contentHeight" : 101.564, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 19.429 + } + }, { + "path" : "TealPulseCv[0]/ClosingBand[5]/CertificationsColumn[1]/ColumnHeader_CERTIFICATIONS[0]", + "entityName" : "ColumnHeader_CERTIFICATIONS", + "entityKind" : "ShapeContainerNode", + "parentPath" : "TealPulseCv[0]/ClosingBand[5]/CertificationsColumn[1]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 190.799, + "computedY" : 117.373, + "placementX" : 190.799, + "placementY" : 117.373, + "placementWidth" : 162.316, + "placementHeight" : 27.524, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 162.316, + "contentHeight" : 27.524, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 11.873, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TealPulseCv[0]/ClosingBand[5]/CertificationsColumn[1]/ColumnHeader_CERTIFICATIONS[0]/Badge_CERTIFICATIONS[0]", + "entityName" : "Badge_CERTIFICATIONS", + "entityKind" : "ShapeContainerNode", + "parentPath" : "TealPulseCv[0]/ClosingBand[5]/CertificationsColumn[1]/ColumnHeader_CERTIFICATIONS[0]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 190.799, + "computedY" : 117.373, + "placementX" : 190.799, + "placementY" : 117.373, + "placementWidth" : 27.524, + "placementHeight" : 27.524, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 27.524, + "contentHeight" : 27.524, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TealPulseCv[0]/ClosingBand[5]/CertificationsColumn[1]/ColumnHeader_CERTIFICATIONS[0]/Badge_CERTIFICATIONS[0]/Icon_certifications[0]", + "entityName" : "Icon_certifications", + "entityKind" : "SectionNode", + "parentPath" : "TealPulseCv[0]/ClosingBand[5]/CertificationsColumn[1]/ColumnHeader_CERTIFICATIONS[0]/Badge_CERTIFICATIONS[0]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 195.812, + "computedY" : 122.385, + "placementX" : 195.812, + "placementY" : 122.385, + "placementWidth" : 17.5, + "placementHeight" : 17.5, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 17.5, + "contentHeight" : 17.5, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TealPulseCv[0]/ClosingBand[5]/CertificationsColumn[1]/ColumnHeader_CERTIFICATIONS[0]/Badge_CERTIFICATIONS[0]/Icon_certifications[0]/SvgIcon[0]", + "entityName" : "SvgIcon", + "entityKind" : "LayerStackNode", + "parentPath" : "TealPulseCv[0]/ClosingBand[5]/CertificationsColumn[1]/ColumnHeader_CERTIFICATIONS[0]/Badge_CERTIFICATIONS[0]/Icon_certifications[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 195.812, + "computedY" : 122.385, + "placementX" : 195.812, + "placementY" : 122.385, + "placementWidth" : 17.5, + "placementHeight" : 17.5, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 17.5, + "contentHeight" : 17.5, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TealPulseCv[0]/ClosingBand[5]/CertificationsColumn[1]/ColumnHeader_CERTIFICATIONS[0]/Badge_CERTIFICATIONS[0]/Icon_certifications[0]/SvgIcon[0]/SvgLayer0[0]", + "entityName" : "SvgLayer0", + "entityKind" : "Path", + "parentPath" : "TealPulseCv[0]/ClosingBand[5]/CertificationsColumn[1]/ColumnHeader_CERTIFICATIONS[0]/Badge_CERTIFICATIONS[0]/Icon_certifications[0]/SvgIcon[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 195.812, + "computedY" : 122.385, + "placementX" : 195.812, + "placementY" : 122.385, + "placementWidth" : 17.5, + "placementHeight" : 17.5, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 17.5, + "contentHeight" : 17.5, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TealPulseCv[0]/ClosingBand[5]/CertificationsColumn[1]/ColumnHeader_CERTIFICATIONS[0]/Badge_CERTIFICATIONS[0]/Icon_certifications[0]/SvgIcon[0]/SvgLayer1[1]", + "entityName" : "SvgLayer1", + "entityKind" : "Path", + "parentPath" : "TealPulseCv[0]/ClosingBand[5]/CertificationsColumn[1]/ColumnHeader_CERTIFICATIONS[0]/Badge_CERTIFICATIONS[0]/Icon_certifications[0]/SvgIcon[0]", + "childIndex" : 1, + "depth" : 8, + "layer" : 8, + "computedX" : 195.812, + "computedY" : 122.385, + "placementX" : 195.812, + "placementY" : 122.385, + "placementWidth" : 17.5, + "placementHeight" : 17.5, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 17.5, + "contentHeight" : 17.5, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TealPulseCv[0]/ClosingBand[5]/CertificationsColumn[1]/ColumnHeader_CERTIFICATIONS[0]/ColumnHeadingStack_CERTIFICATIONS[1]", + "entityName" : "ColumnHeadingStack_CERTIFICATIONS", + "entityKind" : "SectionNode", + "parentPath" : "TealPulseCv[0]/ClosingBand[5]/CertificationsColumn[1]/ColumnHeader_CERTIFICATIONS[0]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 228.038, + "computedY" : 115.526, + "placementX" : 228.038, + "placementY" : 115.526, + "placementWidth" : 125.077, + "placementHeight" : 18.266, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 125.077, + "contentHeight" : 18.266, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TealPulseCv[0]/ClosingBand[5]/CertificationsColumn[1]/ColumnHeader_CERTIFICATIONS[0]/ColumnHeadingStack_CERTIFICATIONS[1]/ColumnHeading_CERTIFICATIONS[0]", + "entityName" : "ColumnHeading_CERTIFICATIONS", + "entityKind" : "ParagraphNode", + "parentPath" : "TealPulseCv[0]/ClosingBand[5]/CertificationsColumn[1]/ColumnHeader_CERTIFICATIONS[0]/ColumnHeadingStack_CERTIFICATIONS[1]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 228.038, + "computedY" : 122.272, + "placementX" : 228.038, + "placementY" : 122.272, + "placementWidth" : 77.117, + "placementHeight" : 11.52, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 77.117, + "contentHeight" : 11.52, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TealPulseCv[0]/ClosingBand[5]/CertificationsColumn[1]/ColumnHeader_CERTIFICATIONS[0]/ColumnHeadingStack_CERTIFICATIONS[1]/ColumnHeadingRule_CERTIFICATIONS[1]", + "entityName" : "ColumnHeadingRule_CERTIFICATIONS", + "entityKind" : "LineNode", + "parentPath" : "TealPulseCv[0]/ClosingBand[5]/CertificationsColumn[1]/ColumnHeader_CERTIFICATIONS[0]/ColumnHeadingStack_CERTIFICATIONS[1]", + "childIndex" : 1, + "depth" : 6, + "layer" : 6, + "computedX" : 228.038, + "computedY" : 115.526, + "placementX" : 228.038, + "placementY" : 115.526, + "placementWidth" : 125.077, + "placementHeight" : 1.619, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 125.077, + "contentHeight" : 1.619, + "margin" : { + "top" : 5.127, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TealPulseCv[0]/ClosingBand[5]/CertificationsColumn[1]/CertificationItems[1]", + "entityName" : "CertificationItems", + "entityKind" : "ListNode", + "parentPath" : "TealPulseCv[0]/ClosingBand[5]/CertificationsColumn[1]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 190.799, + "computedY" : 43.334, + "placementX" : 190.799, + "placementY" : 43.334, + "placementWidth" : 179.107, + "placementHeight" : 62.166, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 179.107, + "contentHeight" : 62.166, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TealPulseCv[0]/ClosingBand[5]/AdditionalColumn[2]", + "entityName" : "AdditionalColumn", + "entityKind" : "SectionNode", + "parentPath" : "TealPulseCv[0]/ClosingBand[5]", + "childIndex" : 2, + "depth" : 3, + "layer" : 3, + "computedX" : 374.703, + "computedY" : 43.751, + "placementX" : 374.703, + "placementY" : 43.751, + "placementWidth" : 191.974, + "placementHeight" : 101.146, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 191.974, + "contentHeight" : 101.146, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 19.429 + } + }, { + "path" : "TealPulseCv[0]/ClosingBand[5]/AdditionalColumn[2]/ColumnHeader_ADDITIONALINFORMATION[0]", + "entityName" : "ColumnHeader_ADDITIONALINFORMATION", + "entityKind" : "ShapeContainerNode", + "parentPath" : "TealPulseCv[0]/ClosingBand[5]/AdditionalColumn[2]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 394.132, + "computedY" : 117.373, + "placementX" : 394.132, + "placementY" : 117.373, + "placementWidth" : 172.545, + "placementHeight" : 27.524, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 172.545, + "contentHeight" : 27.524, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 11.873, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TealPulseCv[0]/ClosingBand[5]/AdditionalColumn[2]/ColumnHeader_ADDITIONALINFORMATION[0]/Badge_ADDITIONALINFORMATION[0]", + "entityName" : "Badge_ADDITIONALINFORMATION", + "entityKind" : "ShapeContainerNode", + "parentPath" : "TealPulseCv[0]/ClosingBand[5]/AdditionalColumn[2]/ColumnHeader_ADDITIONALINFORMATION[0]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 394.132, + "computedY" : 117.373, + "placementX" : 394.132, + "placementY" : 117.373, + "placementWidth" : 27.524, + "placementHeight" : 27.524, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 27.524, + "contentHeight" : 27.524, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TealPulseCv[0]/ClosingBand[5]/AdditionalColumn[2]/ColumnHeader_ADDITIONALINFORMATION[0]/Badge_ADDITIONALINFORMATION[0]/Icon_additional[0]", + "entityName" : "Icon_additional", + "entityKind" : "SectionNode", + "parentPath" : "TealPulseCv[0]/ClosingBand[5]/AdditionalColumn[2]/ColumnHeader_ADDITIONALINFORMATION[0]/Badge_ADDITIONALINFORMATION[0]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 399.144, + "computedY" : 122.385, + "placementX" : 399.144, + "placementY" : 122.385, + "placementWidth" : 17.5, + "placementHeight" : 17.5, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 17.5, + "contentHeight" : 17.5, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TealPulseCv[0]/ClosingBand[5]/AdditionalColumn[2]/ColumnHeader_ADDITIONALINFORMATION[0]/Badge_ADDITIONALINFORMATION[0]/Icon_additional[0]/SvgIcon[0]", + "entityName" : "SvgIcon", + "entityKind" : "LayerStackNode", + "parentPath" : "TealPulseCv[0]/ClosingBand[5]/AdditionalColumn[2]/ColumnHeader_ADDITIONALINFORMATION[0]/Badge_ADDITIONALINFORMATION[0]/Icon_additional[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 399.144, + "computedY" : 122.385, + "placementX" : 399.144, + "placementY" : 122.385, + "placementWidth" : 17.5, + "placementHeight" : 17.5, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 17.5, + "contentHeight" : 17.5, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TealPulseCv[0]/ClosingBand[5]/AdditionalColumn[2]/ColumnHeader_ADDITIONALINFORMATION[0]/Badge_ADDITIONALINFORMATION[0]/Icon_additional[0]/SvgIcon[0]/SvgLayer0[0]", + "entityName" : "SvgLayer0", + "entityKind" : "Path", + "parentPath" : "TealPulseCv[0]/ClosingBand[5]/AdditionalColumn[2]/ColumnHeader_ADDITIONALINFORMATION[0]/Badge_ADDITIONALINFORMATION[0]/Icon_additional[0]/SvgIcon[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 399.144, + "computedY" : 122.385, + "placementX" : 399.144, + "placementY" : 122.385, + "placementWidth" : 17.5, + "placementHeight" : 17.5, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 17.5, + "contentHeight" : 17.5, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TealPulseCv[0]/ClosingBand[5]/AdditionalColumn[2]/ColumnHeader_ADDITIONALINFORMATION[0]/Badge_ADDITIONALINFORMATION[0]/Icon_additional[0]/SvgIcon[0]/SvgLayer1[1]", + "entityName" : "SvgLayer1", + "entityKind" : "Path", + "parentPath" : "TealPulseCv[0]/ClosingBand[5]/AdditionalColumn[2]/ColumnHeader_ADDITIONALINFORMATION[0]/Badge_ADDITIONALINFORMATION[0]/Icon_additional[0]/SvgIcon[0]", + "childIndex" : 1, + "depth" : 8, + "layer" : 8, + "computedX" : 399.144, + "computedY" : 122.385, + "placementX" : 399.144, + "placementY" : 122.385, + "placementWidth" : 17.5, + "placementHeight" : 17.5, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 17.5, + "contentHeight" : 17.5, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TealPulseCv[0]/ClosingBand[5]/AdditionalColumn[2]/ColumnHeader_ADDITIONALINFORMATION[0]/ColumnHeadingStack_ADDITIONALINFORMATION[1]", + "entityName" : "ColumnHeadingStack_ADDITIONALINFORMATION", + "entityKind" : "SectionNode", + "parentPath" : "TealPulseCv[0]/ClosingBand[5]/AdditionalColumn[2]/ColumnHeader_ADDITIONALINFORMATION[0]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 431.37, + "computedY" : 115.526, + "placementX" : 431.37, + "placementY" : 115.526, + "placementWidth" : 135.306, + "placementHeight" : 18.266, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 135.306, + "contentHeight" : 18.266, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TealPulseCv[0]/ClosingBand[5]/AdditionalColumn[2]/ColumnHeader_ADDITIONALINFORMATION[0]/ColumnHeadingStack_ADDITIONALINFORMATION[1]/ColumnHeading_ADDITIONALINFORMATION[0]", + "entityName" : "ColumnHeading_ADDITIONALINFORMATION", + "entityKind" : "ParagraphNode", + "parentPath" : "TealPulseCv[0]/ClosingBand[5]/AdditionalColumn[2]/ColumnHeader_ADDITIONALINFORMATION[0]/ColumnHeadingStack_ADDITIONALINFORMATION[1]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 431.37, + "computedY" : 122.272, + "placementX" : 431.37, + "placementY" : 122.272, + "placementWidth" : 123.773, + "placementHeight" : 11.52, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 123.773, + "contentHeight" : 11.52, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TealPulseCv[0]/ClosingBand[5]/AdditionalColumn[2]/ColumnHeader_ADDITIONALINFORMATION[0]/ColumnHeadingStack_ADDITIONALINFORMATION[1]/ColumnHeadingRule_ADDITIONALINFORMATION[1]", + "entityName" : "ColumnHeadingRule_ADDITIONALINFORMATION", + "entityKind" : "LineNode", + "parentPath" : "TealPulseCv[0]/ClosingBand[5]/AdditionalColumn[2]/ColumnHeader_ADDITIONALINFORMATION[0]/ColumnHeadingStack_ADDITIONALINFORMATION[1]", + "childIndex" : 1, + "depth" : 6, + "layer" : 6, + "computedX" : 431.37, + "computedY" : 115.526, + "placementX" : 431.37, + "placementY" : 115.526, + "placementWidth" : 135.306, + "placementHeight" : 1.619, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 135.306, + "contentHeight" : 1.619, + "margin" : { + "top" : 5.127, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TealPulseCv[0]/ClosingBand[5]/AdditionalColumn[2]/AdditionalFacts[1]", + "entityName" : "AdditionalFacts", + "entityKind" : "SectionNode", + "parentPath" : "TealPulseCv[0]/ClosingBand[5]/AdditionalColumn[2]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 394.132, + "computedY" : 43.751, + "placementX" : 394.132, + "placementY" : 43.751, + "placementWidth" : 163.122, + "placementHeight" : 61.749, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 163.122, + "contentHeight" : 61.749, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 42.636, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TealPulseCv[0]/ClosingBand[5]/AdditionalColumn[2]/AdditionalFacts[1]/Fact_Languages[0]", + "entityName" : "Fact_Languages", + "entityKind" : "ParagraphNode", + "parentPath" : "TealPulseCv[0]/ClosingBand[5]/AdditionalColumn[2]/AdditionalFacts[1]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 394.132, + "computedY" : 80.15, + "placementX" : 394.132, + "placementY" : 80.15, + "placementWidth" : 105.367, + "placementHeight" : 25.35, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 105.367, + "contentHeight" : 25.35, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TealPulseCv[0]/ClosingBand[5]/AdditionalColumn[2]/AdditionalFacts[1]/Fact_RighttoWork[1]", + "entityName" : "Fact_RighttoWork", + "entityKind" : "ParagraphNode", + "parentPath" : "TealPulseCv[0]/ClosingBand[5]/AdditionalColumn[2]/AdditionalFacts[1]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 394.132, + "computedY" : 61.95, + "placementX" : 394.132, + "placementY" : 61.95, + "placementWidth" : 120.486, + "placementHeight" : 10.32, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 120.486, + "contentHeight" : 10.32, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TealPulseCv[0]/ClosingBand[5]/AdditionalColumn[2]/AdditionalFacts[1]/Fact_Availability[2]", + "entityName" : "Fact_Availability", + "entityKind" : "ParagraphNode", + "parentPath" : "TealPulseCv[0]/ClosingBand[5]/AdditionalColumn[2]/AdditionalFacts[1]", + "childIndex" : 2, + "depth" : 5, + "layer" : 5, + "computedX" : 394.132, + "computedY" : 43.751, + "placementX" : 394.132, + "placementY" : 43.751, + "placementWidth" : 104.645, + "placementHeight" : 10.32, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 104.645, + "contentHeight" : 10.32, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TealPulseCv[0]/TaglineRule[6]", + "entityName" : "TaglineRule", + "entityKind" : "ShapeContainerNode", + "parentPath" : "TealPulseCv[0]", + "childIndex" : 6, + "depth" : 2, + "layer" : 2, + "computedX" : 28.604, + "computedY" : 20.127, + "placementX" : 28.604, + "placementY" : 20.127, + "placementWidth" : 538.073, + "placementHeight" : 11.334, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 538.073, + "contentHeight" : 11.334, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.864, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TealPulseCv[0]/TaglineRule[6]/TaglineRuleLine[0]", + "entityName" : "TaglineRuleLine", + "entityKind" : "ShapeNode", + "parentPath" : "TealPulseCv[0]/TaglineRule[6]", + "childIndex" : 0, + "depth" : 3, + "layer" : 3, + "computedX" : 28.604, + "computedY" : 25.119, + "placementX" : 28.604, + "placementY" : 25.119, + "placementWidth" : 538.073, + "placementHeight" : 1.349, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 538.073, + "contentHeight" : 1.349, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TealPulseCv[0]/TaglineRule[6]/Icon_heart[1]", + "entityName" : "Icon_heart", + "entityKind" : "SectionNode", + "parentPath" : "TealPulseCv[0]/TaglineRule[6]", + "childIndex" : 1, + "depth" : 3, + "layer" : 3, + "computedX" : 551.776, + "computedY" : 18.344, + "placementX" : 551.776, + "placementY" : 18.344, + "placementWidth" : 14.9, + "placementHeight" : 14.9, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 14.9, + "contentHeight" : 14.9, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TealPulseCv[0]/TaglineRule[6]/Icon_heart[1]/SvgIcon[0]", + "entityName" : "SvgIcon", + "entityKind" : "LayerStackNode", + "parentPath" : "TealPulseCv[0]/TaglineRule[6]/Icon_heart[1]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 551.776, + "computedY" : 18.344, + "placementX" : 551.776, + "placementY" : 18.344, + "placementWidth" : 14.9, + "placementHeight" : 14.9, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 14.9, + "contentHeight" : 14.9, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TealPulseCv[0]/TaglineRule[6]/Icon_heart[1]/SvgIcon[0]/SvgLayer0[0]", + "entityName" : "SvgLayer0", + "entityKind" : "Path", + "parentPath" : "TealPulseCv[0]/TaglineRule[6]/Icon_heart[1]/SvgIcon[0]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 551.776, + "computedY" : 18.344, + "placementX" : 551.776, + "placementY" : 18.344, + "placementWidth" : 14.9, + "placementHeight" : 14.9, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 14.9, + "contentHeight" : 14.9, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TealPulseCv[0]/Tagline[7]", + "entityName" : "Tagline", + "entityKind" : "ParagraphNode", + "parentPath" : "TealPulseCv[0]", + "childIndex" : 7, + "depth" : 2, + "layer" : 2, + "computedX" : 28.604, + "computedY" : 9.555, + "placementX" : 28.604, + "placementY" : 9.555, + "placementWidth" : 538.073, + "placementHeight" : 9.708, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 538.073, + "contentHeight" : 9.708, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + } ] +} diff --git a/qa/src/test/resources/layout-snapshots/canonical-templates/cv-v2/terracotta_rail_layout.json b/qa/src/test/resources/layout-snapshots/canonical-templates/cv-v2/terracotta_rail_layout.json new file mode 100644 index 000000000..7f8208ff9 --- /dev/null +++ b/qa/src/test/resources/layout-snapshots/canonical-templates/cv-v2/terracotta_rail_layout.json @@ -0,0 +1,6167 @@ +{ + "formatVersion" : "2.0", + "canvas" : { + "pageWidth" : 595.276, + "pageHeight" : 841.89, + "innerWidth" : 595.276, + "innerHeight" : 841.89, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, + "totalPages" : 1, + "nodes" : [ { + "path" : "TerracottaRailCv[0]", + "entityName" : "TerracottaRailCv", + "entityKind" : "ContainerNode", + "parentPath" : null, + "childIndex" : 0, + "depth" : 1, + "layer" : 1, + "computedX" : 0.0, + "computedY" : 47.306, + "placementX" : 0.0, + "placementY" : 47.306, + "placementWidth" : 595.276, + "placementHeight" : 794.584, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 595.276, + "contentHeight" : 794.584, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]", + "entityName" : "Body", + "entityKind" : "RowNode", + "parentPath" : "TerracottaRailCv[0]", + "childIndex" : 0, + "depth" : 2, + "layer" : 2, + "computedX" : 0.0, + "computedY" : 47.306, + "placementX" : 0.0, + "placementY" : 47.306, + "placementWidth" : 595.276, + "placementHeight" : 794.584, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 595.276, + "contentHeight" : 794.584, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/Sidebar[0]", + "entityName" : "Sidebar", + "entityKind" : "SectionNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]", + "childIndex" : 0, + "depth" : 3, + "layer" : 3, + "computedX" : 0.0, + "computedY" : 100.839, + "placementX" : 0.0, + "placementY" : 100.839, + "placementWidth" : 183.345, + "placementHeight" : 741.051, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 183.345, + "contentHeight" : 741.051, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 20.0, + "right" : 11.4, + "bottom" : 5.0, + "left" : 25.4 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/Sidebar[0]/Monogram[0]", + "entityName" : "Monogram", + "entityKind" : "ParagraphNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/Sidebar[0]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 25.4, + "computedY" : 774.19, + "placementX" : 25.4, + "placementY" : 774.19, + "placementWidth" : 48.78, + "placementHeight" : 47.7, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 48.78, + "contentHeight" : 47.7, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 8.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/Sidebar[0]/MonogramRule[1]", + "entityName" : "MonogramRule", + "entityKind" : "LineNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/Sidebar[0]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 25.4, + "computedY" : 764.79, + "placementX" : 25.4, + "placementY" : 764.79, + "placementWidth" : 48.0, + "placementHeight" : 1.4, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 48.0, + "contentHeight" : 1.4, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 10.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/Sidebar[0]/Contact[2]", + "entityName" : "Contact", + "entityKind" : "SectionNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/Sidebar[0]", + "childIndex" : 2, + "depth" : 4, + "layer" : 4, + "computedX" : 25.4, + "computedY" : 684.99, + "placementX" : 25.4, + "placementY" : 684.99, + "placementWidth" : 113.88, + "placementHeight" : 69.8, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 113.88, + "contentHeight" : 69.8, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/Sidebar[0]/Contact[2]/Contact_0[0]", + "entityName" : "Contact_0", + "entityKind" : "ParagraphNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/Sidebar[0]/Contact[2]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 25.4, + "computedY" : 741.84, + "placementX" : 25.4, + "placementY" : 741.84, + "placementWidth" : 113.88, + "placementHeight" : 12.95, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 113.88, + "contentHeight" : 12.95, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 4.5, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/Sidebar[0]/Contact[2]/Contact_1[1]", + "entityName" : "Contact_1", + "entityKind" : "ParagraphNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/Sidebar[0]/Contact[2]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 25.4, + "computedY" : 724.39, + "placementX" : 25.4, + "placementY" : 724.39, + "placementWidth" : 85.646, + "placementHeight" : 12.95, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 85.646, + "contentHeight" : 12.95, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 4.5, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/Sidebar[0]/Contact[2]/Contact_2[2]", + "entityName" : "Contact_2", + "entityKind" : "ParagraphNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/Sidebar[0]/Contact[2]", + "childIndex" : 2, + "depth" : 5, + "layer" : 5, + "computedX" : 25.4, + "computedY" : 706.94, + "placementX" : 25.4, + "placementY" : 706.94, + "placementWidth" : 106.253, + "placementHeight" : 12.95, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 106.253, + "contentHeight" : 12.95, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 4.5, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/Sidebar[0]/Contact[2]/Contact_3[3]", + "entityName" : "Contact_3", + "entityKind" : "ParagraphNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/Sidebar[0]/Contact[2]", + "childIndex" : 3, + "depth" : 5, + "layer" : 5, + "computedX" : 25.4, + "computedY" : 689.49, + "placementX" : 25.4, + "placementY" : 689.49, + "placementWidth" : 49.355, + "placementHeight" : 12.95, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 49.355, + "contentHeight" : 12.95, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 4.5, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/Sidebar[0]/Divider_AfterContact[3]", + "entityName" : "Divider_AfterContact", + "entityKind" : "LineNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/Sidebar[0]", + "childIndex" : 3, + "depth" : 4, + "layer" : 4, + "computedX" : 25.4, + "computedY" : 675.99, + "placementX" : 25.4, + "placementY" : 675.99, + "placementWidth" : 146.545, + "placementHeight" : 1.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 146.545, + "contentHeight" : 1.0, + "margin" : { + "top" : 8.0, + "right" : 0.0, + "bottom" : 8.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/Sidebar[0]/CoreCompetencies[4]", + "entityName" : "CoreCompetencies", + "entityKind" : "SectionNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/Sidebar[0]", + "childIndex" : 4, + "depth" : 4, + "layer" : 4, + "computedX" : 25.4, + "computedY" : 457.063, + "placementX" : 25.4, + "placementY" : 457.063, + "placementWidth" : 106.829, + "placementHeight" : 210.926, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 106.829, + "contentHeight" : 210.926, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/Sidebar[0]/CoreCompetencies[4]/Heading_CORECOMPETENCIES[0]", + "entityName" : "Heading_CORECOMPETENCIES", + "entityKind" : "ParagraphNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/Sidebar[0]/CoreCompetencies[4]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 25.4, + "computedY" : 657.463, + "placementX" : 25.4, + "placementY" : 657.463, + "placementWidth" : 101.553, + "placementHeight" : 10.526, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 101.553, + "contentHeight" : 10.526, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 9.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/Sidebar[0]/CoreCompetencies[4]/Competency_0[1]", + "entityName" : "Competency_0", + "entityKind" : "ParagraphNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/Sidebar[0]/CoreCompetencies[4]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 25.4, + "computedY" : 635.513, + "placementX" : 25.4, + "placementY" : 635.513, + "placementWidth" : 87.369, + "placementHeight" : 12.95, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 87.369, + "contentHeight" : 12.95, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 3.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/Sidebar[0]/CoreCompetencies[4]/Competency_1[2]", + "entityName" : "Competency_1", + "entityKind" : "ParagraphNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/Sidebar[0]/CoreCompetencies[4]", + "childIndex" : 2, + "depth" : 5, + "layer" : 5, + "computedX" : 25.4, + "computedY" : 619.563, + "placementX" : 25.4, + "placementY" : 619.563, + "placementWidth" : 95.358, + "placementHeight" : 12.95, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 95.358, + "contentHeight" : 12.95, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 3.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/Sidebar[0]/CoreCompetencies[4]/Competency_2[3]", + "entityName" : "Competency_2", + "entityKind" : "ParagraphNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/Sidebar[0]/CoreCompetencies[4]", + "childIndex" : 3, + "depth" : 5, + "layer" : 5, + "computedX" : 25.4, + "computedY" : 603.613, + "placementX" : 25.4, + "placementY" : 603.613, + "placementWidth" : 91.068, + "placementHeight" : 12.95, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 91.068, + "contentHeight" : 12.95, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 3.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/Sidebar[0]/CoreCompetencies[4]/Competency_3[4]", + "entityName" : "Competency_3", + "entityKind" : "ParagraphNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/Sidebar[0]/CoreCompetencies[4]", + "childIndex" : 4, + "depth" : 5, + "layer" : 5, + "computedX" : 25.4, + "computedY" : 587.663, + "placementX" : 25.4, + "placementY" : 587.663, + "placementWidth" : 83.888, + "placementHeight" : 12.95, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 83.888, + "contentHeight" : 12.95, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 3.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/Sidebar[0]/CoreCompetencies[4]/Competency_4[5]", + "entityName" : "Competency_4", + "entityKind" : "ParagraphNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/Sidebar[0]/CoreCompetencies[4]", + "childIndex" : 5, + "depth" : 5, + "layer" : 5, + "computedX" : 25.4, + "computedY" : 571.713, + "placementX" : 25.4, + "placementY" : 571.713, + "placementWidth" : 30.716, + "placementHeight" : 12.95, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 30.716, + "contentHeight" : 12.95, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 3.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/Sidebar[0]/CoreCompetencies[4]/Competency_5[6]", + "entityName" : "Competency_5", + "entityKind" : "ParagraphNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/Sidebar[0]/CoreCompetencies[4]", + "childIndex" : 6, + "depth" : 5, + "layer" : 5, + "computedX" : 25.4, + "computedY" : 555.763, + "placementX" : 25.4, + "placementY" : 555.763, + "placementWidth" : 47.546, + "placementHeight" : 12.95, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 47.546, + "contentHeight" : 12.95, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 3.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/Sidebar[0]/CoreCompetencies[4]/Competency_6[7]", + "entityName" : "Competency_6", + "entityKind" : "ParagraphNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/Sidebar[0]/CoreCompetencies[4]", + "childIndex" : 7, + "depth" : 5, + "layer" : 5, + "computedX" : 25.4, + "computedY" : 539.813, + "placementX" : 25.4, + "placementY" : 539.813, + "placementWidth" : 77.803, + "placementHeight" : 12.95, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 77.803, + "contentHeight" : 12.95, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 3.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/Sidebar[0]/CoreCompetencies[4]/Competency_7[8]", + "entityName" : "Competency_7", + "entityKind" : "ParagraphNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/Sidebar[0]/CoreCompetencies[4]", + "childIndex" : 8, + "depth" : 5, + "layer" : 5, + "computedX" : 25.4, + "computedY" : 523.863, + "placementX" : 25.4, + "placementY" : 523.863, + "placementWidth" : 90.141, + "placementHeight" : 12.95, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 90.141, + "contentHeight" : 12.95, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 3.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/Sidebar[0]/CoreCompetencies[4]/Competency_8[9]", + "entityName" : "Competency_8", + "entityKind" : "ParagraphNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/Sidebar[0]/CoreCompetencies[4]", + "childIndex" : 9, + "depth" : 5, + "layer" : 5, + "computedX" : 25.4, + "computedY" : 507.913, + "placementX" : 25.4, + "placementY" : 507.913, + "placementWidth" : 71.271, + "placementHeight" : 12.95, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 71.271, + "contentHeight" : 12.95, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 3.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/Sidebar[0]/CoreCompetencies[4]/Competency_9[10]", + "entityName" : "Competency_9", + "entityKind" : "ParagraphNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/Sidebar[0]/CoreCompetencies[4]", + "childIndex" : 10, + "depth" : 5, + "layer" : 5, + "computedX" : 25.4, + "computedY" : 491.963, + "placementX" : 25.4, + "placementY" : 491.963, + "placementWidth" : 106.829, + "placementHeight" : 12.95, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 106.829, + "contentHeight" : 12.95, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 3.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/Sidebar[0]/CoreCompetencies[4]/Competency_10[11]", + "entityName" : "Competency_10", + "entityKind" : "ParagraphNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/Sidebar[0]/CoreCompetencies[4]", + "childIndex" : 11, + "depth" : 5, + "layer" : 5, + "computedX" : 25.4, + "computedY" : 476.013, + "placementX" : 25.4, + "placementY" : 476.013, + "placementWidth" : 81.579, + "placementHeight" : 12.95, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 81.579, + "contentHeight" : 12.95, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 3.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/Sidebar[0]/CoreCompetencies[4]/Competency_11[12]", + "entityName" : "Competency_11", + "entityKind" : "ParagraphNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/Sidebar[0]/CoreCompetencies[4]", + "childIndex" : 12, + "depth" : 5, + "layer" : 5, + "computedX" : 25.4, + "computedY" : 460.063, + "placementX" : 25.4, + "placementY" : 460.063, + "placementWidth" : 86.913, + "placementHeight" : 12.95, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 86.913, + "contentHeight" : 12.95, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 3.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/Sidebar[0]/Divider_AfterCompetencies[5]", + "entityName" : "Divider_AfterCompetencies", + "entityKind" : "LineNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/Sidebar[0]", + "childIndex" : 5, + "depth" : 4, + "layer" : 4, + "computedX" : 25.4, + "computedY" : 448.063, + "placementX" : 25.4, + "placementY" : 448.063, + "placementWidth" : 146.545, + "placementHeight" : 1.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 146.545, + "contentHeight" : 1.0, + "margin" : { + "top" : 8.0, + "right" : 0.0, + "bottom" : 8.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/Sidebar[0]/Software[6]", + "entityName" : "Software", + "entityKind" : "SectionNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/Sidebar[0]", + "childIndex" : 6, + "depth" : 4, + "layer" : 4, + "computedX" : 25.4, + "computedY" : 307.487, + "placementX" : 25.4, + "placementY" : 307.487, + "placementWidth" : 72.249, + "placementHeight" : 132.576, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 72.249, + "contentHeight" : 132.576, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/Sidebar[0]/Software[6]/Heading_SOFTWARE[0]", + "entityName" : "Heading_SOFTWARE", + "entityKind" : "ParagraphNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/Sidebar[0]/Software[6]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 25.4, + "computedY" : 429.537, + "placementX" : 25.4, + "placementY" : 429.537, + "placementWidth" : 52.0, + "placementHeight" : 10.526, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 52.0, + "contentHeight" : 10.526, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 3.5, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/Sidebar[0]/Software[6]/AccentLine_SOFTWARE[1]", + "entityName" : "AccentLine_SOFTWARE", + "entityKind" : "LineNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/Sidebar[0]/Software[6]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 25.4, + "computedY" : 424.637, + "placementX" : 25.4, + "placementY" : 424.637, + "placementWidth" : 20.0, + "placementHeight" : 1.4, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 20.0, + "contentHeight" : 1.4, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 5.5, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/Sidebar[0]/Software[6]/Software_0[2]", + "entityName" : "Software_0", + "entityKind" : "ParagraphNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/Sidebar[0]/Software[6]", + "childIndex" : 2, + "depth" : 5, + "layer" : 5, + "computedX" : 25.4, + "computedY" : 406.187, + "placementX" : 25.4, + "placementY" : 406.187, + "placementWidth" : 29.916, + "placementHeight" : 12.95, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 29.916, + "contentHeight" : 12.95, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 3.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/Sidebar[0]/Software[6]/Software_1[3]", + "entityName" : "Software_1", + "entityKind" : "ParagraphNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/Sidebar[0]/Software[6]", + "childIndex" : 3, + "depth" : 5, + "layer" : 5, + "computedX" : 25.4, + "computedY" : 390.237, + "placementX" : 25.4, + "placementY" : 390.237, + "placementWidth" : 46.746, + "placementHeight" : 12.95, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 46.746, + "contentHeight" : 12.95, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 3.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/Sidebar[0]/Software[6]/Software_2[4]", + "entityName" : "Software_2", + "entityKind" : "ParagraphNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/Sidebar[0]/Software[6]", + "childIndex" : 4, + "depth" : 5, + "layer" : 5, + "computedX" : 25.4, + "computedY" : 374.287, + "placementX" : 25.4, + "placementY" : 374.287, + "placementWidth" : 32.275, + "placementHeight" : 12.95, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 32.275, + "contentHeight" : 12.95, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 3.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/Sidebar[0]/Software[6]/Software_3[5]", + "entityName" : "Software_3", + "entityKind" : "ParagraphNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/Sidebar[0]/Software[6]", + "childIndex" : 5, + "depth" : 5, + "layer" : 5, + "computedX" : 25.4, + "computedY" : 358.337, + "placementX" : 25.4, + "placementY" : 358.337, + "placementWidth" : 46.78, + "placementHeight" : 12.95, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 46.78, + "contentHeight" : 12.95, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 3.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/Sidebar[0]/Software[6]/Software_4[6]", + "entityName" : "Software_4", + "entityKind" : "ParagraphNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/Sidebar[0]/Software[6]", + "childIndex" : 6, + "depth" : 5, + "layer" : 5, + "computedX" : 25.4, + "computedY" : 342.387, + "placementX" : 25.4, + "placementY" : 342.387, + "placementWidth" : 69.999, + "placementHeight" : 12.95, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 69.999, + "contentHeight" : 12.95, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 3.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/Sidebar[0]/Software[6]/Software_5[7]", + "entityName" : "Software_5", + "entityKind" : "ParagraphNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/Sidebar[0]/Software[6]", + "childIndex" : 7, + "depth" : 5, + "layer" : 5, + "computedX" : 25.4, + "computedY" : 326.437, + "placementX" : 25.4, + "placementY" : 326.437, + "placementWidth" : 50.994, + "placementHeight" : 12.95, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 50.994, + "contentHeight" : 12.95, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 3.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/Sidebar[0]/Software[6]/Software_6[8]", + "entityName" : "Software_6", + "entityKind" : "ParagraphNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/Sidebar[0]/Software[6]", + "childIndex" : 8, + "depth" : 5, + "layer" : 5, + "computedX" : 25.4, + "computedY" : 310.487, + "placementX" : 25.4, + "placementY" : 310.487, + "placementWidth" : 72.249, + "placementHeight" : 12.95, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 72.249, + "contentHeight" : 12.95, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 3.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/Sidebar[0]/Divider_AfterSoftware[7]", + "entityName" : "Divider_AfterSoftware", + "entityKind" : "LineNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/Sidebar[0]", + "childIndex" : 7, + "depth" : 4, + "layer" : 4, + "computedX" : 25.4, + "computedY" : 298.487, + "placementX" : 25.4, + "placementY" : 298.487, + "placementWidth" : 146.545, + "placementHeight" : 1.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 146.545, + "contentHeight" : 1.0, + "margin" : { + "top" : 8.0, + "right" : 0.0, + "bottom" : 8.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/Sidebar[0]/Certifications[8]", + "entityName" : "Certifications", + "entityKind" : "SectionNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/Sidebar[0]", + "childIndex" : 8, + "depth" : 4, + "layer" : 4, + "computedX" : 25.4, + "computedY" : 237.661, + "placementX" : 25.4, + "placementY" : 237.661, + "placementWidth" : 105.102, + "placementHeight" : 52.826, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 105.102, + "contentHeight" : 52.826, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/Sidebar[0]/Certifications[8]/Heading_CERTIFICATIONS[0]", + "entityName" : "Heading_CERTIFICATIONS", + "entityKind" : "ParagraphNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/Sidebar[0]/Certifications[8]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 25.4, + "computedY" : 279.961, + "placementX" : 25.4, + "placementY" : 279.961, + "placementWidth" : 77.553, + "placementHeight" : 10.526, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 77.553, + "contentHeight" : 10.526, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 3.5, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/Sidebar[0]/Certifications[8]/AccentLine_CERTIFICATIONS[1]", + "entityName" : "AccentLine_CERTIFICATIONS", + "entityKind" : "LineNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/Sidebar[0]/Certifications[8]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 25.4, + "computedY" : 275.061, + "placementX" : 25.4, + "placementY" : 275.061, + "placementWidth" : 20.0, + "placementHeight" : 1.4, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 20.0, + "contentHeight" : 1.4, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 5.5, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/Sidebar[0]/Certifications[8]/Certification_0[2]", + "entityName" : "Certification_0", + "entityKind" : "ParagraphNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/Sidebar[0]/Certifications[8]", + "childIndex" : 2, + "depth" : 5, + "layer" : 5, + "computedX" : 25.4, + "computedY" : 256.611, + "placementX" : 25.4, + "placementY" : 256.611, + "placementWidth" : 105.102, + "placementHeight" : 12.95, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 105.102, + "contentHeight" : 12.95, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 3.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/Sidebar[0]/Certifications[8]/Certification_1[3]", + "entityName" : "Certification_1", + "entityKind" : "ParagraphNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/Sidebar[0]/Certifications[8]", + "childIndex" : 3, + "depth" : 5, + "layer" : 5, + "computedX" : 25.4, + "computedY" : 240.661, + "placementX" : 25.4, + "placementY" : 240.661, + "placementWidth" : 102.658, + "placementHeight" : 12.95, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 102.658, + "contentHeight" : 12.95, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 3.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/Sidebar[0]/Divider_AfterCertifications[9]", + "entityName" : "Divider_AfterCertifications", + "entityKind" : "LineNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/Sidebar[0]", + "childIndex" : 9, + "depth" : 4, + "layer" : 4, + "computedX" : 25.4, + "computedY" : 228.661, + "placementX" : 25.4, + "placementY" : 228.661, + "placementWidth" : 146.545, + "placementHeight" : 1.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 146.545, + "contentHeight" : 1.0, + "margin" : { + "top" : 8.0, + "right" : 0.0, + "bottom" : 8.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/Sidebar[0]/AdditionalInformation[10]", + "entityName" : "AdditionalInformation", + "entityKind" : "SectionNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/Sidebar[0]", + "childIndex" : 10, + "depth" : 4, + "layer" : 4, + "computedX" : 25.4, + "computedY" : 105.839, + "placementX" : 25.4, + "placementY" : 105.839, + "placementWidth" : 146.545, + "placementHeight" : 114.822, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 146.545, + "contentHeight" : 114.822, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/Sidebar[0]/AdditionalInformation[10]/Heading_ADDITIONALINFORMATION[0]", + "entityName" : "Heading_ADDITIONALINFORMATION", + "entityKind" : "ParagraphNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/Sidebar[0]/AdditionalInformation[10]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 25.4, + "computedY" : 210.134, + "placementX" : 25.4, + "placementY" : 210.134, + "placementWidth" : 131.334, + "placementHeight" : 10.526, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 131.334, + "contentHeight" : 10.526, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 9.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/Sidebar[0]/AdditionalInformation[10]/AddInfo_0Stack[1]", + "entityName" : "AddInfo_0Stack", + "entityKind" : "LayerStackNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/Sidebar[0]/AdditionalInformation[10]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 25.4, + "computedY" : 169.293, + "placementX" : 25.4, + "placementY" : 169.293, + "placementWidth" : 146.545, + "placementHeight" : 31.841, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 146.545, + "contentHeight" : 31.841, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 7.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/Sidebar[0]/AdditionalInformation[10]/AddInfo_0Stack[1]/AddInfo_0Layer[0]", + "entityName" : "AddInfo_0Layer", + "entityKind" : "SectionNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/Sidebar[0]/AdditionalInformation[10]/AddInfo_0Stack[1]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 25.4, + "computedY" : 169.293, + "placementX" : 25.4, + "placementY" : 169.293, + "placementWidth" : 146.545, + "placementHeight" : 31.841, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 146.545, + "contentHeight" : 31.841, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/Sidebar[0]/AdditionalInformation[10]/AddInfo_0Stack[1]/AddInfo_0Layer[0]/AddInfo_0[0]", + "entityName" : "AddInfo_0", + "entityKind" : "RowNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/Sidebar[0]/AdditionalInformation[10]/AddInfo_0Stack[1]/AddInfo_0Layer[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 25.4, + "computedY" : 169.293, + "placementX" : 25.4, + "placementY" : 169.293, + "placementWidth" : 146.545, + "placementHeight" : 31.841, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 146.545, + "contentHeight" : 31.841, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/Sidebar[0]/AdditionalInformation[10]/AddInfo_0Stack[1]/AddInfo_0Layer[0]/AddInfo_0[0]/Icon_0[0]", + "entityName" : "Icon_0", + "entityKind" : "ParagraphNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/Sidebar[0]/AdditionalInformation[10]/AddInfo_0Stack[1]/AddInfo_0Layer[0]/AddInfo_0[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 25.4, + "computedY" : 185.134, + "placementX" : 25.4, + "placementY" : 185.134, + "placementWidth" : 16.0, + "placementHeight" : 16.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 16.0, + "contentHeight" : 16.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/Sidebar[0]/AdditionalInformation[10]/AddInfo_0Stack[1]/AddInfo_0Layer[0]/AddInfo_0[0]/Text_0[1]", + "entityName" : "Text_0", + "entityKind" : "SectionNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/Sidebar[0]/AdditionalInformation[10]/AddInfo_0Stack[1]/AddInfo_0Layer[0]/AddInfo_0[0]", + "childIndex" : 1, + "depth" : 8, + "layer" : 8, + "computedX" : 51.778, + "computedY" : 169.293, + "placementX" : 51.778, + "placementY" : 169.293, + "placementWidth" : 90.753, + "placementHeight" : 31.841, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 90.753, + "contentHeight" : 31.841, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/Sidebar[0]/AdditionalInformation[10]/AddInfo_0Stack[1]/AddInfo_0Layer[0]/AddInfo_0[0]/Text_0[1]/Label_0[0]", + "entityName" : "Label_0", + "entityKind" : "ParagraphNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/Sidebar[0]/AdditionalInformation[10]/AddInfo_0Stack[1]/AddInfo_0Layer[0]/AddInfo_0[0]/Text_0[1]", + "childIndex" : 0, + "depth" : 9, + "layer" : 9, + "computedX" : 51.778, + "computedY" : 191.021, + "placementX" : 51.778, + "placementY" : 191.021, + "placementWidth" : 41.735, + "placementHeight" : 10.114, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 41.735, + "contentHeight" : 10.114, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.5, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/Sidebar[0]/AdditionalInformation[10]/AddInfo_0Stack[1]/AddInfo_0Layer[0]/AddInfo_0[0]/Text_0[1]/Val_0[1]", + "entityName" : "Val_0", + "entityKind" : "ParagraphNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/Sidebar[0]/AdditionalInformation[10]/AddInfo_0Stack[1]/AddInfo_0Layer[0]/AddInfo_0[0]/Text_0[1]", + "childIndex" : 1, + "depth" : 9, + "layer" : 9, + "computedX" : 51.778, + "computedY" : 180.407, + "placementX" : 51.778, + "placementY" : 180.407, + "placementWidth" : 59.367, + "placementHeight" : 10.114, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 59.367, + "contentHeight" : 10.114, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.5, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/Sidebar[0]/AdditionalInformation[10]/AddInfo_0Stack[1]/AddInfo_0Layer[0]/AddInfo_0[0]/Text_0[1]/Val_0[2]", + "entityName" : "Val_0", + "entityKind" : "ParagraphNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/Sidebar[0]/AdditionalInformation[10]/AddInfo_0Stack[1]/AddInfo_0Layer[0]/AddInfo_0[0]/Text_0[1]", + "childIndex" : 2, + "depth" : 9, + "layer" : 9, + "computedX" : 51.778, + "computedY" : 169.793, + "placementX" : 51.778, + "placementY" : 169.793, + "placementWidth" : 90.753, + "placementHeight" : 10.114, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 90.753, + "contentHeight" : 10.114, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.5, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/Sidebar[0]/AdditionalInformation[10]/AddInfo_1Stack[2]", + "entityName" : "AddInfo_1Stack", + "entityKind" : "LayerStackNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/Sidebar[0]/AdditionalInformation[10]", + "childIndex" : 2, + "depth" : 5, + "layer" : 5, + "computedX" : 25.4, + "computedY" : 141.066, + "placementX" : 25.4, + "placementY" : 141.066, + "placementWidth" : 146.545, + "placementHeight" : 21.227, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 146.545, + "contentHeight" : 21.227, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 7.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/Sidebar[0]/AdditionalInformation[10]/AddInfo_1Stack[2]/AddInfo_1Layer[0]", + "entityName" : "AddInfo_1Layer", + "entityKind" : "SectionNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/Sidebar[0]/AdditionalInformation[10]/AddInfo_1Stack[2]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 25.4, + "computedY" : 141.066, + "placementX" : 25.4, + "placementY" : 141.066, + "placementWidth" : 146.545, + "placementHeight" : 21.227, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 146.545, + "contentHeight" : 21.227, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/Sidebar[0]/AdditionalInformation[10]/AddInfo_1Stack[2]/AddInfo_1Layer[0]/AddInfo_1[0]", + "entityName" : "AddInfo_1", + "entityKind" : "RowNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/Sidebar[0]/AdditionalInformation[10]/AddInfo_1Stack[2]/AddInfo_1Layer[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 25.4, + "computedY" : 141.066, + "placementX" : 25.4, + "placementY" : 141.066, + "placementWidth" : 146.545, + "placementHeight" : 21.227, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 146.545, + "contentHeight" : 21.227, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/Sidebar[0]/AdditionalInformation[10]/AddInfo_1Stack[2]/AddInfo_1Layer[0]/AddInfo_1[0]/Icon_1[0]", + "entityName" : "Icon_1", + "entityKind" : "ParagraphNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/Sidebar[0]/AdditionalInformation[10]/AddInfo_1Stack[2]/AddInfo_1Layer[0]/AddInfo_1[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 25.4, + "computedY" : 146.293, + "placementX" : 25.4, + "placementY" : 146.293, + "placementWidth" : 16.0, + "placementHeight" : 16.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 16.0, + "contentHeight" : 16.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/Sidebar[0]/AdditionalInformation[10]/AddInfo_1Stack[2]/AddInfo_1Layer[0]/AddInfo_1[0]/Text_1[1]", + "entityName" : "Text_1", + "entityKind" : "SectionNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/Sidebar[0]/AdditionalInformation[10]/AddInfo_1Stack[2]/AddInfo_1Layer[0]/AddInfo_1[0]", + "childIndex" : 1, + "depth" : 8, + "layer" : 8, + "computedX" : 51.778, + "computedY" : 141.066, + "placementX" : 51.778, + "placementY" : 141.066, + "placementWidth" : 60.412, + "placementHeight" : 21.227, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 60.412, + "contentHeight" : 21.227, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/Sidebar[0]/AdditionalInformation[10]/AddInfo_1Stack[2]/AddInfo_1Layer[0]/AddInfo_1[0]/Text_1[1]/Label_1[0]", + "entityName" : "Label_1", + "entityKind" : "ParagraphNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/Sidebar[0]/AdditionalInformation[10]/AddInfo_1Stack[2]/AddInfo_1Layer[0]/AddInfo_1[0]/Text_1[1]", + "childIndex" : 0, + "depth" : 9, + "layer" : 9, + "computedX" : 51.778, + "computedY" : 152.18, + "placementX" : 51.778, + "placementY" : 152.18, + "placementWidth" : 55.507, + "placementHeight" : 10.114, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 55.507, + "contentHeight" : 10.114, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.5, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/Sidebar[0]/AdditionalInformation[10]/AddInfo_1Stack[2]/AddInfo_1Layer[0]/AddInfo_1[0]/Text_1[1]/Val_1[1]", + "entityName" : "Val_1", + "entityKind" : "ParagraphNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/Sidebar[0]/AdditionalInformation[10]/AddInfo_1Stack[2]/AddInfo_1Layer[0]/AddInfo_1[0]/Text_1[1]", + "childIndex" : 1, + "depth" : 9, + "layer" : 9, + "computedX" : 51.778, + "computedY" : 141.566, + "placementX" : 51.778, + "placementY" : 141.566, + "placementWidth" : 60.412, + "placementHeight" : 10.114, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 60.412, + "contentHeight" : 10.114, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.5, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/Sidebar[0]/AdditionalInformation[10]/AddInfo_2Stack[3]", + "entityName" : "AddInfo_2Stack", + "entityKind" : "LayerStackNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/Sidebar[0]/AdditionalInformation[10]", + "childIndex" : 3, + "depth" : 5, + "layer" : 5, + "computedX" : 25.4, + "computedY" : 112.839, + "placementX" : 25.4, + "placementY" : 112.839, + "placementWidth" : 146.545, + "placementHeight" : 21.227, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 146.545, + "contentHeight" : 21.227, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 7.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/Sidebar[0]/AdditionalInformation[10]/AddInfo_2Stack[3]/AddInfo_2Layer[0]", + "entityName" : "AddInfo_2Layer", + "entityKind" : "SectionNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/Sidebar[0]/AdditionalInformation[10]/AddInfo_2Stack[3]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 25.4, + "computedY" : 112.839, + "placementX" : 25.4, + "placementY" : 112.839, + "placementWidth" : 146.545, + "placementHeight" : 21.227, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 146.545, + "contentHeight" : 21.227, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/Sidebar[0]/AdditionalInformation[10]/AddInfo_2Stack[3]/AddInfo_2Layer[0]/AddInfo_2[0]", + "entityName" : "AddInfo_2", + "entityKind" : "RowNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/Sidebar[0]/AdditionalInformation[10]/AddInfo_2Stack[3]/AddInfo_2Layer[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 25.4, + "computedY" : 112.839, + "placementX" : 25.4, + "placementY" : 112.839, + "placementWidth" : 146.545, + "placementHeight" : 21.227, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 146.545, + "contentHeight" : 21.227, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/Sidebar[0]/AdditionalInformation[10]/AddInfo_2Stack[3]/AddInfo_2Layer[0]/AddInfo_2[0]/Icon_2[0]", + "entityName" : "Icon_2", + "entityKind" : "ParagraphNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/Sidebar[0]/AdditionalInformation[10]/AddInfo_2Stack[3]/AddInfo_2Layer[0]/AddInfo_2[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 25.4, + "computedY" : 118.066, + "placementX" : 25.4, + "placementY" : 118.066, + "placementWidth" : 16.0, + "placementHeight" : 16.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 16.0, + "contentHeight" : 16.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/Sidebar[0]/AdditionalInformation[10]/AddInfo_2Stack[3]/AddInfo_2Layer[0]/AddInfo_2[0]/Text_2[1]", + "entityName" : "Text_2", + "entityKind" : "SectionNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/Sidebar[0]/AdditionalInformation[10]/AddInfo_2Stack[3]/AddInfo_2Layer[0]/AddInfo_2[0]", + "childIndex" : 1, + "depth" : 8, + "layer" : 8, + "computedX" : 51.778, + "computedY" : 112.839, + "placementX" : 51.778, + "placementY" : 112.839, + "placementWidth" : 56.358, + "placementHeight" : 21.227, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 56.358, + "contentHeight" : 21.227, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/Sidebar[0]/AdditionalInformation[10]/AddInfo_2Stack[3]/AddInfo_2Layer[0]/AddInfo_2[0]/Text_2[1]/Label_2[0]", + "entityName" : "Label_2", + "entityKind" : "ParagraphNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/Sidebar[0]/AdditionalInformation[10]/AddInfo_2Stack[3]/AddInfo_2Layer[0]/AddInfo_2[0]/Text_2[1]", + "childIndex" : 0, + "depth" : 9, + "layer" : 9, + "computedX" : 51.778, + "computedY" : 123.953, + "placementX" : 51.778, + "placementY" : 123.953, + "placementWidth" : 44.036, + "placementHeight" : 10.114, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 44.036, + "contentHeight" : 10.114, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.5, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/Sidebar[0]/AdditionalInformation[10]/AddInfo_2Stack[3]/AddInfo_2Layer[0]/AddInfo_2[0]/Text_2[1]/Val_2[1]", + "entityName" : "Val_2", + "entityKind" : "ParagraphNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/Sidebar[0]/AdditionalInformation[10]/AddInfo_2Stack[3]/AddInfo_2Layer[0]/AddInfo_2[0]/Text_2[1]", + "childIndex" : 1, + "depth" : 9, + "layer" : 9, + "computedX" : 51.778, + "computedY" : 113.339, + "placementX" : 51.778, + "placementY" : 113.339, + "placementWidth" : 56.358, + "placementHeight" : 10.114, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 56.358, + "contentHeight" : 10.114, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.5, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]", + "entityName" : "MainColumn", + "entityKind" : "SectionNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]", + "childIndex" : 1, + "depth" : 3, + "layer" : 3, + "computedX" : 183.345, + "computedY" : 47.306, + "placementX" : 183.345, + "placementY" : 47.306, + "placementWidth" : 411.931, + "placementHeight" : 794.584, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 411.931, + "contentHeight" : 794.584, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 20.0, + "right" : 24.0, + "bottom" : 5.0, + "left" : 22.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Masthead[0]", + "entityName" : "Masthead", + "entityKind" : "SectionNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 205.345, + "computedY" : 759.69, + "placementX" : 205.345, + "placementY" : 759.69, + "placementWidth" : 321.748, + "placementHeight" : 62.2, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 321.748, + "contentHeight" : 62.2, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Masthead[0]/FullName[0]", + "entityName" : "FullName", + "entityKind" : "ParagraphNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Masthead[0]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 205.345, + "computedY" : 788.29, + "placementX" : 205.345, + "placementY" : 788.29, + "placementWidth" : 321.748, + "placementHeight" : 33.6, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 321.748, + "contentHeight" : 33.6, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 8.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Masthead[0]/JobTitle[1]", + "entityName" : "JobTitle", + "entityKind" : "ParagraphNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Masthead[0]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 205.345, + "computedY" : 764.69, + "placementX" : 205.345, + "placementY" : 764.69, + "placementWidth" : 169.468, + "placementHeight" : 15.6, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 169.468, + "contentHeight" : 15.6, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 5.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Divider_AfterMasthead[1]", + "entityName" : "Divider_AfterMasthead", + "entityKind" : "LineNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 205.345, + "computedY" : 754.69, + "placementX" : 205.345, + "placementY" : 754.69, + "placementWidth" : 365.931, + "placementHeight" : 1.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 365.931, + "contentHeight" : 1.0, + "margin" : { + "top" : 4.0, + "right" : 0.0, + "bottom" : 6.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Summary[2]", + "entityName" : "Summary", + "entityKind" : "SectionNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]", + "childIndex" : 2, + "depth" : 4, + "layer" : 4, + "computedX" : 205.345, + "computedY" : 654.443, + "placementX" : 205.345, + "placementY" : 654.443, + "placementWidth" : 346.356, + "placementHeight" : 94.246, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 346.356, + "contentHeight" : 94.246, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Summary[2]/Heading_PROFESSIONALSUMMARY[0]", + "entityName" : "Heading_PROFESSIONALSUMMARY", + "entityKind" : "ParagraphNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Summary[2]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 205.345, + "computedY" : 738.163, + "placementX" : 205.345, + "placementY" : 738.163, + "placementWidth" : 133.212, + "placementHeight" : 10.526, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 133.212, + "contentHeight" : 10.526, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 3.5, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Summary[2]/AccentLine_PROFESSIONALSUMMARY[1]", + "entityName" : "AccentLine_PROFESSIONALSUMMARY", + "entityKind" : "LineNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Summary[2]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 205.345, + "computedY" : 733.263, + "placementX" : 205.345, + "placementY" : 733.263, + "placementWidth" : 22.0, + "placementHeight" : 1.4, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 22.0, + "contentHeight" : 1.4, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 5.5, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Summary[2]/SummaryPara_0[2]", + "entityName" : "SummaryPara_0", + "entityKind" : "ParagraphNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Summary[2]", + "childIndex" : 2, + "depth" : 5, + "layer" : 5, + "computedX" : 205.345, + "computedY" : 705.823, + "placementX" : 205.345, + "placementY" : 705.823, + "placementWidth" : 323.91, + "placementHeight" : 21.94, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 323.91, + "contentHeight" : 21.94, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 2.5, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Summary[2]/SummaryPara_1[3]", + "entityName" : "SummaryPara_1", + "entityKind" : "ParagraphNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Summary[2]", + "childIndex" : 3, + "depth" : 5, + "layer" : 5, + "computedX" : 205.345, + "computedY" : 681.383, + "placementX" : 205.345, + "placementY" : 681.383, + "placementWidth" : 346.356, + "placementHeight" : 21.94, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 346.356, + "contentHeight" : 21.94, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 2.5, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Summary[2]/SummaryPara_2[4]", + "entityName" : "SummaryPara_2", + "entityKind" : "ParagraphNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Summary[2]", + "childIndex" : 4, + "depth" : 5, + "layer" : 5, + "computedX" : 205.345, + "computedY" : 656.943, + "placementX" : 205.345, + "placementY" : 656.943, + "placementWidth" : 342.787, + "placementHeight" : 21.94, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 342.787, + "contentHeight" : 21.94, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 2.5, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Divider_AfterSummary[3]", + "entityName" : "Divider_AfterSummary", + "entityKind" : "LineNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]", + "childIndex" : 3, + "depth" : 4, + "layer" : 4, + "computedX" : 205.345, + "computedY" : 647.443, + "placementX" : 205.345, + "placementY" : 647.443, + "placementWidth" : 365.931, + "placementHeight" : 1.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 365.931, + "contentHeight" : 1.0, + "margin" : { + "top" : 6.0, + "right" : 0.0, + "bottom" : 6.5, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Experience[4]", + "entityName" : "Experience", + "entityKind" : "SectionNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]", + "childIndex" : 4, + "depth" : 4, + "layer" : 4, + "computedX" : 205.345, + "computedY" : 303.313, + "placementX" : 205.345, + "placementY" : 303.313, + "placementWidth" : 365.931, + "placementHeight" : 337.631, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 365.931, + "contentHeight" : 337.631, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Experience[4]/Heading_PROFESSIONALEXPERIENCE[0]", + "entityName" : "Heading_PROFESSIONALEXPERIENCE", + "entityKind" : "ParagraphNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Experience[4]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 205.345, + "computedY" : 630.417, + "placementX" : 205.345, + "placementY" : 630.417, + "placementWidth" : 143.475, + "placementHeight" : 10.526, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 143.475, + "contentHeight" : 10.526, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 9.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Experience[4]/ExperienceRail[1]", + "entityName" : "ExperienceRail", + "entityKind" : "LayerStackNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Experience[4]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 205.345, + "computedY" : 303.313, + "placementX" : 205.345, + "placementY" : 303.313, + "placementWidth" : 365.931, + "placementHeight" : 318.104, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 365.931, + "contentHeight" : 318.104, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Experience[4]/ExperienceRail[1]/ExperienceRailHolder[0]", + "entityName" : "ExperienceRailHolder", + "entityKind" : "SectionNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Experience[4]/ExperienceRail[1]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 202.245, + "computedY" : 303.313, + "placementX" : 202.245, + "placementY" : 303.313, + "placementWidth" : 369.031, + "placementHeight" : 318.104, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 369.031, + "contentHeight" : 318.104, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : -3.1 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Experience[4]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]", + "entityName" : null, + "entityKind" : "SectionNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Experience[4]/ExperienceRail[1]/ExperienceRailHolder[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 202.245, + "computedY" : 303.313, + "placementX" : 202.245, + "placementY" : 303.313, + "placementWidth" : 369.031, + "placementHeight" : 318.104, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 369.031, + "contentHeight" : 318.104, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Experience[4]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]", + "entityName" : null, + "entityKind" : "LayoutAnchorNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Experience[4]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 202.245, + "computedY" : 497.072, + "placementX" : 202.245, + "placementY" : 497.072, + "placementWidth" : 369.031, + "placementHeight" : 124.345, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 369.031, + "contentHeight" : 124.345, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Experience[4]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]", + "entityName" : null, + "entityKind" : "SectionNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Experience[4]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]", + "childIndex" : 0, + "depth" : 9, + "layer" : 9, + "computedX" : 202.245, + "computedY" : 497.072, + "placementX" : 202.245, + "placementY" : 497.072, + "placementWidth" : 369.031, + "placementHeight" : 124.345, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 369.031, + "contentHeight" : 124.345, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 9.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Experience[4]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]", + "entityName" : null, + "entityKind" : "HorizontalBandsNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Experience[4]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]", + "childIndex" : 0, + "depth" : 10, + "layer" : 10, + "computedX" : 202.245, + "computedY" : 506.072, + "placementX" : 202.245, + "placementY" : 506.072, + "placementWidth" : 369.031, + "placementHeight" : 115.345, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 369.031, + "contentHeight" : 115.345, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Experience[4]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]", + "entityName" : null, + "entityKind" : "RowNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Experience[4]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]", + "childIndex" : 0, + "depth" : 11, + "layer" : 11, + "computedX" : 202.245, + "computedY" : 506.072, + "placementX" : 202.245, + "placementY" : 506.072, + "placementWidth" : 369.031, + "placementHeight" : 115.345, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 369.031, + "contentHeight" : 115.345, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Experience[4]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]", + "entityName" : null, + "entityKind" : "SectionNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Experience[4]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]", + "childIndex" : 0, + "depth" : 12, + "layer" : 12, + "computedX" : 202.245, + "computedY" : 615.217, + "placementX" : 202.245, + "placementY" : 615.217, + "placementWidth" : 6.2, + "placementHeight" : 6.2, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 6.2, + "contentHeight" : 6.2, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Experience[4]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]", + "entityName" : null, + "entityKind" : "Align", + "parentPath" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Experience[4]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]", + "childIndex" : 0, + "depth" : 13, + "layer" : 13, + "computedX" : 202.245, + "computedY" : 615.217, + "placementX" : 202.245, + "placementY" : 615.217, + "placementWidth" : 6.2, + "placementHeight" : 6.2, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 6.2, + "contentHeight" : 6.2, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Experience[4]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]/LayoutAnchorNode[0]", + "entityName" : null, + "entityKind" : "LayoutAnchorNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Experience[4]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]", + "childIndex" : 0, + "depth" : 14, + "layer" : 14, + "computedX" : 202.245, + "computedY" : 615.217, + "placementX" : 202.245, + "placementY" : 615.217, + "placementWidth" : 6.2, + "placementHeight" : 6.2, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 6.2, + "contentHeight" : 6.2, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Experience[4]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]/LayoutAnchorNode[0]/marker[0]", + "entityName" : "marker", + "entityKind" : "CanvasLayerNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Experience[4]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]/LayoutAnchorNode[0]", + "childIndex" : 0, + "depth" : 15, + "layer" : 15, + "computedX" : 202.245, + "computedY" : 615.217, + "placementX" : 202.245, + "placementY" : 615.217, + "placementWidth" : 6.2, + "placementHeight" : 6.2, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 6.2, + "contentHeight" : 6.2, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Experience[4]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]/LayoutAnchorNode[0]/marker[0]/SectionNode[0]", + "entityName" : null, + "entityKind" : "SectionNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Experience[4]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]/LayoutAnchorNode[0]/marker[0]", + "childIndex" : 0, + "depth" : 16, + "layer" : 16, + "computedX" : 202.245, + "computedY" : 615.217, + "placementX" : 202.245, + "placementY" : 615.217, + "placementWidth" : 6.2, + "placementHeight" : 6.2, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 6.2, + "contentHeight" : 6.2, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Experience[4]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]/LayoutAnchorNode[0]/marker[0]/SectionNode[0]/RoleMarker_0[0]", + "entityName" : "RoleMarker_0", + "entityKind" : "EllipseNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Experience[4]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]/LayoutAnchorNode[0]/marker[0]/SectionNode[0]", + "childIndex" : 0, + "depth" : 17, + "layer" : 17, + "computedX" : 202.245, + "computedY" : 615.217, + "placementX" : 202.245, + "placementY" : 615.217, + "placementWidth" : 6.2, + "placementHeight" : 6.2, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 6.2, + "contentHeight" : 6.2, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Experience[4]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]", + "entityName" : null, + "entityKind" : "SectionNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Experience[4]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]", + "childIndex" : 1, + "depth" : 12, + "layer" : 12, + "computedX" : 218.345, + "computedY" : 506.072, + "placementX" : 218.345, + "placementY" : 506.072, + "placementWidth" : 352.921, + "placementHeight" : 115.345, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 352.921, + "contentHeight" : 115.345, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Experience[4]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]/RoleLine_0[0]", + "entityName" : "RoleLine_0", + "entityKind" : "LayerStackNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Experience[4]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]", + "childIndex" : 0, + "depth" : 13, + "layer" : 13, + "computedX" : 218.345, + "computedY" : 609.652, + "placementX" : 218.345, + "placementY" : 609.652, + "placementWidth" : 352.921, + "placementHeight" : 11.765, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 352.921, + "contentHeight" : 11.765, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Experience[4]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]/RoleLine_0[0]/RoleTable_0[0]", + "entityName" : "RoleTable_0", + "entityKind" : "TableNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Experience[4]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]/RoleLine_0[0]", + "childIndex" : 0, + "depth" : 14, + "layer" : 14, + "computedX" : 218.345, + "computedY" : 609.652, + "placementX" : 218.345, + "placementY" : 609.652, + "placementWidth" : 352.921, + "placementHeight" : 11.765, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 352.921, + "contentHeight" : 11.765, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Experience[4]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]/Employer_0[1]", + "entityName" : "Employer_0", + "entityKind" : "ParagraphNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Experience[4]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]", + "childIndex" : 1, + "depth" : 13, + "layer" : 13, + "computedX" : 218.345, + "computedY" : 599.332, + "placementX" : 218.345, + "placementY" : 599.332, + "placementWidth" : 100.783, + "placementHeight" : 10.32, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 100.783, + "contentHeight" : 10.32, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 2.5, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Experience[4]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]/Highlights_0[2]", + "entityName" : "Highlights_0", + "entityKind" : "ListNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Experience[4]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]", + "childIndex" : 2, + "depth" : 13, + "layer" : 13, + "computedX" : 218.345, + "computedY" : 506.072, + "placementX" : 218.345, + "placementY" : 506.072, + "placementWidth" : 348.171, + "placementHeight" : 90.76, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 348.171, + "contentHeight" : 90.76, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Experience[4]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]", + "entityName" : null, + "entityKind" : "LayoutAnchorNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Experience[4]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]", + "childIndex" : 1, + "depth" : 8, + "layer" : 8, + "computedX" : 202.245, + "computedY" : 384.197, + "placementX" : 202.245, + "placementY" : 384.197, + "placementWidth" : 369.031, + "placementHeight" : 112.875, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 369.031, + "contentHeight" : 112.875, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Experience[4]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]", + "entityName" : null, + "entityKind" : "SectionNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Experience[4]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]", + "childIndex" : 0, + "depth" : 9, + "layer" : 9, + "computedX" : 202.245, + "computedY" : 384.197, + "placementX" : 202.245, + "placementY" : 384.197, + "placementWidth" : 369.031, + "placementHeight" : 112.875, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 369.031, + "contentHeight" : 112.875, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 9.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Experience[4]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]", + "entityName" : null, + "entityKind" : "HorizontalBandsNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Experience[4]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]", + "childIndex" : 0, + "depth" : 10, + "layer" : 10, + "computedX" : 202.245, + "computedY" : 393.197, + "placementX" : 202.245, + "placementY" : 393.197, + "placementWidth" : 369.031, + "placementHeight" : 103.875, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 369.031, + "contentHeight" : 103.875, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Experience[4]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]", + "entityName" : null, + "entityKind" : "RowNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Experience[4]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]", + "childIndex" : 0, + "depth" : 11, + "layer" : 11, + "computedX" : 202.245, + "computedY" : 393.197, + "placementX" : 202.245, + "placementY" : 393.197, + "placementWidth" : 369.031, + "placementHeight" : 103.875, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 369.031, + "contentHeight" : 103.875, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Experience[4]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]", + "entityName" : null, + "entityKind" : "SectionNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Experience[4]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]", + "childIndex" : 0, + "depth" : 12, + "layer" : 12, + "computedX" : 202.245, + "computedY" : 490.872, + "placementX" : 202.245, + "placementY" : 490.872, + "placementWidth" : 6.2, + "placementHeight" : 6.2, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 6.2, + "contentHeight" : 6.2, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Experience[4]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]", + "entityName" : null, + "entityKind" : "Align", + "parentPath" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Experience[4]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]", + "childIndex" : 0, + "depth" : 13, + "layer" : 13, + "computedX" : 202.245, + "computedY" : 490.872, + "placementX" : 202.245, + "placementY" : 490.872, + "placementWidth" : 6.2, + "placementHeight" : 6.2, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 6.2, + "contentHeight" : 6.2, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Experience[4]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]/LayoutAnchorNode[0]", + "entityName" : null, + "entityKind" : "LayoutAnchorNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Experience[4]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]", + "childIndex" : 0, + "depth" : 14, + "layer" : 14, + "computedX" : 202.245, + "computedY" : 490.872, + "placementX" : 202.245, + "placementY" : 490.872, + "placementWidth" : 6.2, + "placementHeight" : 6.2, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 6.2, + "contentHeight" : 6.2, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Experience[4]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]/LayoutAnchorNode[0]/marker[0]", + "entityName" : "marker", + "entityKind" : "CanvasLayerNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Experience[4]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]/LayoutAnchorNode[0]", + "childIndex" : 0, + "depth" : 15, + "layer" : 15, + "computedX" : 202.245, + "computedY" : 490.872, + "placementX" : 202.245, + "placementY" : 490.872, + "placementWidth" : 6.2, + "placementHeight" : 6.2, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 6.2, + "contentHeight" : 6.2, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Experience[4]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]/LayoutAnchorNode[0]/marker[0]/SectionNode[0]", + "entityName" : null, + "entityKind" : "SectionNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Experience[4]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]/LayoutAnchorNode[0]/marker[0]", + "childIndex" : 0, + "depth" : 16, + "layer" : 16, + "computedX" : 202.245, + "computedY" : 490.872, + "placementX" : 202.245, + "placementY" : 490.872, + "placementWidth" : 6.2, + "placementHeight" : 6.2, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 6.2, + "contentHeight" : 6.2, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Experience[4]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]/LayoutAnchorNode[0]/marker[0]/SectionNode[0]/RoleMarker_1[0]", + "entityName" : "RoleMarker_1", + "entityKind" : "EllipseNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Experience[4]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]/LayoutAnchorNode[0]/marker[0]/SectionNode[0]", + "childIndex" : 0, + "depth" : 17, + "layer" : 17, + "computedX" : 202.245, + "computedY" : 490.872, + "placementX" : 202.245, + "placementY" : 490.872, + "placementWidth" : 6.2, + "placementHeight" : 6.2, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 6.2, + "contentHeight" : 6.2, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Experience[4]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]", + "entityName" : null, + "entityKind" : "SectionNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Experience[4]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]", + "childIndex" : 1, + "depth" : 12, + "layer" : 12, + "computedX" : 218.345, + "computedY" : 393.197, + "placementX" : 218.345, + "placementY" : 393.197, + "placementWidth" : 352.921, + "placementHeight" : 103.875, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 352.921, + "contentHeight" : 103.875, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Experience[4]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]/RoleLine_1[0]", + "entityName" : "RoleLine_1", + "entityKind" : "LayerStackNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Experience[4]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]", + "childIndex" : 0, + "depth" : 13, + "layer" : 13, + "computedX" : 218.345, + "computedY" : 485.307, + "placementX" : 218.345, + "placementY" : 485.307, + "placementWidth" : 352.921, + "placementHeight" : 11.765, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 352.921, + "contentHeight" : 11.765, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Experience[4]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]/RoleLine_1[0]/RoleTable_1[0]", + "entityName" : "RoleTable_1", + "entityKind" : "TableNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Experience[4]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]/RoleLine_1[0]", + "childIndex" : 0, + "depth" : 14, + "layer" : 14, + "computedX" : 218.345, + "computedY" : 485.307, + "placementX" : 218.345, + "placementY" : 485.307, + "placementWidth" : 352.921, + "placementHeight" : 11.765, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 352.921, + "contentHeight" : 11.765, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Experience[4]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]/Employer_1[1]", + "entityName" : "Employer_1", + "entityKind" : "ParagraphNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Experience[4]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]", + "childIndex" : 1, + "depth" : 13, + "layer" : 13, + "computedX" : 218.345, + "computedY" : 474.987, + "placementX" : 218.345, + "placementY" : 474.987, + "placementWidth" : 111.654, + "placementHeight" : 10.32, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 111.654, + "contentHeight" : 10.32, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 2.5, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Experience[4]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]/Highlights_1[2]", + "entityName" : "Highlights_1", + "entityKind" : "ListNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Experience[4]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]", + "childIndex" : 2, + "depth" : 13, + "layer" : 13, + "computedX" : 218.345, + "computedY" : 393.197, + "placementX" : 218.345, + "placementY" : 393.197, + "placementWidth" : 349.599, + "placementHeight" : 79.29, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 349.599, + "contentHeight" : 79.29, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Experience[4]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]", + "entityName" : null, + "entityKind" : "LayoutAnchorNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Experience[4]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]", + "childIndex" : 2, + "depth" : 8, + "layer" : 8, + "computedX" : 202.245, + "computedY" : 303.313, + "placementX" : 202.245, + "placementY" : 303.313, + "placementWidth" : 369.031, + "placementHeight" : 80.885, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 369.031, + "contentHeight" : 80.885, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Experience[4]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]", + "entityName" : null, + "entityKind" : "SectionNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Experience[4]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]", + "childIndex" : 0, + "depth" : 9, + "layer" : 9, + "computedX" : 202.245, + "computedY" : 303.313, + "placementX" : 202.245, + "placementY" : 303.313, + "placementWidth" : 369.031, + "placementHeight" : 80.885, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 369.031, + "contentHeight" : 80.885, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Experience[4]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]", + "entityName" : null, + "entityKind" : "HorizontalBandsNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Experience[4]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]", + "childIndex" : 0, + "depth" : 10, + "layer" : 10, + "computedX" : 202.245, + "computedY" : 303.313, + "placementX" : 202.245, + "placementY" : 303.313, + "placementWidth" : 369.031, + "placementHeight" : 80.885, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 369.031, + "contentHeight" : 80.885, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Experience[4]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]", + "entityName" : null, + "entityKind" : "RowNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Experience[4]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]", + "childIndex" : 0, + "depth" : 11, + "layer" : 11, + "computedX" : 202.245, + "computedY" : 303.313, + "placementX" : 202.245, + "placementY" : 303.313, + "placementWidth" : 369.031, + "placementHeight" : 80.885, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 369.031, + "contentHeight" : 80.885, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Experience[4]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]", + "entityName" : null, + "entityKind" : "SectionNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Experience[4]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]", + "childIndex" : 0, + "depth" : 12, + "layer" : 12, + "computedX" : 202.245, + "computedY" : 377.997, + "placementX" : 202.245, + "placementY" : 377.997, + "placementWidth" : 6.2, + "placementHeight" : 6.2, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 6.2, + "contentHeight" : 6.2, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Experience[4]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]", + "entityName" : null, + "entityKind" : "Align", + "parentPath" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Experience[4]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]", + "childIndex" : 0, + "depth" : 13, + "layer" : 13, + "computedX" : 202.245, + "computedY" : 377.997, + "placementX" : 202.245, + "placementY" : 377.997, + "placementWidth" : 6.2, + "placementHeight" : 6.2, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 6.2, + "contentHeight" : 6.2, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Experience[4]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]/LayoutAnchorNode[0]", + "entityName" : null, + "entityKind" : "LayoutAnchorNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Experience[4]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]", + "childIndex" : 0, + "depth" : 14, + "layer" : 14, + "computedX" : 202.245, + "computedY" : 377.997, + "placementX" : 202.245, + "placementY" : 377.997, + "placementWidth" : 6.2, + "placementHeight" : 6.2, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 6.2, + "contentHeight" : 6.2, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Experience[4]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]/LayoutAnchorNode[0]/marker[0]", + "entityName" : "marker", + "entityKind" : "CanvasLayerNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Experience[4]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]/LayoutAnchorNode[0]", + "childIndex" : 0, + "depth" : 15, + "layer" : 15, + "computedX" : 202.245, + "computedY" : 377.997, + "placementX" : 202.245, + "placementY" : 377.997, + "placementWidth" : 6.2, + "placementHeight" : 6.2, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 6.2, + "contentHeight" : 6.2, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Experience[4]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]/LayoutAnchorNode[0]/marker[0]/SectionNode[0]", + "entityName" : null, + "entityKind" : "SectionNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Experience[4]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]/LayoutAnchorNode[0]/marker[0]", + "childIndex" : 0, + "depth" : 16, + "layer" : 16, + "computedX" : 202.245, + "computedY" : 377.997, + "placementX" : 202.245, + "placementY" : 377.997, + "placementWidth" : 6.2, + "placementHeight" : 6.2, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 6.2, + "contentHeight" : 6.2, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Experience[4]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]/LayoutAnchorNode[0]/marker[0]/SectionNode[0]/RoleMarker_2[0]", + "entityName" : "RoleMarker_2", + "entityKind" : "EllipseNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Experience[4]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]/LayoutAnchorNode[0]/marker[0]/SectionNode[0]", + "childIndex" : 0, + "depth" : 17, + "layer" : 17, + "computedX" : 202.245, + "computedY" : 377.997, + "placementX" : 202.245, + "placementY" : 377.997, + "placementWidth" : 6.2, + "placementHeight" : 6.2, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 6.2, + "contentHeight" : 6.2, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Experience[4]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]", + "entityName" : null, + "entityKind" : "SectionNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Experience[4]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]", + "childIndex" : 1, + "depth" : 12, + "layer" : 12, + "computedX" : 218.345, + "computedY" : 303.313, + "placementX" : 218.345, + "placementY" : 303.313, + "placementWidth" : 352.921, + "placementHeight" : 80.885, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 352.921, + "contentHeight" : 80.885, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Experience[4]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]/RoleLine_2[0]", + "entityName" : "RoleLine_2", + "entityKind" : "LayerStackNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Experience[4]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]", + "childIndex" : 0, + "depth" : 13, + "layer" : 13, + "computedX" : 218.345, + "computedY" : 372.433, + "placementX" : 218.345, + "placementY" : 372.433, + "placementWidth" : 352.921, + "placementHeight" : 11.765, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 352.921, + "contentHeight" : 11.765, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Experience[4]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]/RoleLine_2[0]/RoleTable_2[0]", + "entityName" : "RoleTable_2", + "entityKind" : "TableNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Experience[4]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]/RoleLine_2[0]", + "childIndex" : 0, + "depth" : 14, + "layer" : 14, + "computedX" : 218.345, + "computedY" : 372.433, + "placementX" : 218.345, + "placementY" : 372.433, + "placementWidth" : 352.921, + "placementHeight" : 11.765, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 352.921, + "contentHeight" : 11.765, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Experience[4]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]/Employer_2[1]", + "entityName" : "Employer_2", + "entityKind" : "ParagraphNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Experience[4]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]", + "childIndex" : 1, + "depth" : 13, + "layer" : 13, + "computedX" : 218.345, + "computedY" : 362.113, + "placementX" : 218.345, + "placementY" : 362.113, + "placementWidth" : 123.771, + "placementHeight" : 10.32, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 123.771, + "contentHeight" : 10.32, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 2.5, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Experience[4]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]/Highlights_2[2]", + "entityName" : "Highlights_2", + "entityKind" : "ListNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Experience[4]/ExperienceRail[1]/ExperienceRailHolder[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]", + "childIndex" : 2, + "depth" : 13, + "layer" : 13, + "computedX" : 218.345, + "computedY" : 303.313, + "placementX" : 218.345, + "placementY" : 303.313, + "placementWidth" : 329.019, + "placementHeight" : 56.3, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 329.019, + "contentHeight" : 56.3, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Divider_AfterExperience[5]", + "entityName" : "Divider_AfterExperience", + "entityKind" : "LineNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]", + "childIndex" : 5, + "depth" : 4, + "layer" : 4, + "computedX" : 205.345, + "computedY" : 296.313, + "placementX" : 205.345, + "placementY" : 296.313, + "placementWidth" : 365.931, + "placementHeight" : 1.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 365.931, + "contentHeight" : 1.0, + "margin" : { + "top" : 6.0, + "right" : 0.0, + "bottom" : 6.5, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/SelectedProjects[6]", + "entityName" : "SelectedProjects", + "entityKind" : "SectionNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]", + "childIndex" : 6, + "depth" : 4, + "layer" : 4, + "computedX" : 205.345, + "computedY" : 139.902, + "placementX" : 205.345, + "placementY" : 139.902, + "placementWidth" : 365.931, + "placementHeight" : 149.91, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 365.931, + "contentHeight" : 149.91, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/SelectedProjects[6]/Heading_SELECTEDPROJECTS[0]", + "entityName" : "Heading_SELECTEDPROJECTS", + "entityKind" : "ParagraphNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/SelectedProjects[6]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 205.345, + "computedY" : 279.286, + "placementX" : 205.345, + "placementY" : 279.286, + "placementWidth" : 104.79, + "placementHeight" : 10.526, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 104.79, + "contentHeight" : 10.526, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 3.5, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/SelectedProjects[6]/AccentLine_SELECTEDPROJECTS[1]", + "entityName" : "AccentLine_SELECTEDPROJECTS", + "entityKind" : "LineNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/SelectedProjects[6]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 205.345, + "computedY" : 274.386, + "placementX" : 205.345, + "placementY" : 274.386, + "placementWidth" : 22.0, + "placementHeight" : 1.4, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 22.0, + "contentHeight" : 1.4, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 5.5, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/SelectedProjects[6]/Project_0Stack[2]", + "entityName" : "Project_0Stack", + "entityKind" : "LayerStackNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/SelectedProjects[6]", + "childIndex" : 2, + "depth" : 5, + "layer" : 5, + "computedX" : 205.345, + "computedY" : 234.894, + "placementX" : 205.345, + "placementY" : 234.894, + "placementWidth" : 365.931, + "placementHeight" : 33.992, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 365.931, + "contentHeight" : 33.992, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 2.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/SelectedProjects[6]/Project_0Stack[2]/Project_0Layer[0]", + "entityName" : "Project_0Layer", + "entityKind" : "SectionNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/SelectedProjects[6]/Project_0Stack[2]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 205.345, + "computedY" : 234.894, + "placementX" : 205.345, + "placementY" : 234.894, + "placementWidth" : 365.931, + "placementHeight" : 33.992, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 365.931, + "contentHeight" : 33.992, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/SelectedProjects[6]/Project_0Stack[2]/Project_0Layer[0]/Project_0[0]", + "entityName" : "Project_0", + "entityKind" : "RowNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/SelectedProjects[6]/Project_0Stack[2]/Project_0Layer[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 205.345, + "computedY" : 234.894, + "placementX" : 205.345, + "placementY" : 234.894, + "placementWidth" : 365.931, + "placementHeight" : 33.992, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 365.931, + "contentHeight" : 33.992, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/SelectedProjects[6]/Project_0Stack[2]/Project_0Layer[0]/Project_0[0]/ProjIcon_0[0]", + "entityName" : "ProjIcon_0", + "entityKind" : "ParagraphNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/SelectedProjects[6]/Project_0Stack[2]/Project_0Layer[0]/Project_0[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 205.345, + "computedY" : 246.886, + "placementX" : 205.345, + "placementY" : 246.886, + "placementWidth" : 22.0, + "placementHeight" : 22.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 22.0, + "contentHeight" : 22.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/SelectedProjects[6]/Project_0Stack[2]/Project_0Layer[0]/Project_0[0]/ProjMeta_0[1]", + "entityName" : "ProjMeta_0", + "entityKind" : "SectionNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/SelectedProjects[6]/Project_0Stack[2]/Project_0Layer[0]/Project_0[0]", + "childIndex" : 1, + "depth" : 8, + "layer" : 8, + "computedX" : 234.619, + "computedY" : 234.894, + "placementX" : 234.619, + "placementY" : 234.894, + "placementWidth" : 98.269, + "placementHeight" : 33.992, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 98.269, + "contentHeight" : 33.992, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 8.0, + "bottom" : 0.0, + "left" : 4.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/SelectedProjects[6]/Project_0Stack[2]/Project_0Layer[0]/Project_0[0]/ProjMeta_0[1]/ProjTitle_0[0]", + "entityName" : "ProjTitle_0", + "entityKind" : "ParagraphNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/SelectedProjects[6]/Project_0Stack[2]/Project_0Layer[0]/Project_0[0]/ProjMeta_0[1]", + "childIndex" : 0, + "depth" : 9, + "layer" : 9, + "computedX" : 238.619, + "computedY" : 257.121, + "placementX" : 238.619, + "placementY" : 257.121, + "placementWidth" : 62.344, + "placementHeight" : 11.765, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 62.344, + "contentHeight" : 11.765, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 1.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/SelectedProjects[6]/Project_0Stack[2]/Project_0Layer[0]/Project_0[0]/ProjMeta_0[1]/ProjSub_0[1]", + "entityName" : "ProjSub_0", + "entityKind" : "ParagraphNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/SelectedProjects[6]/Project_0Stack[2]/Project_0Layer[0]/Project_0[0]/ProjMeta_0[1]", + "childIndex" : 1, + "depth" : 9, + "layer" : 9, + "computedX" : 238.619, + "computedY" : 246.008, + "placementX" : 238.619, + "placementY" : 246.008, + "placementWidth" : 86.269, + "placementHeight" : 10.114, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 86.269, + "contentHeight" : 10.114, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 1.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/SelectedProjects[6]/Project_0Stack[2]/Project_0Layer[0]/Project_0[0]/ProjMeta_0[1]/ProjLoc_0[2]", + "entityName" : "ProjLoc_0", + "entityKind" : "ParagraphNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/SelectedProjects[6]/Project_0Stack[2]/Project_0Layer[0]/Project_0[0]/ProjMeta_0[1]", + "childIndex" : 2, + "depth" : 9, + "layer" : 9, + "computedX" : 238.619, + "computedY" : 234.894, + "placementX" : 238.619, + "placementY" : 234.894, + "placementWidth" : 23.986, + "placementHeight" : 10.114, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 23.986, + "contentHeight" : 10.114, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/SelectedProjects[6]/Project_0Stack[2]/Project_0Layer[0]/Project_0[0]/ProjDesc_0[2]", + "entityName" : "ProjDesc_0", + "entityKind" : "SectionNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/SelectedProjects[6]/Project_0Stack[2]/Project_0Layer[0]/Project_0[0]", + "childIndex" : 2, + "depth" : 8, + "layer" : 8, + "computedX" : 366.354, + "computedY" : 235.446, + "placementX" : 366.354, + "placementY" : 235.446, + "placementWidth" : 187.16, + "placementHeight" : 33.44, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 187.16, + "contentHeight" : 33.44, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 10.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/SelectedProjects[6]/Project_0Stack[2]/Project_0Layer[0]/Project_0[0]/ProjDesc_0[2]/ProjDescText_0[0]", + "entityName" : "ProjDescText_0", + "entityKind" : "ParagraphNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/SelectedProjects[6]/Project_0Stack[2]/Project_0Layer[0]/Project_0[0]/ProjDesc_0[2]", + "childIndex" : 0, + "depth" : 9, + "layer" : 9, + "computedX" : 376.354, + "computedY" : 235.446, + "placementX" : 376.354, + "placementY" : 235.446, + "placementWidth" : 177.16, + "placementHeight" : 33.44, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 177.16, + "contentHeight" : 33.44, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/SelectedProjects[6]/ProjDiv_0[3]", + "entityName" : "ProjDiv_0", + "entityKind" : "LineNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/SelectedProjects[6]", + "childIndex" : 3, + "depth" : 5, + "layer" : 5, + "computedX" : 205.345, + "computedY" : 229.894, + "placementX" : 205.345, + "placementY" : 229.894, + "placementWidth" : 365.931, + "placementHeight" : 1.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 365.931, + "contentHeight" : 1.0, + "margin" : { + "top" : 2.0, + "right" : 0.0, + "bottom" : 2.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/SelectedProjects[6]/Project_1Stack[4]", + "entityName" : "Project_1Stack", + "entityKind" : "LayerStackNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/SelectedProjects[6]", + "childIndex" : 4, + "depth" : 5, + "layer" : 5, + "computedX" : 205.345, + "computedY" : 193.902, + "placementX" : 205.345, + "placementY" : 193.902, + "placementWidth" : 365.931, + "placementHeight" : 33.992, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 365.931, + "contentHeight" : 33.992, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 2.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/SelectedProjects[6]/Project_1Stack[4]/Project_1Layer[0]", + "entityName" : "Project_1Layer", + "entityKind" : "SectionNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/SelectedProjects[6]/Project_1Stack[4]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 205.345, + "computedY" : 193.902, + "placementX" : 205.345, + "placementY" : 193.902, + "placementWidth" : 365.931, + "placementHeight" : 33.992, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 365.931, + "contentHeight" : 33.992, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/SelectedProjects[6]/Project_1Stack[4]/Project_1Layer[0]/Project_1[0]", + "entityName" : "Project_1", + "entityKind" : "RowNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/SelectedProjects[6]/Project_1Stack[4]/Project_1Layer[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 205.345, + "computedY" : 193.902, + "placementX" : 205.345, + "placementY" : 193.902, + "placementWidth" : 365.931, + "placementHeight" : 33.992, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 365.931, + "contentHeight" : 33.992, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/SelectedProjects[6]/Project_1Stack[4]/Project_1Layer[0]/Project_1[0]/ProjIcon_1[0]", + "entityName" : "ProjIcon_1", + "entityKind" : "ParagraphNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/SelectedProjects[6]/Project_1Stack[4]/Project_1Layer[0]/Project_1[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 205.345, + "computedY" : 205.894, + "placementX" : 205.345, + "placementY" : 205.894, + "placementWidth" : 22.0, + "placementHeight" : 22.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 22.0, + "contentHeight" : 22.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/SelectedProjects[6]/Project_1Stack[4]/Project_1Layer[0]/Project_1[0]/ProjMeta_1[1]", + "entityName" : "ProjMeta_1", + "entityKind" : "SectionNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/SelectedProjects[6]/Project_1Stack[4]/Project_1Layer[0]/Project_1[0]", + "childIndex" : 1, + "depth" : 8, + "layer" : 8, + "computedX" : 234.619, + "computedY" : 193.902, + "placementX" : 234.619, + "placementY" : 193.902, + "placementWidth" : 118.269, + "placementHeight" : 33.992, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 118.269, + "contentHeight" : 33.992, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 8.0, + "bottom" : 0.0, + "left" : 4.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/SelectedProjects[6]/Project_1Stack[4]/Project_1Layer[0]/Project_1[0]/ProjMeta_1[1]/ProjTitle_1[0]", + "entityName" : "ProjTitle_1", + "entityKind" : "ParagraphNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/SelectedProjects[6]/Project_1Stack[4]/Project_1Layer[0]/Project_1[0]/ProjMeta_1[1]", + "childIndex" : 0, + "depth" : 9, + "layer" : 9, + "computedX" : 238.619, + "computedY" : 216.129, + "placementX" : 238.619, + "placementY" : 216.129, + "placementWidth" : 88.011, + "placementHeight" : 11.765, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 88.011, + "contentHeight" : 11.765, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 1.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/SelectedProjects[6]/Project_1Stack[4]/Project_1Layer[0]/Project_1[0]/ProjMeta_1[1]/ProjSub_1[1]", + "entityName" : "ProjSub_1", + "entityKind" : "ParagraphNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/SelectedProjects[6]/Project_1Stack[4]/Project_1Layer[0]/Project_1[0]/ProjMeta_1[1]", + "childIndex" : 1, + "depth" : 9, + "layer" : 9, + "computedX" : 238.619, + "computedY" : 205.016, + "placementX" : 238.619, + "placementY" : 205.016, + "placementWidth" : 106.269, + "placementHeight" : 10.114, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 106.269, + "contentHeight" : 10.114, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 1.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/SelectedProjects[6]/Project_1Stack[4]/Project_1Layer[0]/Project_1[0]/ProjMeta_1[1]/ProjLoc_1[2]", + "entityName" : "ProjLoc_1", + "entityKind" : "ParagraphNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/SelectedProjects[6]/Project_1Stack[4]/Project_1Layer[0]/Project_1[0]/ProjMeta_1[1]", + "childIndex" : 2, + "depth" : 9, + "layer" : 9, + "computedX" : 238.619, + "computedY" : 193.902, + "placementX" : 238.619, + "placementY" : 193.902, + "placementWidth" : 17.37, + "placementHeight" : 10.114, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 17.37, + "contentHeight" : 10.114, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/SelectedProjects[6]/Project_1Stack[4]/Project_1Layer[0]/Project_1[0]/ProjDesc_1[2]", + "entityName" : "ProjDesc_1", + "entityKind" : "SectionNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/SelectedProjects[6]/Project_1Stack[4]/Project_1Layer[0]/Project_1[0]", + "childIndex" : 2, + "depth" : 8, + "layer" : 8, + "computedX" : 366.354, + "computedY" : 194.454, + "placementX" : 366.354, + "placementY" : 194.454, + "placementWidth" : 200.327, + "placementHeight" : 33.44, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 200.327, + "contentHeight" : 33.44, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 10.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/SelectedProjects[6]/Project_1Stack[4]/Project_1Layer[0]/Project_1[0]/ProjDesc_1[2]/ProjDescText_1[0]", + "entityName" : "ProjDescText_1", + "entityKind" : "ParagraphNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/SelectedProjects[6]/Project_1Stack[4]/Project_1Layer[0]/Project_1[0]/ProjDesc_1[2]", + "childIndex" : 0, + "depth" : 9, + "layer" : 9, + "computedX" : 376.354, + "computedY" : 194.454, + "placementX" : 376.354, + "placementY" : 194.454, + "placementWidth" : 190.327, + "placementHeight" : 33.44, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 190.327, + "contentHeight" : 33.44, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/SelectedProjects[6]/ProjDiv_1[5]", + "entityName" : "ProjDiv_1", + "entityKind" : "LineNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/SelectedProjects[6]", + "childIndex" : 5, + "depth" : 5, + "layer" : 5, + "computedX" : 205.345, + "computedY" : 188.902, + "placementX" : 205.345, + "placementY" : 188.902, + "placementWidth" : 365.931, + "placementHeight" : 1.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 365.931, + "contentHeight" : 1.0, + "margin" : { + "top" : 2.0, + "right" : 0.0, + "bottom" : 2.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/SelectedProjects[6]/Project_2Stack[6]", + "entityName" : "Project_2Stack", + "entityKind" : "LayerStackNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/SelectedProjects[6]", + "childIndex" : 6, + "depth" : 5, + "layer" : 5, + "computedX" : 205.345, + "computedY" : 141.902, + "placementX" : 205.345, + "placementY" : 141.902, + "placementWidth" : 365.931, + "placementHeight" : 45.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 365.931, + "contentHeight" : 45.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 2.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/SelectedProjects[6]/Project_2Stack[6]/Project_2Layer[0]", + "entityName" : "Project_2Layer", + "entityKind" : "SectionNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/SelectedProjects[6]/Project_2Stack[6]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 205.345, + "computedY" : 141.902, + "placementX" : 205.345, + "placementY" : 141.902, + "placementWidth" : 365.931, + "placementHeight" : 45.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 365.931, + "contentHeight" : 45.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/SelectedProjects[6]/Project_2Stack[6]/Project_2Layer[0]/Project_2[0]", + "entityName" : "Project_2", + "entityKind" : "RowNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/SelectedProjects[6]/Project_2Stack[6]/Project_2Layer[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 205.345, + "computedY" : 141.902, + "placementX" : 205.345, + "placementY" : 141.902, + "placementWidth" : 365.931, + "placementHeight" : 45.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 365.931, + "contentHeight" : 45.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/SelectedProjects[6]/Project_2Stack[6]/Project_2Layer[0]/Project_2[0]/ProjIcon_2[0]", + "entityName" : "ProjIcon_2", + "entityKind" : "ParagraphNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/SelectedProjects[6]/Project_2Stack[6]/Project_2Layer[0]/Project_2[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 205.345, + "computedY" : 164.902, + "placementX" : 205.345, + "placementY" : 164.902, + "placementWidth" : 22.0, + "placementHeight" : 22.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 22.0, + "contentHeight" : 22.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/SelectedProjects[6]/Project_2Stack[6]/Project_2Layer[0]/Project_2[0]/ProjMeta_2[1]", + "entityName" : "ProjMeta_2", + "entityKind" : "SectionNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/SelectedProjects[6]/Project_2Stack[6]/Project_2Layer[0]/Project_2[0]", + "childIndex" : 1, + "depth" : 8, + "layer" : 8, + "computedX" : 234.619, + "computedY" : 152.91, + "placementX" : 234.619, + "placementY" : 152.91, + "placementWidth" : 99.272, + "placementHeight" : 33.992, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 99.272, + "contentHeight" : 33.992, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 8.0, + "bottom" : 0.0, + "left" : 4.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/SelectedProjects[6]/Project_2Stack[6]/Project_2Layer[0]/Project_2[0]/ProjMeta_2[1]/ProjTitle_2[0]", + "entityName" : "ProjTitle_2", + "entityKind" : "ParagraphNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/SelectedProjects[6]/Project_2Stack[6]/Project_2Layer[0]/Project_2[0]/ProjMeta_2[1]", + "childIndex" : 0, + "depth" : 9, + "layer" : 9, + "computedX" : 238.619, + "computedY" : 175.137, + "placementX" : 238.619, + "placementY" : 175.137, + "placementWidth" : 70.079, + "placementHeight" : 11.765, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 70.079, + "contentHeight" : 11.765, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 1.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/SelectedProjects[6]/Project_2Stack[6]/Project_2Layer[0]/Project_2[0]/ProjMeta_2[1]/ProjSub_2[1]", + "entityName" : "ProjSub_2", + "entityKind" : "ParagraphNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/SelectedProjects[6]/Project_2Stack[6]/Project_2Layer[0]/Project_2[0]/ProjMeta_2[1]", + "childIndex" : 1, + "depth" : 9, + "layer" : 9, + "computedX" : 238.619, + "computedY" : 164.024, + "placementX" : 238.619, + "placementY" : 164.024, + "placementWidth" : 87.272, + "placementHeight" : 10.114, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 87.272, + "contentHeight" : 10.114, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 1.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/SelectedProjects[6]/Project_2Stack[6]/Project_2Layer[0]/Project_2[0]/ProjMeta_2[1]/ProjLoc_2[2]", + "entityName" : "ProjLoc_2", + "entityKind" : "ParagraphNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/SelectedProjects[6]/Project_2Stack[6]/Project_2Layer[0]/Project_2[0]/ProjMeta_2[1]", + "childIndex" : 2, + "depth" : 9, + "layer" : 9, + "computedX" : 238.619, + "computedY" : 152.91, + "placementX" : 238.619, + "placementY" : 152.91, + "placementWidth" : 53.164, + "placementHeight" : 10.114, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 53.164, + "contentHeight" : 10.114, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/SelectedProjects[6]/Project_2Stack[6]/Project_2Layer[0]/Project_2[0]/ProjDesc_2[2]", + "entityName" : "ProjDesc_2", + "entityKind" : "SectionNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/SelectedProjects[6]/Project_2Stack[6]/Project_2Layer[0]/Project_2[0]", + "childIndex" : 2, + "depth" : 8, + "layer" : 8, + "computedX" : 366.354, + "computedY" : 141.902, + "placementX" : 366.354, + "placementY" : 141.902, + "placementWidth" : 166.176, + "placementHeight" : 45.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 166.176, + "contentHeight" : 45.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 10.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/SelectedProjects[6]/Project_2Stack[6]/Project_2Layer[0]/Project_2[0]/ProjDesc_2[2]/ProjDescText_2[0]", + "entityName" : "ProjDescText_2", + "entityKind" : "ParagraphNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/SelectedProjects[6]/Project_2Stack[6]/Project_2Layer[0]/Project_2[0]/ProjDesc_2[2]", + "childIndex" : 0, + "depth" : 9, + "layer" : 9, + "computedX" : 376.354, + "computedY" : 141.902, + "placementX" : 376.354, + "placementY" : 141.902, + "placementWidth" : 156.176, + "placementHeight" : 45.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 156.176, + "contentHeight" : 45.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Divider_AfterProjects[7]", + "entityName" : "Divider_AfterProjects", + "entityKind" : "LineNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]", + "childIndex" : 7, + "depth" : 4, + "layer" : 4, + "computedX" : 205.345, + "computedY" : 132.902, + "placementX" : 205.345, + "placementY" : 132.902, + "placementWidth" : 365.931, + "placementHeight" : 1.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 365.931, + "contentHeight" : 1.0, + "margin" : { + "top" : 6.0, + "right" : 0.0, + "bottom" : 6.5, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Education[8]", + "entityName" : "Education", + "entityKind" : "SectionNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]", + "childIndex" : 8, + "depth" : 4, + "layer" : 4, + "computedX" : 205.345, + "computedY" : 52.306, + "placementX" : 205.345, + "placementY" : 52.306, + "placementWidth" : 365.931, + "placementHeight" : 74.096, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 365.931, + "contentHeight" : 74.096, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Education[8]/Heading_EDUCATION[0]", + "entityName" : "Heading_EDUCATION", + "entityKind" : "ParagraphNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Education[8]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 205.345, + "computedY" : 115.876, + "placementX" : 205.345, + "placementY" : 115.876, + "placementWidth" : 60.632, + "placementHeight" : 10.526, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 60.632, + "contentHeight" : 10.526, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 3.5, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Education[8]/AccentLine_EDUCATION[1]", + "entityName" : "AccentLine_EDUCATION", + "entityKind" : "LineNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Education[8]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 205.345, + "computedY" : 110.976, + "placementX" : 205.345, + "placementY" : 110.976, + "placementWidth" : 22.0, + "placementHeight" : 1.4, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 22.0, + "contentHeight" : 1.4, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 5.5, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Education[8]/EducationBandStack[2]", + "entityName" : "EducationBandStack", + "entityKind" : "LayerStackNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Education[8]", + "childIndex" : 2, + "depth" : 5, + "layer" : 5, + "computedX" : 205.345, + "computedY" : 52.306, + "placementX" : 205.345, + "placementY" : 52.306, + "placementWidth" : 365.931, + "placementHeight" : 53.17, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 365.931, + "contentHeight" : 53.17, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Education[8]/EducationBandStack[2]/EducationBandLayer[0]", + "entityName" : "EducationBandLayer", + "entityKind" : "SectionNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Education[8]/EducationBandStack[2]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 205.345, + "computedY" : 52.306, + "placementX" : 205.345, + "placementY" : 52.306, + "placementWidth" : 365.931, + "placementHeight" : 53.17, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 365.931, + "contentHeight" : 53.17, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Education[8]/EducationBandStack[2]/EducationBandLayer[0]/EducationBand[0]", + "entityName" : "EducationBand", + "entityKind" : "RowNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Education[8]/EducationBandStack[2]/EducationBandLayer[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 205.345, + "computedY" : 52.306, + "placementX" : 205.345, + "placementY" : 52.306, + "placementWidth" : 365.931, + "placementHeight" : 53.17, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 365.931, + "contentHeight" : 53.17, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Education[8]/EducationBandStack[2]/EducationBandLayer[0]/EducationBand[0]/EducationEntries[0]", + "entityName" : "EducationEntries", + "entityKind" : "SectionNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Education[8]/EducationBandStack[2]/EducationBandLayer[0]/EducationBand[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 205.345, + "computedY" : 52.306, + "placementX" : 205.345, + "placementY" : 52.306, + "placementWidth" : 256.152, + "placementHeight" : 53.17, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 256.152, + "contentHeight" : 53.17, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 8.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Education[8]/EducationBandStack[2]/EducationBandLayer[0]/EducationBand[0]/EducationEntries[0]/EducationRail[0]", + "entityName" : "EducationRail", + "entityKind" : "LayerStackNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Education[8]/EducationBandStack[2]/EducationBandLayer[0]/EducationBand[0]/EducationEntries[0]", + "childIndex" : 0, + "depth" : 9, + "layer" : 9, + "computedX" : 205.345, + "computedY" : 52.306, + "placementX" : 205.345, + "placementY" : 52.306, + "placementWidth" : 248.152, + "placementHeight" : 53.17, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 248.152, + "contentHeight" : 53.17, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Education[8]/EducationBandStack[2]/EducationBandLayer[0]/EducationBand[0]/EducationEntries[0]/EducationRail[0]/EducationRailHolder[0]", + "entityName" : "EducationRailHolder", + "entityKind" : "SectionNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Education[8]/EducationBandStack[2]/EducationBandLayer[0]/EducationBand[0]/EducationEntries[0]/EducationRail[0]", + "childIndex" : 0, + "depth" : 10, + "layer" : 10, + "computedX" : 202.245, + "computedY" : 52.306, + "placementX" : 202.245, + "placementY" : 52.306, + "placementWidth" : 251.252, + "placementHeight" : 53.17, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 251.252, + "contentHeight" : 53.17, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : -3.1 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Education[8]/EducationBandStack[2]/EducationBandLayer[0]/EducationBand[0]/EducationEntries[0]/EducationRail[0]/EducationRailHolder[0]/SectionNode[0]", + "entityName" : null, + "entityKind" : "SectionNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Education[8]/EducationBandStack[2]/EducationBandLayer[0]/EducationBand[0]/EducationEntries[0]/EducationRail[0]/EducationRailHolder[0]", + "childIndex" : 0, + "depth" : 11, + "layer" : 11, + "computedX" : 202.245, + "computedY" : 52.306, + "placementX" : 202.245, + "placementY" : 52.306, + "placementWidth" : 251.252, + "placementHeight" : 53.17, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 251.252, + "contentHeight" : 53.17, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Education[8]/EducationBandStack[2]/EducationBandLayer[0]/EducationBand[0]/EducationEntries[0]/EducationRail[0]/EducationRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]", + "entityName" : null, + "entityKind" : "LayoutAnchorNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Education[8]/EducationBandStack[2]/EducationBandLayer[0]/EducationBand[0]/EducationEntries[0]/EducationRail[0]/EducationRailHolder[0]/SectionNode[0]", + "childIndex" : 0, + "depth" : 12, + "layer" : 12, + "computedX" : 202.245, + "computedY" : 74.391, + "placementX" : 202.245, + "placementY" : 74.391, + "placementWidth" : 251.252, + "placementHeight" : 31.085, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 251.252, + "contentHeight" : 31.085, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Education[8]/EducationBandStack[2]/EducationBandLayer[0]/EducationBand[0]/EducationEntries[0]/EducationRail[0]/EducationRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]", + "entityName" : null, + "entityKind" : "SectionNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Education[8]/EducationBandStack[2]/EducationBandLayer[0]/EducationBand[0]/EducationEntries[0]/EducationRail[0]/EducationRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]", + "childIndex" : 0, + "depth" : 13, + "layer" : 13, + "computedX" : 202.245, + "computedY" : 74.391, + "placementX" : 202.245, + "placementY" : 74.391, + "placementWidth" : 251.252, + "placementHeight" : 31.085, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 251.252, + "contentHeight" : 31.085, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 9.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Education[8]/EducationBandStack[2]/EducationBandLayer[0]/EducationBand[0]/EducationEntries[0]/EducationRail[0]/EducationRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]", + "entityName" : null, + "entityKind" : "HorizontalBandsNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Education[8]/EducationBandStack[2]/EducationBandLayer[0]/EducationBand[0]/EducationEntries[0]/EducationRail[0]/EducationRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]", + "childIndex" : 0, + "depth" : 14, + "layer" : 14, + "computedX" : 202.245, + "computedY" : 83.391, + "placementX" : 202.245, + "placementY" : 83.391, + "placementWidth" : 251.252, + "placementHeight" : 22.085, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 251.252, + "contentHeight" : 22.085, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Education[8]/EducationBandStack[2]/EducationBandLayer[0]/EducationBand[0]/EducationEntries[0]/EducationRail[0]/EducationRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]", + "entityName" : null, + "entityKind" : "RowNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Education[8]/EducationBandStack[2]/EducationBandLayer[0]/EducationBand[0]/EducationEntries[0]/EducationRail[0]/EducationRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]", + "childIndex" : 0, + "depth" : 15, + "layer" : 15, + "computedX" : 202.245, + "computedY" : 83.391, + "placementX" : 202.245, + "placementY" : 83.391, + "placementWidth" : 251.252, + "placementHeight" : 22.085, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 251.252, + "contentHeight" : 22.085, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Education[8]/EducationBandStack[2]/EducationBandLayer[0]/EducationBand[0]/EducationEntries[0]/EducationRail[0]/EducationRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]", + "entityName" : null, + "entityKind" : "SectionNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Education[8]/EducationBandStack[2]/EducationBandLayer[0]/EducationBand[0]/EducationEntries[0]/EducationRail[0]/EducationRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]", + "childIndex" : 0, + "depth" : 16, + "layer" : 16, + "computedX" : 202.245, + "computedY" : 99.276, + "placementX" : 202.245, + "placementY" : 99.276, + "placementWidth" : 6.2, + "placementHeight" : 6.2, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 6.2, + "contentHeight" : 6.2, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Education[8]/EducationBandStack[2]/EducationBandLayer[0]/EducationBand[0]/EducationEntries[0]/EducationRail[0]/EducationRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]", + "entityName" : null, + "entityKind" : "Align", + "parentPath" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Education[8]/EducationBandStack[2]/EducationBandLayer[0]/EducationBand[0]/EducationEntries[0]/EducationRail[0]/EducationRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]", + "childIndex" : 0, + "depth" : 17, + "layer" : 17, + "computedX" : 202.245, + "computedY" : 99.276, + "placementX" : 202.245, + "placementY" : 99.276, + "placementWidth" : 6.2, + "placementHeight" : 6.2, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 6.2, + "contentHeight" : 6.2, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Education[8]/EducationBandStack[2]/EducationBandLayer[0]/EducationBand[0]/EducationEntries[0]/EducationRail[0]/EducationRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]/LayoutAnchorNode[0]", + "entityName" : null, + "entityKind" : "LayoutAnchorNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Education[8]/EducationBandStack[2]/EducationBandLayer[0]/EducationBand[0]/EducationEntries[0]/EducationRail[0]/EducationRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]", + "childIndex" : 0, + "depth" : 18, + "layer" : 18, + "computedX" : 202.245, + "computedY" : 99.276, + "placementX" : 202.245, + "placementY" : 99.276, + "placementWidth" : 6.2, + "placementHeight" : 6.2, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 6.2, + "contentHeight" : 6.2, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Education[8]/EducationBandStack[2]/EducationBandLayer[0]/EducationBand[0]/EducationEntries[0]/EducationRail[0]/EducationRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]/LayoutAnchorNode[0]/marker[0]", + "entityName" : "marker", + "entityKind" : "CanvasLayerNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Education[8]/EducationBandStack[2]/EducationBandLayer[0]/EducationBand[0]/EducationEntries[0]/EducationRail[0]/EducationRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]/LayoutAnchorNode[0]", + "childIndex" : 0, + "depth" : 19, + "layer" : 19, + "computedX" : 202.245, + "computedY" : 99.276, + "placementX" : 202.245, + "placementY" : 99.276, + "placementWidth" : 6.2, + "placementHeight" : 6.2, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 6.2, + "contentHeight" : 6.2, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Education[8]/EducationBandStack[2]/EducationBandLayer[0]/EducationBand[0]/EducationEntries[0]/EducationRail[0]/EducationRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]/LayoutAnchorNode[0]/marker[0]/SectionNode[0]", + "entityName" : null, + "entityKind" : "SectionNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Education[8]/EducationBandStack[2]/EducationBandLayer[0]/EducationBand[0]/EducationEntries[0]/EducationRail[0]/EducationRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]/LayoutAnchorNode[0]/marker[0]", + "childIndex" : 0, + "depth" : 20, + "layer" : 20, + "computedX" : 202.245, + "computedY" : 99.276, + "placementX" : 202.245, + "placementY" : 99.276, + "placementWidth" : 6.2, + "placementHeight" : 6.2, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 6.2, + "contentHeight" : 6.2, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Education[8]/EducationBandStack[2]/EducationBandLayer[0]/EducationBand[0]/EducationEntries[0]/EducationRail[0]/EducationRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]/LayoutAnchorNode[0]/marker[0]/SectionNode[0]/EduMarker_0[0]", + "entityName" : "EduMarker_0", + "entityKind" : "EllipseNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Education[8]/EducationBandStack[2]/EducationBandLayer[0]/EducationBand[0]/EducationEntries[0]/EducationRail[0]/EducationRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]/LayoutAnchorNode[0]/marker[0]/SectionNode[0]", + "childIndex" : 0, + "depth" : 21, + "layer" : 21, + "computedX" : 202.245, + "computedY" : 99.276, + "placementX" : 202.245, + "placementY" : 99.276, + "placementWidth" : 6.2, + "placementHeight" : 6.2, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 6.2, + "contentHeight" : 6.2, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Education[8]/EducationBandStack[2]/EducationBandLayer[0]/EducationBand[0]/EducationEntries[0]/EducationRail[0]/EducationRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]", + "entityName" : null, + "entityKind" : "SectionNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Education[8]/EducationBandStack[2]/EducationBandLayer[0]/EducationBand[0]/EducationEntries[0]/EducationRail[0]/EducationRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]", + "childIndex" : 1, + "depth" : 16, + "layer" : 16, + "computedX" : 218.345, + "computedY" : 83.391, + "placementX" : 218.345, + "placementY" : 83.391, + "placementWidth" : 235.142, + "placementHeight" : 22.085, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 235.142, + "contentHeight" : 22.085, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Education[8]/EducationBandStack[2]/EducationBandLayer[0]/EducationBand[0]/EducationEntries[0]/EducationRail[0]/EducationRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]/EduLine_0[0]", + "entityName" : "EduLine_0", + "entityKind" : "LayerStackNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Education[8]/EducationBandStack[2]/EducationBandLayer[0]/EducationBand[0]/EducationEntries[0]/EducationRail[0]/EducationRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]", + "childIndex" : 0, + "depth" : 17, + "layer" : 17, + "computedX" : 218.345, + "computedY" : 93.711, + "placementX" : 218.345, + "placementY" : 93.711, + "placementWidth" : 235.142, + "placementHeight" : 11.765, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 235.142, + "contentHeight" : 11.765, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Education[8]/EducationBandStack[2]/EducationBandLayer[0]/EducationBand[0]/EducationEntries[0]/EducationRail[0]/EducationRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]/EduLine_0[0]/EduTable_0[0]", + "entityName" : "EduTable_0", + "entityKind" : "TableNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Education[8]/EducationBandStack[2]/EducationBandLayer[0]/EducationBand[0]/EducationEntries[0]/EducationRail[0]/EducationRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]/EduLine_0[0]", + "childIndex" : 0, + "depth" : 18, + "layer" : 18, + "computedX" : 218.345, + "computedY" : 93.711, + "placementX" : 218.345, + "placementY" : 93.711, + "placementWidth" : 235.142, + "placementHeight" : 11.765, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 235.142, + "contentHeight" : 11.765, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Education[8]/EducationBandStack[2]/EducationBandLayer[0]/EducationBand[0]/EducationEntries[0]/EducationRail[0]/EducationRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]/Institution_0[1]", + "entityName" : "Institution_0", + "entityKind" : "ParagraphNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Education[8]/EducationBandStack[2]/EducationBandLayer[0]/EducationBand[0]/EducationEntries[0]/EducationRail[0]/EducationRailHolder[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]", + "childIndex" : 1, + "depth" : 17, + "layer" : 17, + "computedX" : 218.345, + "computedY" : 83.391, + "placementX" : 218.345, + "placementY" : 83.391, + "placementWidth" : 78.604, + "placementHeight" : 10.32, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 78.604, + "contentHeight" : 10.32, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Education[8]/EducationBandStack[2]/EducationBandLayer[0]/EducationBand[0]/EducationEntries[0]/EducationRail[0]/EducationRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]", + "entityName" : null, + "entityKind" : "LayoutAnchorNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Education[8]/EducationBandStack[2]/EducationBandLayer[0]/EducationBand[0]/EducationEntries[0]/EducationRail[0]/EducationRailHolder[0]/SectionNode[0]", + "childIndex" : 1, + "depth" : 12, + "layer" : 12, + "computedX" : 202.245, + "computedY" : 52.306, + "placementX" : 202.245, + "placementY" : 52.306, + "placementWidth" : 251.252, + "placementHeight" : 22.085, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 251.252, + "contentHeight" : 22.085, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Education[8]/EducationBandStack[2]/EducationBandLayer[0]/EducationBand[0]/EducationEntries[0]/EducationRail[0]/EducationRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]", + "entityName" : null, + "entityKind" : "SectionNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Education[8]/EducationBandStack[2]/EducationBandLayer[0]/EducationBand[0]/EducationEntries[0]/EducationRail[0]/EducationRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]", + "childIndex" : 0, + "depth" : 13, + "layer" : 13, + "computedX" : 202.245, + "computedY" : 52.306, + "placementX" : 202.245, + "placementY" : 52.306, + "placementWidth" : 251.252, + "placementHeight" : 22.085, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 251.252, + "contentHeight" : 22.085, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Education[8]/EducationBandStack[2]/EducationBandLayer[0]/EducationBand[0]/EducationEntries[0]/EducationRail[0]/EducationRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]", + "entityName" : null, + "entityKind" : "HorizontalBandsNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Education[8]/EducationBandStack[2]/EducationBandLayer[0]/EducationBand[0]/EducationEntries[0]/EducationRail[0]/EducationRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]", + "childIndex" : 0, + "depth" : 14, + "layer" : 14, + "computedX" : 202.245, + "computedY" : 52.306, + "placementX" : 202.245, + "placementY" : 52.306, + "placementWidth" : 251.252, + "placementHeight" : 22.085, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 251.252, + "contentHeight" : 22.085, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Education[8]/EducationBandStack[2]/EducationBandLayer[0]/EducationBand[0]/EducationEntries[0]/EducationRail[0]/EducationRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]", + "entityName" : null, + "entityKind" : "RowNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Education[8]/EducationBandStack[2]/EducationBandLayer[0]/EducationBand[0]/EducationEntries[0]/EducationRail[0]/EducationRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]", + "childIndex" : 0, + "depth" : 15, + "layer" : 15, + "computedX" : 202.245, + "computedY" : 52.306, + "placementX" : 202.245, + "placementY" : 52.306, + "placementWidth" : 251.252, + "placementHeight" : 22.085, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 251.252, + "contentHeight" : 22.085, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Education[8]/EducationBandStack[2]/EducationBandLayer[0]/EducationBand[0]/EducationEntries[0]/EducationRail[0]/EducationRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]", + "entityName" : null, + "entityKind" : "SectionNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Education[8]/EducationBandStack[2]/EducationBandLayer[0]/EducationBand[0]/EducationEntries[0]/EducationRail[0]/EducationRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]", + "childIndex" : 0, + "depth" : 16, + "layer" : 16, + "computedX" : 202.245, + "computedY" : 68.191, + "placementX" : 202.245, + "placementY" : 68.191, + "placementWidth" : 6.2, + "placementHeight" : 6.2, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 6.2, + "contentHeight" : 6.2, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Education[8]/EducationBandStack[2]/EducationBandLayer[0]/EducationBand[0]/EducationEntries[0]/EducationRail[0]/EducationRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]", + "entityName" : null, + "entityKind" : "Align", + "parentPath" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Education[8]/EducationBandStack[2]/EducationBandLayer[0]/EducationBand[0]/EducationEntries[0]/EducationRail[0]/EducationRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]", + "childIndex" : 0, + "depth" : 17, + "layer" : 17, + "computedX" : 202.245, + "computedY" : 68.191, + "placementX" : 202.245, + "placementY" : 68.191, + "placementWidth" : 6.2, + "placementHeight" : 6.2, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 6.2, + "contentHeight" : 6.2, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Education[8]/EducationBandStack[2]/EducationBandLayer[0]/EducationBand[0]/EducationEntries[0]/EducationRail[0]/EducationRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]/LayoutAnchorNode[0]", + "entityName" : null, + "entityKind" : "LayoutAnchorNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Education[8]/EducationBandStack[2]/EducationBandLayer[0]/EducationBand[0]/EducationEntries[0]/EducationRail[0]/EducationRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]", + "childIndex" : 0, + "depth" : 18, + "layer" : 18, + "computedX" : 202.245, + "computedY" : 68.191, + "placementX" : 202.245, + "placementY" : 68.191, + "placementWidth" : 6.2, + "placementHeight" : 6.2, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 6.2, + "contentHeight" : 6.2, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Education[8]/EducationBandStack[2]/EducationBandLayer[0]/EducationBand[0]/EducationEntries[0]/EducationRail[0]/EducationRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]/LayoutAnchorNode[0]/marker[0]", + "entityName" : "marker", + "entityKind" : "CanvasLayerNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Education[8]/EducationBandStack[2]/EducationBandLayer[0]/EducationBand[0]/EducationEntries[0]/EducationRail[0]/EducationRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]/LayoutAnchorNode[0]", + "childIndex" : 0, + "depth" : 19, + "layer" : 19, + "computedX" : 202.245, + "computedY" : 68.191, + "placementX" : 202.245, + "placementY" : 68.191, + "placementWidth" : 6.2, + "placementHeight" : 6.2, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 6.2, + "contentHeight" : 6.2, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Education[8]/EducationBandStack[2]/EducationBandLayer[0]/EducationBand[0]/EducationEntries[0]/EducationRail[0]/EducationRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]/LayoutAnchorNode[0]/marker[0]/SectionNode[0]", + "entityName" : null, + "entityKind" : "SectionNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Education[8]/EducationBandStack[2]/EducationBandLayer[0]/EducationBand[0]/EducationEntries[0]/EducationRail[0]/EducationRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]/LayoutAnchorNode[0]/marker[0]", + "childIndex" : 0, + "depth" : 20, + "layer" : 20, + "computedX" : 202.245, + "computedY" : 68.191, + "placementX" : 202.245, + "placementY" : 68.191, + "placementWidth" : 6.2, + "placementHeight" : 6.2, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 6.2, + "contentHeight" : 6.2, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Education[8]/EducationBandStack[2]/EducationBandLayer[0]/EducationBand[0]/EducationEntries[0]/EducationRail[0]/EducationRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]/LayoutAnchorNode[0]/marker[0]/SectionNode[0]/EduMarker_1[0]", + "entityName" : "EduMarker_1", + "entityKind" : "EllipseNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Education[8]/EducationBandStack[2]/EducationBandLayer[0]/EducationBand[0]/EducationEntries[0]/EducationRail[0]/EducationRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Align[0]/LayoutAnchorNode[0]/marker[0]/SectionNode[0]", + "childIndex" : 0, + "depth" : 21, + "layer" : 21, + "computedX" : 202.245, + "computedY" : 68.191, + "placementX" : 202.245, + "placementY" : 68.191, + "placementWidth" : 6.2, + "placementHeight" : 6.2, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 6.2, + "contentHeight" : 6.2, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Education[8]/EducationBandStack[2]/EducationBandLayer[0]/EducationBand[0]/EducationEntries[0]/EducationRail[0]/EducationRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]", + "entityName" : null, + "entityKind" : "SectionNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Education[8]/EducationBandStack[2]/EducationBandLayer[0]/EducationBand[0]/EducationEntries[0]/EducationRail[0]/EducationRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]", + "childIndex" : 1, + "depth" : 16, + "layer" : 16, + "computedX" : 218.345, + "computedY" : 52.306, + "placementX" : 218.345, + "placementY" : 52.306, + "placementWidth" : 235.142, + "placementHeight" : 22.085, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 235.142, + "contentHeight" : 22.085, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Education[8]/EducationBandStack[2]/EducationBandLayer[0]/EducationBand[0]/EducationEntries[0]/EducationRail[0]/EducationRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]/EduLine_1[0]", + "entityName" : "EduLine_1", + "entityKind" : "LayerStackNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Education[8]/EducationBandStack[2]/EducationBandLayer[0]/EducationBand[0]/EducationEntries[0]/EducationRail[0]/EducationRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]", + "childIndex" : 0, + "depth" : 17, + "layer" : 17, + "computedX" : 218.345, + "computedY" : 62.626, + "placementX" : 218.345, + "placementY" : 62.626, + "placementWidth" : 235.142, + "placementHeight" : 11.765, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 235.142, + "contentHeight" : 11.765, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Education[8]/EducationBandStack[2]/EducationBandLayer[0]/EducationBand[0]/EducationEntries[0]/EducationRail[0]/EducationRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]/EduLine_1[0]/EduTable_1[0]", + "entityName" : "EduTable_1", + "entityKind" : "TableNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Education[8]/EducationBandStack[2]/EducationBandLayer[0]/EducationBand[0]/EducationEntries[0]/EducationRail[0]/EducationRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]/EduLine_1[0]", + "childIndex" : 0, + "depth" : 18, + "layer" : 18, + "computedX" : 218.345, + "computedY" : 62.626, + "placementX" : 218.345, + "placementY" : 62.626, + "placementWidth" : 235.142, + "placementHeight" : 11.765, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 235.142, + "contentHeight" : 11.765, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Education[8]/EducationBandStack[2]/EducationBandLayer[0]/EducationBand[0]/EducationEntries[0]/EducationRail[0]/EducationRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]/Institution_1[1]", + "entityName" : "Institution_1", + "entityKind" : "ParagraphNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Education[8]/EducationBandStack[2]/EducationBandLayer[0]/EducationBand[0]/EducationEntries[0]/EducationRail[0]/EducationRailHolder[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]", + "childIndex" : 1, + "depth" : 17, + "layer" : 17, + "computedX" : 218.345, + "computedY" : 52.306, + "placementX" : 218.345, + "placementY" : 52.306, + "placementWidth" : 120.056, + "placementHeight" : 10.32, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 120.056, + "contentHeight" : 10.32, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Education[8]/EducationBandStack[2]/EducationBandLayer[0]/EducationBand[0]/EducationBandRight[1]", + "entityName" : "EducationBandRight", + "entityKind" : "SectionNode", + "parentPath" : "TerracottaRailCv[0]/Body[0]/MainColumn[1]/Education[8]/EducationBandStack[2]/EducationBandLayer[0]/EducationBand[0]", + "childIndex" : 1, + "depth" : 8, + "layer" : 8, + "computedX" : 461.496, + "computedY" : 105.476, + "placementX" : 461.496, + "placementY" : 105.476, + "placementWidth" : 0.0, + "placementHeight" : 0.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 0.0, + "contentHeight" : 0.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + } ] +} diff --git a/qa/src/test/resources/layout-snapshots/canonical-templates/cv-v2/violet_grid_layout.json b/qa/src/test/resources/layout-snapshots/canonical-templates/cv-v2/violet_grid_layout.json new file mode 100644 index 000000000..f05e0141b --- /dev/null +++ b/qa/src/test/resources/layout-snapshots/canonical-templates/cv-v2/violet_grid_layout.json @@ -0,0 +1,5237 @@ +{ + "formatVersion" : "2.0", + "canvas" : { + "pageWidth" : 595.276, + "pageHeight" : 841.89, + "innerWidth" : 535.466, + "innerHeight" : 818.192, + "margin" : { + "top" : 13.542, + "right" : 29.905, + "bottom" : 10.156, + "left" : 29.905 + } + }, + "totalPages" : 1, + "nodes" : [ { + "path" : "VioletGridCv[0]", + "entityName" : "VioletGridCv", + "entityKind" : "ContainerNode", + "parentPath" : null, + "childIndex" : 0, + "depth" : 1, + "layer" : 1, + "computedX" : 29.905, + "computedY" : 10.348, + "placementX" : 29.905, + "placementY" : 10.348, + "placementWidth" : 535.466, + "placementHeight" : 818.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 535.466, + "contentHeight" : 818.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "VioletGridCv[0]/Header[0]", + "entityName" : "Header", + "entityKind" : "RowNode", + "parentPath" : "VioletGridCv[0]", + "childIndex" : 0, + "depth" : 2, + "layer" : 2, + "computedX" : 29.905, + "computedY" : 734.92, + "placementX" : 29.905, + "placementY" : 734.92, + "placementWidth" : 535.466, + "placementHeight" : 93.428, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 535.466, + "contentHeight" : 93.428, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "VioletGridCv[0]/Header[0]/Identity[0]", + "entityName" : "Identity", + "entityKind" : "SectionNode", + "parentPath" : "VioletGridCv[0]/Header[0]", + "childIndex" : 0, + "depth" : 3, + "layer" : 3, + "computedX" : 29.905, + "computedY" : 739.279, + "placementX" : 29.905, + "placementY" : 739.279, + "placementWidth" : 284.374, + "placementHeight" : 89.069, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 284.374, + "contentHeight" : 89.069, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "VioletGridCv[0]/Header[0]/Identity[0]/Name[0]", + "entityName" : "Name", + "entityKind" : "ParagraphNode", + "parentPath" : "VioletGridCv[0]/Header[0]/Identity[0]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 29.905, + "computedY" : 775.002, + "placementX" : 29.905, + "placementY" : 775.002, + "placementWidth" : 284.374, + "placementHeight" : 53.346, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 284.374, + "contentHeight" : 53.346, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "VioletGridCv[0]/Header[0]/Identity[0]/Discipline[1]", + "entityName" : "Discipline", + "entityKind" : "ParagraphNode", + "parentPath" : "VioletGridCv[0]/Header[0]/Identity[0]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 29.905, + "computedY" : 754.683, + "placementX" : 29.905, + "placementY" : 754.683, + "placementWidth" : 157.277, + "placementHeight" : 20.319, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 157.277, + "contentHeight" : 20.319, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 13.147, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "VioletGridCv[0]/Header[0]/Identity[0]/IdentityRule[2]", + "entityName" : "IdentityRule", + "entityKind" : "LineNode", + "parentPath" : "VioletGridCv[0]/Header[0]/Identity[0]", + "childIndex" : 2, + "depth" : 4, + "layer" : 4, + "computedX" : 29.905, + "computedY" : 739.279, + "placementX" : 29.905, + "placementY" : 739.279, + "placementWidth" : 39.497, + "placementHeight" : 2.257, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 39.497, + "contentHeight" : 2.257, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "VioletGridCv[0]/Header[0]/Contact[1]", + "entityName" : "Contact", + "entityKind" : "SectionNode", + "parentPath" : "VioletGridCv[0]/Header[0]", + "childIndex" : 1, + "depth" : 3, + "layer" : 3, + "computedX" : 398.355, + "computedY" : 734.92, + "placementX" : 398.355, + "placementY" : 734.92, + "placementWidth" : 152.21, + "placementHeight" : 93.428, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 152.21, + "contentHeight" : 93.428, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 4.514, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "VioletGridCv[0]/Header[0]/Contact[1]/Contact_0_email[0]", + "entityName" : "Contact_0_email", + "entityKind" : "ParagraphNode", + "parentPath" : "VioletGridCv[0]/Header[0]/Contact[1]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 398.355, + "computedY" : 812.334, + "placementX" : 398.355, + "placementY" : 812.334, + "placementWidth" : 152.21, + "placementHeight" : 11.5, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 152.21, + "contentHeight" : 11.5, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 7.854, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "VioletGridCv[0]/Header[0]/Contact[1]/Contact_1_phone[1]", + "entityName" : "Contact_1_phone", + "entityKind" : "ParagraphNode", + "parentPath" : "VioletGridCv[0]/Header[0]/Contact[1]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 398.355, + "computedY" : 792.98, + "placementX" : 398.355, + "placementY" : 792.98, + "placementWidth" : 97.746, + "placementHeight" : 11.5, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 97.746, + "contentHeight" : 11.5, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 7.854, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "VioletGridCv[0]/Header[0]/Contact[1]/Contact_2_location[2]", + "entityName" : "Contact_2_location", + "entityKind" : "ParagraphNode", + "parentPath" : "VioletGridCv[0]/Header[0]/Contact[1]", + "childIndex" : 2, + "depth" : 4, + "layer" : 4, + "computedX" : 398.355, + "computedY" : 773.627, + "placementX" : 398.355, + "placementY" : 773.627, + "placementWidth" : 114.927, + "placementHeight" : 11.5, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 114.927, + "contentHeight" : 11.5, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 7.854, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "VioletGridCv[0]/Header[0]/Contact[1]/Contact_3_website[3]", + "entityName" : "Contact_3_website", + "entityKind" : "ParagraphNode", + "parentPath" : "VioletGridCv[0]/Header[0]/Contact[1]", + "childIndex" : 3, + "depth" : 4, + "layer" : 4, + "computedX" : 398.355, + "computedY" : 754.273, + "placementX" : 398.355, + "placementY" : 754.273, + "placementWidth" : 103.158, + "placementHeight" : 11.5, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 103.158, + "contentHeight" : 11.5, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 7.854, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "VioletGridCv[0]/Header[0]/Contact[1]/Contact_4_linkedin[4]", + "entityName" : "Contact_4_linkedin", + "entityKind" : "ParagraphNode", + "parentPath" : "VioletGridCv[0]/Header[0]/Contact[1]", + "childIndex" : 4, + "depth" : 4, + "layer" : 4, + "computedX" : 398.355, + "computedY" : 734.92, + "placementX" : 398.355, + "placementY" : 734.92, + "placementWidth" : 56.382, + "placementHeight" : 11.5, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 56.382, + "contentHeight" : 11.5, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "VioletGridCv[0]/SummaryLine_0[1]", + "entityName" : "SummaryLine_0", + "entityKind" : "ParagraphNode", + "parentPath" : "VioletGridCv[0]", + "childIndex" : 1, + "depth" : 2, + "layer" : 2, + "computedX" : 29.905, + "computedY" : 709.078, + "placementX" : 29.905, + "placementY" : 709.078, + "placementWidth" : 482.062, + "placementHeight" : 11.172, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 482.062, + "contentHeight" : 11.172, + "margin" : { + "top" : 14.67, + "right" : 0.0, + "bottom" : 1.241, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "VioletGridCv[0]/SummaryLine_1[2]", + "entityName" : "SummaryLine_1", + "entityKind" : "ParagraphNode", + "parentPath" : "VioletGridCv[0]", + "childIndex" : 2, + "depth" : 2, + "layer" : 2, + "computedX" : 29.905, + "computedY" : 696.664, + "placementX" : 29.905, + "placementY" : 696.664, + "placementWidth" : 495.804, + "placementHeight" : 11.172, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 495.804, + "contentHeight" : 11.172, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 1.241, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "VioletGridCv[0]/SummaryLine_2[3]", + "entityName" : "SummaryLine_2", + "entityKind" : "ParagraphNode", + "parentPath" : "VioletGridCv[0]", + "childIndex" : 3, + "depth" : 2, + "layer" : 2, + "computedX" : 29.905, + "computedY" : 684.251, + "placementX" : 29.905, + "placementY" : 684.251, + "placementWidth" : 501.018, + "placementHeight" : 11.172, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 501.018, + "contentHeight" : 11.172, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "VioletGridCv[0]/SkillsHeading[4]", + "entityName" : "SkillsHeading", + "entityKind" : "RowNode", + "parentPath" : "VioletGridCv[0]", + "childIndex" : 4, + "depth" : 2, + "layer" : 2, + "computedX" : 29.905, + "computedY" : 650.396, + "placementX" : 29.905, + "placementY" : 650.396, + "placementWidth" : 535.466, + "placementHeight" : 14.783, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 535.466, + "contentHeight" : 14.783, + "margin" : { + "top" : 19.071, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "VioletGridCv[0]/SkillsHeading[4]/SkillsHeadingText[0]", + "entityName" : "SkillsHeadingText", + "entityKind" : "SectionNode", + "parentPath" : "VioletGridCv[0]/SkillsHeading[4]", + "childIndex" : 0, + "depth" : 3, + "layer" : 3, + "computedX" : 29.905, + "computedY" : 650.396, + "placementX" : 29.905, + "placementY" : 650.396, + "placementWidth" : 76.844, + "placementHeight" : 14.783, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 76.844, + "contentHeight" : 14.783, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "VioletGridCv[0]/SkillsHeading[4]/SkillsHeadingText[0]/SkillsHeadingLabel[0]", + "entityName" : "SkillsHeadingLabel", + "entityKind" : "ParagraphNode", + "parentPath" : "VioletGridCv[0]/SkillsHeading[4]/SkillsHeadingText[0]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 29.905, + "computedY" : 650.396, + "placementX" : 29.905, + "placementY" : 650.396, + "placementWidth" : 76.844, + "placementHeight" : 14.783, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 76.844, + "contentHeight" : 14.783, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "VioletGridCv[0]/SkillsHeading[4]/SkillsHeadingRule[1]", + "entityName" : "SkillsHeadingRule", + "entityKind" : "SectionNode", + "parentPath" : "VioletGridCv[0]/SkillsHeading[4]", + "childIndex" : 1, + "depth" : 3, + "layer" : 3, + "computedX" : 106.749, + "computedY" : 656.562, + "placementX" : 106.749, + "placementY" : 656.562, + "placementWidth" : 458.622, + "placementHeight" : 8.617, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 458.622, + "contentHeight" : 8.617, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 7.617, + "right" : 0.0, + "bottom" : 0.0, + "left" : 11.285 + } + }, { + "path" : "VioletGridCv[0]/SkillsHeading[4]/SkillsHeadingRule[1]/SkillsHeadingRuleLine[0]", + "entityName" : "SkillsHeadingRuleLine", + "entityKind" : "LineNode", + "parentPath" : "VioletGridCv[0]/SkillsHeading[4]/SkillsHeadingRule[1]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 118.034, + "computedY" : 656.562, + "placementX" : 118.034, + "placementY" : 656.562, + "placementWidth" : 447.337, + "placementHeight" : 1.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 447.337, + "contentHeight" : 1.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "VioletGridCv[0]/SkillGrid[5]", + "entityName" : "SkillGrid", + "entityKind" : "RowNode", + "parentPath" : "VioletGridCv[0]", + "childIndex" : 5, + "depth" : 2, + "layer" : 2, + "computedX" : 29.905, + "computedY" : 562.656, + "placementX" : 29.905, + "placementY" : 562.656, + "placementWidth" : 535.466, + "placementHeight" : 76.737, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 535.466, + "contentHeight" : 76.737, + "margin" : { + "top" : 11.003, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "VioletGridCv[0]/SkillGrid[5]/Skill_0[0]", + "entityName" : "Skill_0", + "entityKind" : "SectionNode", + "parentPath" : "VioletGridCv[0]/SkillGrid[5]", + "childIndex" : 0, + "depth" : 3, + "layer" : 3, + "computedX" : 29.905, + "computedY" : 569.965, + "placementX" : 29.905, + "placementY" : 569.965, + "placementWidth" : 88.304, + "placementHeight" : 69.429, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 88.304, + "contentHeight" : 69.429, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 6.207, + "bottom" : 0.0, + "left" : 6.207 + } + }, { + "path" : "VioletGridCv[0]/SkillGrid[5]/Skill_0[0]/Align[0]", + "entityName" : null, + "entityKind" : "Align", + "parentPath" : "VioletGridCv[0]/SkillGrid[5]/Skill_0[0]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 36.112, + "computedY" : 618.893, + "placementX" : 36.112, + "placementY" : 618.893, + "placementWidth" : 75.891, + "placementHeight" : 20.5, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 75.891, + "contentHeight" : 20.5, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "VioletGridCv[0]/SkillGrid[5]/Skill_0[0]/Align[0]/SvgIcon[0]", + "entityName" : "SvgIcon", + "entityKind" : "LayerStackNode", + "parentPath" : "VioletGridCv[0]/SkillGrid[5]/Skill_0[0]/Align[0]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 63.807, + "computedY" : 618.893, + "placementX" : 63.807, + "placementY" : 618.893, + "placementWidth" : 20.5, + "placementHeight" : 20.5, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 20.5, + "contentHeight" : 20.5, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "VioletGridCv[0]/SkillGrid[5]/Skill_0[0]/Align[0]/SvgIcon[0]/SvgLayer0[0]", + "entityName" : "SvgLayer0", + "entityKind" : "Path", + "parentPath" : "VioletGridCv[0]/SkillGrid[5]/Skill_0[0]/Align[0]/SvgIcon[0]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 63.807, + "computedY" : 618.893, + "placementX" : 63.807, + "placementY" : 618.893, + "placementWidth" : 20.5, + "placementHeight" : 20.5, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 20.5, + "contentHeight" : 20.5, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "VioletGridCv[0]/SkillGrid[5]/Skill_0[0]/SkillLabel_0[1]", + "entityName" : "SkillLabel_0", + "entityKind" : "ParagraphNode", + "parentPath" : "VioletGridCv[0]/SkillGrid[5]/Skill_0[0]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 36.112, + "computedY" : 601.283, + "placementX" : 36.112, + "placementY" : 601.283, + "placementWidth" : 75.891, + "placementHeight" : 7.793, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 75.891, + "contentHeight" : 7.793, + "margin" : { + "top" : 9.818, + "right" : 0.0, + "bottom" : 3.385, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "VioletGridCv[0]/SkillGrid[5]/Skill_0[0]/SkillDescription_0[2]", + "entityName" : "SkillDescription_0", + "entityKind" : "ParagraphNode", + "parentPath" : "VioletGridCv[0]/SkillGrid[5]/Skill_0[0]", + "childIndex" : 2, + "depth" : 4, + "layer" : 4, + "computedX" : 36.112, + "computedY" : 569.965, + "placementX" : 36.112, + "placementY" : 569.965, + "placementWidth" : 75.891, + "placementHeight" : 27.933, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 75.891, + "contentHeight" : 27.933, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 1.695, + "bottom" : 0.0, + "left" : 1.695 + } + }, { + "path" : "VioletGridCv[0]/SkillGrid[5]/SkillDivider_0[1]", + "entityName" : "SkillDivider_0", + "entityKind" : "SectionNode", + "parentPath" : "VioletGridCv[0]/SkillGrid[5]", + "childIndex" : 1, + "depth" : 3, + "layer" : 3, + "computedX" : 118.209, + "computedY" : 562.656, + "placementX" : 118.209, + "placementY" : 562.656, + "placementWidth" : 1.128, + "placementHeight" : 76.737, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 1.128, + "contentHeight" : 76.737, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "VioletGridCv[0]/SkillGrid[5]/SkillDivider_0[1]/SkillDividerRule[0]", + "entityName" : "SkillDividerRule", + "entityKind" : "LineNode", + "parentPath" : "VioletGridCv[0]/SkillGrid[5]/SkillDivider_0[1]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 118.209, + "computedY" : 562.656, + "placementX" : 118.209, + "placementY" : 562.656, + "placementWidth" : 1.128, + "placementHeight" : 76.737, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 1.128, + "contentHeight" : 76.737, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "VioletGridCv[0]/SkillGrid[5]/Skill_1[2]", + "entityName" : "Skill_1", + "entityKind" : "SectionNode", + "parentPath" : "VioletGridCv[0]/SkillGrid[5]", + "childIndex" : 2, + "depth" : 3, + "layer" : 3, + "computedX" : 119.337, + "computedY" : 580.121, + "placementX" : 119.337, + "placementY" : 580.121, + "placementWidth" : 88.304, + "placementHeight" : 59.272, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 88.304, + "contentHeight" : 59.272, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 6.207, + "bottom" : 0.0, + "left" : 6.207 + } + }, { + "path" : "VioletGridCv[0]/SkillGrid[5]/Skill_1[2]/Align[0]", + "entityName" : null, + "entityKind" : "Align", + "parentPath" : "VioletGridCv[0]/SkillGrid[5]/Skill_1[2]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 125.544, + "computedY" : 618.893, + "placementX" : 125.544, + "placementY" : 618.893, + "placementWidth" : 75.891, + "placementHeight" : 20.5, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 75.891, + "contentHeight" : 20.5, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "VioletGridCv[0]/SkillGrid[5]/Skill_1[2]/Align[0]/SvgIcon[0]", + "entityName" : "SvgIcon", + "entityKind" : "LayerStackNode", + "parentPath" : "VioletGridCv[0]/SkillGrid[5]/Skill_1[2]/Align[0]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 153.239, + "computedY" : 618.893, + "placementX" : 153.239, + "placementY" : 618.893, + "placementWidth" : 20.5, + "placementHeight" : 20.5, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 20.5, + "contentHeight" : 20.5, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "VioletGridCv[0]/SkillGrid[5]/Skill_1[2]/Align[0]/SvgIcon[0]/SvgLayer0[0]", + "entityName" : "SvgLayer0", + "entityKind" : "Path", + "parentPath" : "VioletGridCv[0]/SkillGrid[5]/Skill_1[2]/Align[0]/SvgIcon[0]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 153.239, + "computedY" : 618.893, + "placementX" : 153.239, + "placementY" : 618.893, + "placementWidth" : 20.5, + "placementHeight" : 20.5, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 20.5, + "contentHeight" : 20.5, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "VioletGridCv[0]/SkillGrid[5]/Skill_1[2]/SkillLabel_1[1]", + "entityName" : "SkillLabel_1", + "entityKind" : "ParagraphNode", + "parentPath" : "VioletGridCv[0]/SkillGrid[5]/Skill_1[2]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 125.544, + "computedY" : 601.283, + "placementX" : 125.544, + "placementY" : 601.283, + "placementWidth" : 75.891, + "placementHeight" : 7.793, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 75.891, + "contentHeight" : 7.793, + "margin" : { + "top" : 9.818, + "right" : 0.0, + "bottom" : 3.385, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "VioletGridCv[0]/SkillGrid[5]/Skill_1[2]/SkillDescription_1[2]", + "entityName" : "SkillDescription_1", + "entityKind" : "ParagraphNode", + "parentPath" : "VioletGridCv[0]/SkillGrid[5]/Skill_1[2]", + "childIndex" : 2, + "depth" : 4, + "layer" : 4, + "computedX" : 125.544, + "computedY" : 580.121, + "placementX" : 125.544, + "placementY" : 580.121, + "placementWidth" : 75.891, + "placementHeight" : 17.776, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 75.891, + "contentHeight" : 17.776, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 1.695, + "bottom" : 0.0, + "left" : 1.695 + } + }, { + "path" : "VioletGridCv[0]/SkillGrid[5]/SkillDivider_1[3]", + "entityName" : "SkillDivider_1", + "entityKind" : "SectionNode", + "parentPath" : "VioletGridCv[0]/SkillGrid[5]", + "childIndex" : 3, + "depth" : 3, + "layer" : 3, + "computedX" : 207.641, + "computedY" : 562.656, + "placementX" : 207.641, + "placementY" : 562.656, + "placementWidth" : 1.128, + "placementHeight" : 76.737, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 1.128, + "contentHeight" : 76.737, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "VioletGridCv[0]/SkillGrid[5]/SkillDivider_1[3]/SkillDividerRule[0]", + "entityName" : "SkillDividerRule", + "entityKind" : "LineNode", + "parentPath" : "VioletGridCv[0]/SkillGrid[5]/SkillDivider_1[3]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 207.641, + "computedY" : 562.656, + "placementX" : 207.641, + "placementY" : 562.656, + "placementWidth" : 1.128, + "placementHeight" : 76.737, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 1.128, + "contentHeight" : 76.737, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "VioletGridCv[0]/SkillGrid[5]/Skill_2[4]", + "entityName" : "Skill_2", + "entityKind" : "SectionNode", + "parentPath" : "VioletGridCv[0]/SkillGrid[5]", + "childIndex" : 4, + "depth" : 3, + "layer" : 3, + "computedX" : 208.77, + "computedY" : 569.965, + "placementX" : 208.77, + "placementY" : 569.965, + "placementWidth" : 88.304, + "placementHeight" : 69.429, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 88.304, + "contentHeight" : 69.429, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 6.207, + "bottom" : 0.0, + "left" : 6.207 + } + }, { + "path" : "VioletGridCv[0]/SkillGrid[5]/Skill_2[4]/Align[0]", + "entityName" : null, + "entityKind" : "Align", + "parentPath" : "VioletGridCv[0]/SkillGrid[5]/Skill_2[4]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 214.976, + "computedY" : 618.893, + "placementX" : 214.976, + "placementY" : 618.893, + "placementWidth" : 75.891, + "placementHeight" : 20.5, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 75.891, + "contentHeight" : 20.5, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "VioletGridCv[0]/SkillGrid[5]/Skill_2[4]/Align[0]/SvgIcon[0]", + "entityName" : "SvgIcon", + "entityKind" : "LayerStackNode", + "parentPath" : "VioletGridCv[0]/SkillGrid[5]/Skill_2[4]/Align[0]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 242.672, + "computedY" : 618.893, + "placementX" : 242.672, + "placementY" : 618.893, + "placementWidth" : 20.5, + "placementHeight" : 20.5, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 20.5, + "contentHeight" : 20.5, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "VioletGridCv[0]/SkillGrid[5]/Skill_2[4]/Align[0]/SvgIcon[0]/SvgLayer0[0]", + "entityName" : "SvgLayer0", + "entityKind" : "Path", + "parentPath" : "VioletGridCv[0]/SkillGrid[5]/Skill_2[4]/Align[0]/SvgIcon[0]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 242.672, + "computedY" : 618.893, + "placementX" : 242.672, + "placementY" : 618.893, + "placementWidth" : 20.5, + "placementHeight" : 20.5, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 20.5, + "contentHeight" : 20.5, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "VioletGridCv[0]/SkillGrid[5]/Skill_2[4]/SkillLabel_2[1]", + "entityName" : "SkillLabel_2", + "entityKind" : "ParagraphNode", + "parentPath" : "VioletGridCv[0]/SkillGrid[5]/Skill_2[4]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 214.976, + "computedY" : 601.283, + "placementX" : 214.976, + "placementY" : 601.283, + "placementWidth" : 75.891, + "placementHeight" : 7.793, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 75.891, + "contentHeight" : 7.793, + "margin" : { + "top" : 9.818, + "right" : 0.0, + "bottom" : 3.385, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "VioletGridCv[0]/SkillGrid[5]/Skill_2[4]/SkillDescription_2[2]", + "entityName" : "SkillDescription_2", + "entityKind" : "ParagraphNode", + "parentPath" : "VioletGridCv[0]/SkillGrid[5]/Skill_2[4]", + "childIndex" : 2, + "depth" : 4, + "layer" : 4, + "computedX" : 214.976, + "computedY" : 569.965, + "placementX" : 214.976, + "placementY" : 569.965, + "placementWidth" : 75.891, + "placementHeight" : 27.933, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 75.891, + "contentHeight" : 27.933, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 1.695, + "bottom" : 0.0, + "left" : 1.695 + } + }, { + "path" : "VioletGridCv[0]/SkillGrid[5]/SkillDivider_2[5]", + "entityName" : "SkillDivider_2", + "entityKind" : "SectionNode", + "parentPath" : "VioletGridCv[0]/SkillGrid[5]", + "childIndex" : 5, + "depth" : 3, + "layer" : 3, + "computedX" : 297.074, + "computedY" : 562.656, + "placementX" : 297.074, + "placementY" : 562.656, + "placementWidth" : 1.128, + "placementHeight" : 76.737, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 1.128, + "contentHeight" : 76.737, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "VioletGridCv[0]/SkillGrid[5]/SkillDivider_2[5]/SkillDividerRule[0]", + "entityName" : "SkillDividerRule", + "entityKind" : "LineNode", + "parentPath" : "VioletGridCv[0]/SkillGrid[5]/SkillDivider_2[5]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 297.074, + "computedY" : 562.656, + "placementX" : 297.074, + "placementY" : 562.656, + "placementWidth" : 1.128, + "placementHeight" : 76.737, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 1.128, + "contentHeight" : 76.737, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "VioletGridCv[0]/SkillGrid[5]/Skill_3[6]", + "entityName" : "Skill_3", + "entityKind" : "SectionNode", + "parentPath" : "VioletGridCv[0]/SkillGrid[5]", + "childIndex" : 6, + "depth" : 3, + "layer" : 3, + "computedX" : 298.202, + "computedY" : 569.965, + "placementX" : 298.202, + "placementY" : 569.965, + "placementWidth" : 88.304, + "placementHeight" : 69.429, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 88.304, + "contentHeight" : 69.429, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 6.207, + "bottom" : 0.0, + "left" : 6.207 + } + }, { + "path" : "VioletGridCv[0]/SkillGrid[5]/Skill_3[6]/Align[0]", + "entityName" : null, + "entityKind" : "Align", + "parentPath" : "VioletGridCv[0]/SkillGrid[5]/Skill_3[6]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 304.409, + "computedY" : 618.893, + "placementX" : 304.409, + "placementY" : 618.893, + "placementWidth" : 75.891, + "placementHeight" : 20.5, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 75.891, + "contentHeight" : 20.5, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "VioletGridCv[0]/SkillGrid[5]/Skill_3[6]/Align[0]/SvgIcon[0]", + "entityName" : "SvgIcon", + "entityKind" : "LayerStackNode", + "parentPath" : "VioletGridCv[0]/SkillGrid[5]/Skill_3[6]/Align[0]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 332.104, + "computedY" : 618.893, + "placementX" : 332.104, + "placementY" : 618.893, + "placementWidth" : 20.5, + "placementHeight" : 20.5, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 20.5, + "contentHeight" : 20.5, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "VioletGridCv[0]/SkillGrid[5]/Skill_3[6]/Align[0]/SvgIcon[0]/SvgLayer0[0]", + "entityName" : "SvgLayer0", + "entityKind" : "Path", + "parentPath" : "VioletGridCv[0]/SkillGrid[5]/Skill_3[6]/Align[0]/SvgIcon[0]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 332.104, + "computedY" : 618.893, + "placementX" : 332.104, + "placementY" : 618.893, + "placementWidth" : 20.5, + "placementHeight" : 20.5, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 20.5, + "contentHeight" : 20.5, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "VioletGridCv[0]/SkillGrid[5]/Skill_3[6]/SkillLabel_3[1]", + "entityName" : "SkillLabel_3", + "entityKind" : "ParagraphNode", + "parentPath" : "VioletGridCv[0]/SkillGrid[5]/Skill_3[6]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 304.409, + "computedY" : 601.283, + "placementX" : 304.409, + "placementY" : 601.283, + "placementWidth" : 75.891, + "placementHeight" : 7.793, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 75.891, + "contentHeight" : 7.793, + "margin" : { + "top" : 9.818, + "right" : 0.0, + "bottom" : 3.385, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "VioletGridCv[0]/SkillGrid[5]/Skill_3[6]/SkillDescription_3[2]", + "entityName" : "SkillDescription_3", + "entityKind" : "ParagraphNode", + "parentPath" : "VioletGridCv[0]/SkillGrid[5]/Skill_3[6]", + "childIndex" : 2, + "depth" : 4, + "layer" : 4, + "computedX" : 304.409, + "computedY" : 569.965, + "placementX" : 304.409, + "placementY" : 569.965, + "placementWidth" : 75.891, + "placementHeight" : 27.933, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 75.891, + "contentHeight" : 27.933, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 1.695, + "bottom" : 0.0, + "left" : 1.695 + } + }, { + "path" : "VioletGridCv[0]/SkillGrid[5]/SkillDivider_3[7]", + "entityName" : "SkillDivider_3", + "entityKind" : "SectionNode", + "parentPath" : "VioletGridCv[0]/SkillGrid[5]", + "childIndex" : 7, + "depth" : 3, + "layer" : 3, + "computedX" : 386.506, + "computedY" : 562.656, + "placementX" : 386.506, + "placementY" : 562.656, + "placementWidth" : 1.128, + "placementHeight" : 76.737, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 1.128, + "contentHeight" : 76.737, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "VioletGridCv[0]/SkillGrid[5]/SkillDivider_3[7]/SkillDividerRule[0]", + "entityName" : "SkillDividerRule", + "entityKind" : "LineNode", + "parentPath" : "VioletGridCv[0]/SkillGrid[5]/SkillDivider_3[7]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 386.506, + "computedY" : 562.656, + "placementX" : 386.506, + "placementY" : 562.656, + "placementWidth" : 1.128, + "placementHeight" : 76.737, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 1.128, + "contentHeight" : 76.737, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "VioletGridCv[0]/SkillGrid[5]/Skill_4[8]", + "entityName" : "Skill_4", + "entityKind" : "SectionNode", + "parentPath" : "VioletGridCv[0]/SkillGrid[5]", + "childIndex" : 8, + "depth" : 3, + "layer" : 3, + "computedX" : 387.634, + "computedY" : 569.965, + "placementX" : 387.634, + "placementY" : 569.965, + "placementWidth" : 88.304, + "placementHeight" : 69.429, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 88.304, + "contentHeight" : 69.429, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 6.207, + "bottom" : 0.0, + "left" : 6.207 + } + }, { + "path" : "VioletGridCv[0]/SkillGrid[5]/Skill_4[8]/Align[0]", + "entityName" : null, + "entityKind" : "Align", + "parentPath" : "VioletGridCv[0]/SkillGrid[5]/Skill_4[8]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 393.841, + "computedY" : 618.893, + "placementX" : 393.841, + "placementY" : 618.893, + "placementWidth" : 75.891, + "placementHeight" : 20.5, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 75.891, + "contentHeight" : 20.5, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "VioletGridCv[0]/SkillGrid[5]/Skill_4[8]/Align[0]/SvgIcon[0]", + "entityName" : "SvgIcon", + "entityKind" : "LayerStackNode", + "parentPath" : "VioletGridCv[0]/SkillGrid[5]/Skill_4[8]/Align[0]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 421.536, + "computedY" : 618.893, + "placementX" : 421.536, + "placementY" : 618.893, + "placementWidth" : 20.5, + "placementHeight" : 20.5, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 20.5, + "contentHeight" : 20.5, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "VioletGridCv[0]/SkillGrid[5]/Skill_4[8]/Align[0]/SvgIcon[0]/SvgLayer0[0]", + "entityName" : "SvgLayer0", + "entityKind" : "Path", + "parentPath" : "VioletGridCv[0]/SkillGrid[5]/Skill_4[8]/Align[0]/SvgIcon[0]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 421.536, + "computedY" : 618.893, + "placementX" : 421.536, + "placementY" : 618.893, + "placementWidth" : 20.5, + "placementHeight" : 20.5, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 20.5, + "contentHeight" : 20.5, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "VioletGridCv[0]/SkillGrid[5]/Skill_4[8]/SkillLabel_4[1]", + "entityName" : "SkillLabel_4", + "entityKind" : "ParagraphNode", + "parentPath" : "VioletGridCv[0]/SkillGrid[5]/Skill_4[8]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 393.841, + "computedY" : 601.283, + "placementX" : 393.841, + "placementY" : 601.283, + "placementWidth" : 75.891, + "placementHeight" : 7.793, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 75.891, + "contentHeight" : 7.793, + "margin" : { + "top" : 9.818, + "right" : 0.0, + "bottom" : 3.385, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "VioletGridCv[0]/SkillGrid[5]/Skill_4[8]/SkillDescription_4[2]", + "entityName" : "SkillDescription_4", + "entityKind" : "ParagraphNode", + "parentPath" : "VioletGridCv[0]/SkillGrid[5]/Skill_4[8]", + "childIndex" : 2, + "depth" : 4, + "layer" : 4, + "computedX" : 393.841, + "computedY" : 569.965, + "placementX" : 393.841, + "placementY" : 569.965, + "placementWidth" : 75.891, + "placementHeight" : 27.933, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 75.891, + "contentHeight" : 27.933, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 1.695, + "bottom" : 0.0, + "left" : 1.695 + } + }, { + "path" : "VioletGridCv[0]/SkillGrid[5]/SkillDivider_4[9]", + "entityName" : "SkillDivider_4", + "entityKind" : "SectionNode", + "parentPath" : "VioletGridCv[0]/SkillGrid[5]", + "childIndex" : 9, + "depth" : 3, + "layer" : 3, + "computedX" : 475.938, + "computedY" : 562.656, + "placementX" : 475.938, + "placementY" : 562.656, + "placementWidth" : 1.128, + "placementHeight" : 76.737, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 1.128, + "contentHeight" : 76.737, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "VioletGridCv[0]/SkillGrid[5]/SkillDivider_4[9]/SkillDividerRule[0]", + "entityName" : "SkillDividerRule", + "entityKind" : "LineNode", + "parentPath" : "VioletGridCv[0]/SkillGrid[5]/SkillDivider_4[9]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 475.938, + "computedY" : 562.656, + "placementX" : 475.938, + "placementY" : 562.656, + "placementWidth" : 1.128, + "placementHeight" : 76.737, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 1.128, + "contentHeight" : 76.737, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "VioletGridCv[0]/SkillGrid[5]/Skill_5[10]", + "entityName" : "Skill_5", + "entityKind" : "SectionNode", + "parentPath" : "VioletGridCv[0]/SkillGrid[5]", + "childIndex" : 10, + "depth" : 3, + "layer" : 3, + "computedX" : 477.067, + "computedY" : 569.965, + "placementX" : 477.067, + "placementY" : 569.965, + "placementWidth" : 88.304, + "placementHeight" : 69.429, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 88.304, + "contentHeight" : 69.429, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 6.207, + "bottom" : 0.0, + "left" : 6.207 + } + }, { + "path" : "VioletGridCv[0]/SkillGrid[5]/Skill_5[10]/Align[0]", + "entityName" : null, + "entityKind" : "Align", + "parentPath" : "VioletGridCv[0]/SkillGrid[5]/Skill_5[10]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 483.274, + "computedY" : 618.893, + "placementX" : 483.274, + "placementY" : 618.893, + "placementWidth" : 75.891, + "placementHeight" : 20.5, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 75.891, + "contentHeight" : 20.5, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "VioletGridCv[0]/SkillGrid[5]/Skill_5[10]/Align[0]/SvgIcon[0]", + "entityName" : "SvgIcon", + "entityKind" : "LayerStackNode", + "parentPath" : "VioletGridCv[0]/SkillGrid[5]/Skill_5[10]/Align[0]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 510.969, + "computedY" : 618.893, + "placementX" : 510.969, + "placementY" : 618.893, + "placementWidth" : 20.5, + "placementHeight" : 20.5, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 20.5, + "contentHeight" : 20.5, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "VioletGridCv[0]/SkillGrid[5]/Skill_5[10]/Align[0]/SvgIcon[0]/SvgLayer0[0]", + "entityName" : "SvgLayer0", + "entityKind" : "Path", + "parentPath" : "VioletGridCv[0]/SkillGrid[5]/Skill_5[10]/Align[0]/SvgIcon[0]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 510.969, + "computedY" : 618.893, + "placementX" : 510.969, + "placementY" : 618.893, + "placementWidth" : 20.5, + "placementHeight" : 20.5, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 20.5, + "contentHeight" : 20.5, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "VioletGridCv[0]/SkillGrid[5]/Skill_5[10]/SkillLabel_5[1]", + "entityName" : "SkillLabel_5", + "entityKind" : "ParagraphNode", + "parentPath" : "VioletGridCv[0]/SkillGrid[5]/Skill_5[10]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 483.274, + "computedY" : 601.283, + "placementX" : 483.274, + "placementY" : 601.283, + "placementWidth" : 75.891, + "placementHeight" : 7.793, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 75.891, + "contentHeight" : 7.793, + "margin" : { + "top" : 9.818, + "right" : 0.0, + "bottom" : 3.385, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "VioletGridCv[0]/SkillGrid[5]/Skill_5[10]/SkillDescription_5[2]", + "entityName" : "SkillDescription_5", + "entityKind" : "ParagraphNode", + "parentPath" : "VioletGridCv[0]/SkillGrid[5]/Skill_5[10]", + "childIndex" : 2, + "depth" : 4, + "layer" : 4, + "computedX" : 483.274, + "computedY" : 569.965, + "placementX" : 483.274, + "placementY" : 569.965, + "placementWidth" : 75.891, + "placementHeight" : 27.933, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 75.891, + "contentHeight" : 27.933, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 1.695, + "bottom" : 0.0, + "left" : 1.695 + } + }, { + "path" : "VioletGridCv[0]/ToolsHeading[6]", + "entityName" : "ToolsHeading", + "entityKind" : "RowNode", + "parentPath" : "VioletGridCv[0]", + "childIndex" : 6, + "depth" : 2, + "layer" : 2, + "computedX" : 29.905, + "computedY" : 532.469, + "placementX" : 29.905, + "placementY" : 532.469, + "placementWidth" : 535.466, + "placementHeight" : 14.783, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 535.466, + "contentHeight" : 14.783, + "margin" : { + "top" : 15.404, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "VioletGridCv[0]/ToolsHeading[6]/ToolsHeadingText[0]", + "entityName" : "ToolsHeadingText", + "entityKind" : "SectionNode", + "parentPath" : "VioletGridCv[0]/ToolsHeading[6]", + "childIndex" : 0, + "depth" : 3, + "layer" : 3, + "computedX" : 29.905, + "computedY" : 532.469, + "placementX" : 29.905, + "placementY" : 532.469, + "placementWidth" : 34.064, + "placementHeight" : 14.783, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 34.064, + "contentHeight" : 14.783, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "VioletGridCv[0]/ToolsHeading[6]/ToolsHeadingText[0]/ToolsHeadingLabel[0]", + "entityName" : "ToolsHeadingLabel", + "entityKind" : "ParagraphNode", + "parentPath" : "VioletGridCv[0]/ToolsHeading[6]/ToolsHeadingText[0]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 29.905, + "computedY" : 532.469, + "placementX" : 29.905, + "placementY" : 532.469, + "placementWidth" : 34.064, + "placementHeight" : 14.783, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 34.064, + "contentHeight" : 14.783, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "VioletGridCv[0]/ToolsHeading[6]/ToolsHeadingRule[1]", + "entityName" : "ToolsHeadingRule", + "entityKind" : "SectionNode", + "parentPath" : "VioletGridCv[0]/ToolsHeading[6]", + "childIndex" : 1, + "depth" : 3, + "layer" : 3, + "computedX" : 63.969, + "computedY" : 538.635, + "placementX" : 63.969, + "placementY" : 538.635, + "placementWidth" : 501.402, + "placementHeight" : 8.617, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 501.402, + "contentHeight" : 8.617, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 7.617, + "right" : 0.0, + "bottom" : 0.0, + "left" : 11.285 + } + }, { + "path" : "VioletGridCv[0]/ToolsHeading[6]/ToolsHeadingRule[1]/ToolsHeadingRuleLine[0]", + "entityName" : "ToolsHeadingRuleLine", + "entityKind" : "LineNode", + "parentPath" : "VioletGridCv[0]/ToolsHeading[6]/ToolsHeadingRule[1]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 75.254, + "computedY" : 538.635, + "placementX" : 75.254, + "placementY" : 538.635, + "placementWidth" : 490.117, + "placementHeight" : 1.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 490.117, + "contentHeight" : 1.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "VioletGridCv[0]/ToolsLine[7]", + "entityName" : "ToolsLine", + "entityKind" : "ParagraphNode", + "parentPath" : "VioletGridCv[0]", + "childIndex" : 7, + "depth" : 2, + "layer" : 2, + "computedX" : 29.905, + "computedY" : 512.186, + "placementX" : 29.905, + "placementY" : 512.186, + "placementWidth" : 528.641, + "placementHeight" : 9.732, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 528.641, + "contentHeight" : 9.732, + "margin" : { + "top" : 10.551, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "VioletGridCv[0]/ExperienceHeading[8]", + "entityName" : "ExperienceHeading", + "entityKind" : "RowNode", + "parentPath" : "VioletGridCv[0]", + "childIndex" : 8, + "depth" : 2, + "layer" : 2, + "computedX" : 29.905, + "computedY" : 477.259, + "placementX" : 29.905, + "placementY" : 477.259, + "placementWidth" : 535.466, + "placementHeight" : 14.783, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 535.466, + "contentHeight" : 14.783, + "margin" : { + "top" : 20.143, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "VioletGridCv[0]/ExperienceHeading[8]/ExperienceHeadingText[0]", + "entityName" : "ExperienceHeadingText", + "entityKind" : "SectionNode", + "parentPath" : "VioletGridCv[0]/ExperienceHeading[8]", + "childIndex" : 0, + "depth" : 3, + "layer" : 3, + "computedX" : 29.905, + "computedY" : 477.259, + "placementX" : 29.905, + "placementY" : 477.259, + "placementWidth" : 63.841, + "placementHeight" : 14.783, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 63.841, + "contentHeight" : 14.783, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "VioletGridCv[0]/ExperienceHeading[8]/ExperienceHeadingText[0]/ExperienceHeadingLabel[0]", + "entityName" : "ExperienceHeadingLabel", + "entityKind" : "ParagraphNode", + "parentPath" : "VioletGridCv[0]/ExperienceHeading[8]/ExperienceHeadingText[0]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 29.905, + "computedY" : 477.259, + "placementX" : 29.905, + "placementY" : 477.259, + "placementWidth" : 63.841, + "placementHeight" : 14.783, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 63.841, + "contentHeight" : 14.783, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "VioletGridCv[0]/ExperienceHeading[8]/ExperienceHeadingRule[1]", + "entityName" : "ExperienceHeadingRule", + "entityKind" : "SectionNode", + "parentPath" : "VioletGridCv[0]/ExperienceHeading[8]", + "childIndex" : 1, + "depth" : 3, + "layer" : 3, + "computedX" : 93.746, + "computedY" : 483.425, + "placementX" : 93.746, + "placementY" : 483.425, + "placementWidth" : 471.625, + "placementHeight" : 8.617, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 471.625, + "contentHeight" : 8.617, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 7.617, + "right" : 0.0, + "bottom" : 0.0, + "left" : 11.285 + } + }, { + "path" : "VioletGridCv[0]/ExperienceHeading[8]/ExperienceHeadingRule[1]/ExperienceHeadingRuleLine[0]", + "entityName" : "ExperienceHeadingRuleLine", + "entityKind" : "LineNode", + "parentPath" : "VioletGridCv[0]/ExperienceHeading[8]/ExperienceHeadingRule[1]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 105.031, + "computedY" : 483.425, + "placementX" : 105.031, + "placementY" : 483.425, + "placementWidth" : 460.34, + "placementHeight" : 1.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 460.34, + "contentHeight" : 1.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "VioletGridCv[0]/ExperienceEntries[9]", + "entityName" : "ExperienceEntries", + "entityKind" : "SectionNode", + "parentPath" : "VioletGridCv[0]", + "childIndex" : 9, + "depth" : 2, + "layer" : 2, + "computedX" : 29.905, + "computedY" : 262.073, + "placementX" : 29.905, + "placementY" : 262.073, + "placementWidth" : 535.466, + "placementHeight" : 204.296, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 535.466, + "contentHeight" : 204.296, + "margin" : { + "top" : 10.89, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "VioletGridCv[0]/ExperienceEntries[9]/SectionNode[0]", + "entityName" : null, + "entityKind" : "SectionNode", + "parentPath" : "VioletGridCv[0]/ExperienceEntries[9]", + "childIndex" : 0, + "depth" : 3, + "layer" : 3, + "computedX" : 29.905, + "computedY" : 262.073, + "placementX" : 29.905, + "placementY" : 262.073, + "placementWidth" : 535.466, + "placementHeight" : 204.296, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 535.466, + "contentHeight" : 204.296, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "VioletGridCv[0]/ExperienceEntries[9]/SectionNode[0]/LayoutAnchorNode[0]", + "entityName" : null, + "entityKind" : "LayoutAnchorNode", + "parentPath" : "VioletGridCv[0]/ExperienceEntries[9]/SectionNode[0]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 29.905, + "computedY" : 382.189, + "placementX" : 29.905, + "placementY" : 382.189, + "placementWidth" : 535.466, + "placementHeight" : 84.18, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 535.466, + "contentHeight" : 84.18, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "VioletGridCv[0]/ExperienceEntries[9]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]", + "entityName" : null, + "entityKind" : "SectionNode", + "parentPath" : "VioletGridCv[0]/ExperienceEntries[9]/SectionNode[0]/LayoutAnchorNode[0]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 29.905, + "computedY" : 382.189, + "placementX" : 29.905, + "placementY" : 382.189, + "placementWidth" : 535.466, + "placementHeight" : 84.18, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 535.466, + "contentHeight" : 84.18, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 13.542, + "left" : 0.0 + } + }, { + "path" : "VioletGridCv[0]/ExperienceEntries[9]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]", + "entityName" : null, + "entityKind" : "HorizontalBandsNode", + "parentPath" : "VioletGridCv[0]/ExperienceEntries[9]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 29.905, + "computedY" : 395.731, + "placementX" : 29.905, + "placementY" : 395.731, + "placementWidth" : 535.466, + "placementHeight" : 70.638, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 535.466, + "contentHeight" : 70.638, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "VioletGridCv[0]/ExperienceEntries[9]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]", + "entityName" : null, + "entityKind" : "RowNode", + "parentPath" : "VioletGridCv[0]/ExperienceEntries[9]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 29.905, + "computedY" : 395.731, + "placementX" : 29.905, + "placementY" : 395.731, + "placementWidth" : 535.466, + "placementHeight" : 70.638, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 535.466, + "contentHeight" : 70.638, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "VioletGridCv[0]/ExperienceEntries[9]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]", + "entityName" : null, + "entityKind" : "SectionNode", + "parentPath" : "VioletGridCv[0]/ExperienceEntries[9]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 29.905, + "computedY" : 455.515, + "placementX" : 29.905, + "placementY" : 455.515, + "placementWidth" : 57.005, + "placementHeight" : 10.854, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 57.005, + "contentHeight" : 10.854, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.45, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "VioletGridCv[0]/ExperienceEntries[9]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Period_0[0]", + "entityName" : "Period_0", + "entityKind" : "ParagraphNode", + "parentPath" : "VioletGridCv[0]/ExperienceEntries[9]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]", + "childIndex" : 0, + "depth" : 9, + "layer" : 9, + "computedX" : 29.905, + "computedY" : 455.515, + "placementX" : 29.905, + "placementY" : 455.515, + "placementWidth" : 57.005, + "placementHeight" : 10.404, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 57.005, + "contentHeight" : 10.404, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "VioletGridCv[0]/ExperienceEntries[9]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]", + "entityName" : null, + "entityKind" : "SectionNode", + "parentPath" : "VioletGridCv[0]/ExperienceEntries[9]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]", + "childIndex" : 1, + "depth" : 8, + "layer" : 8, + "computedX" : 91.83, + "computedY" : 466.369, + "placementX" : 91.83, + "placementY" : 466.369, + "placementWidth" : 0.0, + "placementHeight" : 0.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 0.0, + "contentHeight" : 0.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "VioletGridCv[0]/ExperienceEntries[9]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[2]", + "entityName" : null, + "entityKind" : "SectionNode", + "parentPath" : "VioletGridCv[0]/ExperienceEntries[9]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]", + "childIndex" : 2, + "depth" : 8, + "layer" : 8, + "computedX" : 101.987, + "computedY" : 455.065, + "placementX" : 101.987, + "placementY" : 455.065, + "placementWidth" : 7.617, + "placementHeight" : 11.304, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 7.617, + "contentHeight" : 11.304, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "VioletGridCv[0]/ExperienceEntries[9]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[2]/Align[0]", + "entityName" : null, + "entityKind" : "Align", + "parentPath" : "VioletGridCv[0]/ExperienceEntries[9]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[2]", + "childIndex" : 0, + "depth" : 9, + "layer" : 9, + "computedX" : 101.987, + "computedY" : 455.065, + "placementX" : 101.987, + "placementY" : 455.065, + "placementWidth" : 7.617, + "placementHeight" : 11.304, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 7.617, + "contentHeight" : 11.304, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "VioletGridCv[0]/ExperienceEntries[9]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[2]/Align[0]/LayoutAnchorNode[0]", + "entityName" : null, + "entityKind" : "LayoutAnchorNode", + "parentPath" : "VioletGridCv[0]/ExperienceEntries[9]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[2]/Align[0]", + "childIndex" : 0, + "depth" : 10, + "layer" : 10, + "computedX" : 101.987, + "computedY" : 455.065, + "placementX" : 101.987, + "placementY" : 455.065, + "placementWidth" : 7.617, + "placementHeight" : 11.304, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 7.617, + "contentHeight" : 11.304, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "VioletGridCv[0]/ExperienceEntries[9]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[2]/Align[0]/LayoutAnchorNode[0]/marker[0]", + "entityName" : "marker", + "entityKind" : "CanvasLayerNode", + "parentPath" : "VioletGridCv[0]/ExperienceEntries[9]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[2]/Align[0]/LayoutAnchorNode[0]", + "childIndex" : 0, + "depth" : 11, + "layer" : 11, + "computedX" : 101.987, + "computedY" : 455.065, + "placementX" : 101.987, + "placementY" : 455.065, + "placementWidth" : 7.617, + "placementHeight" : 11.304, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 7.617, + "contentHeight" : 11.304, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "VioletGridCv[0]/ExperienceEntries[9]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[2]/Align[0]/LayoutAnchorNode[0]/marker[0]/SectionNode[0]", + "entityName" : null, + "entityKind" : "SectionNode", + "parentPath" : "VioletGridCv[0]/ExperienceEntries[9]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[2]/Align[0]/LayoutAnchorNode[0]/marker[0]", + "childIndex" : 0, + "depth" : 12, + "layer" : 12, + "computedX" : 101.987, + "computedY" : 456.908, + "placementX" : 101.987, + "placementY" : 456.908, + "placementWidth" : 7.617, + "placementHeight" : 9.461, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 7.617, + "contentHeight" : 9.461, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 1.843, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "VioletGridCv[0]/ExperienceEntries[9]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[2]/Align[0]/LayoutAnchorNode[0]/marker[0]/SectionNode[0]/Marker_0[0]", + "entityName" : "Marker_0", + "entityKind" : "ParagraphNode", + "parentPath" : "VioletGridCv[0]/ExperienceEntries[9]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[2]/Align[0]/LayoutAnchorNode[0]/marker[0]/SectionNode[0]", + "childIndex" : 0, + "depth" : 13, + "layer" : 13, + "computedX" : 101.987, + "computedY" : 456.908, + "placementX" : 101.987, + "placementY" : 456.908, + "placementWidth" : 7.617, + "placementHeight" : 7.617, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 7.617, + "contentHeight" : 7.617, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "VioletGridCv[0]/ExperienceEntries[9]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[3]", + "entityName" : null, + "entityKind" : "SectionNode", + "parentPath" : "VioletGridCv[0]/ExperienceEntries[9]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]", + "childIndex" : 3, + "depth" : 8, + "layer" : 8, + "computedX" : 109.604, + "computedY" : 466.369, + "placementX" : 109.604, + "placementY" : 466.369, + "placementWidth" : 0.0, + "placementHeight" : 0.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 0.0, + "contentHeight" : 0.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "VioletGridCv[0]/ExperienceEntries[9]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[4]", + "entityName" : null, + "entityKind" : "SectionNode", + "parentPath" : "VioletGridCv[0]/ExperienceEntries[9]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]", + "childIndex" : 4, + "depth" : 8, + "layer" : 8, + "computedX" : 125.826, + "computedY" : 395.731, + "placementX" : 125.826, + "placementY" : 395.731, + "placementWidth" : 439.535, + "placementHeight" : 70.638, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 439.535, + "contentHeight" : 70.638, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "VioletGridCv[0]/ExperienceEntries[9]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[4]/TitleTable_0[0]", + "entityName" : "TitleTable_0", + "entityKind" : "TableNode", + "parentPath" : "VioletGridCv[0]/ExperienceEntries[9]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[4]", + "childIndex" : 0, + "depth" : 9, + "layer" : 9, + "computedX" : 125.826, + "computedY" : 455.065, + "placementX" : 125.826, + "placementY" : 455.065, + "placementWidth" : 439.535, + "placementHeight" : 11.304, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 439.535, + "contentHeight" : 11.304, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "VioletGridCv[0]/ExperienceEntries[9]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[4]/Highlights_0[1]", + "entityName" : "Highlights_0", + "entityKind" : "TableNode", + "parentPath" : "VioletGridCv[0]/ExperienceEntries[9]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[4]", + "childIndex" : 1, + "depth" : 9, + "layer" : 9, + "computedX" : 125.826, + "computedY" : 395.731, + "placementX" : 125.826, + "placementY" : 395.731, + "placementWidth" : 439.535, + "placementHeight" : 55.892, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 439.535, + "contentHeight" : 55.892, + "margin" : { + "top" : 3.442, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "VioletGridCv[0]/ExperienceEntries[9]/SectionNode[0]/LayoutAnchorNode[1]", + "entityName" : null, + "entityKind" : "LayoutAnchorNode", + "parentPath" : "VioletGridCv[0]/ExperienceEntries[9]/SectionNode[0]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 29.905, + "computedY" : 309.577, + "placementX" : 29.905, + "placementY" : 309.577, + "placementWidth" : 535.466, + "placementHeight" : 72.613, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 535.466, + "contentHeight" : 72.613, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "VioletGridCv[0]/ExperienceEntries[9]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]", + "entityName" : null, + "entityKind" : "SectionNode", + "parentPath" : "VioletGridCv[0]/ExperienceEntries[9]/SectionNode[0]/LayoutAnchorNode[1]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 29.905, + "computedY" : 309.577, + "placementX" : 29.905, + "placementY" : 309.577, + "placementWidth" : 535.466, + "placementHeight" : 72.613, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 535.466, + "contentHeight" : 72.613, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 13.542, + "left" : 0.0 + } + }, { + "path" : "VioletGridCv[0]/ExperienceEntries[9]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]", + "entityName" : null, + "entityKind" : "HorizontalBandsNode", + "parentPath" : "VioletGridCv[0]/ExperienceEntries[9]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 29.905, + "computedY" : 323.119, + "placementX" : 29.905, + "placementY" : 323.119, + "placementWidth" : 535.466, + "placementHeight" : 59.071, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 535.466, + "contentHeight" : 59.071, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "VioletGridCv[0]/ExperienceEntries[9]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]", + "entityName" : null, + "entityKind" : "RowNode", + "parentPath" : "VioletGridCv[0]/ExperienceEntries[9]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 29.905, + "computedY" : 323.119, + "placementX" : 29.905, + "placementY" : 323.119, + "placementWidth" : 535.466, + "placementHeight" : 59.071, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 535.466, + "contentHeight" : 59.071, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "VioletGridCv[0]/ExperienceEntries[9]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]", + "entityName" : null, + "entityKind" : "SectionNode", + "parentPath" : "VioletGridCv[0]/ExperienceEntries[9]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 29.905, + "computedY" : 371.335, + "placementX" : 29.905, + "placementY" : 371.335, + "placementWidth" : 47.893, + "placementHeight" : 10.854, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 47.893, + "contentHeight" : 10.854, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.45, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "VioletGridCv[0]/ExperienceEntries[9]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Period_1[0]", + "entityName" : "Period_1", + "entityKind" : "ParagraphNode", + "parentPath" : "VioletGridCv[0]/ExperienceEntries[9]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]", + "childIndex" : 0, + "depth" : 9, + "layer" : 9, + "computedX" : 29.905, + "computedY" : 371.335, + "placementX" : 29.905, + "placementY" : 371.335, + "placementWidth" : 47.893, + "placementHeight" : 10.404, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 47.893, + "contentHeight" : 10.404, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "VioletGridCv[0]/ExperienceEntries[9]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]", + "entityName" : null, + "entityKind" : "SectionNode", + "parentPath" : "VioletGridCv[0]/ExperienceEntries[9]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]", + "childIndex" : 1, + "depth" : 8, + "layer" : 8, + "computedX" : 91.83, + "computedY" : 382.189, + "placementX" : 91.83, + "placementY" : 382.189, + "placementWidth" : 0.0, + "placementHeight" : 0.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 0.0, + "contentHeight" : 0.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "VioletGridCv[0]/ExperienceEntries[9]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[2]", + "entityName" : null, + "entityKind" : "SectionNode", + "parentPath" : "VioletGridCv[0]/ExperienceEntries[9]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]", + "childIndex" : 2, + "depth" : 8, + "layer" : 8, + "computedX" : 101.987, + "computedY" : 370.885, + "placementX" : 101.987, + "placementY" : 370.885, + "placementWidth" : 7.617, + "placementHeight" : 11.304, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 7.617, + "contentHeight" : 11.304, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "VioletGridCv[0]/ExperienceEntries[9]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[2]/Align[0]", + "entityName" : null, + "entityKind" : "Align", + "parentPath" : "VioletGridCv[0]/ExperienceEntries[9]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[2]", + "childIndex" : 0, + "depth" : 9, + "layer" : 9, + "computedX" : 101.987, + "computedY" : 370.885, + "placementX" : 101.987, + "placementY" : 370.885, + "placementWidth" : 7.617, + "placementHeight" : 11.304, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 7.617, + "contentHeight" : 11.304, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "VioletGridCv[0]/ExperienceEntries[9]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[2]/Align[0]/LayoutAnchorNode[0]", + "entityName" : null, + "entityKind" : "LayoutAnchorNode", + "parentPath" : "VioletGridCv[0]/ExperienceEntries[9]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[2]/Align[0]", + "childIndex" : 0, + "depth" : 10, + "layer" : 10, + "computedX" : 101.987, + "computedY" : 370.885, + "placementX" : 101.987, + "placementY" : 370.885, + "placementWidth" : 7.617, + "placementHeight" : 11.304, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 7.617, + "contentHeight" : 11.304, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "VioletGridCv[0]/ExperienceEntries[9]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[2]/Align[0]/LayoutAnchorNode[0]/marker[0]", + "entityName" : "marker", + "entityKind" : "CanvasLayerNode", + "parentPath" : "VioletGridCv[0]/ExperienceEntries[9]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[2]/Align[0]/LayoutAnchorNode[0]", + "childIndex" : 0, + "depth" : 11, + "layer" : 11, + "computedX" : 101.987, + "computedY" : 370.885, + "placementX" : 101.987, + "placementY" : 370.885, + "placementWidth" : 7.617, + "placementHeight" : 11.304, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 7.617, + "contentHeight" : 11.304, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "VioletGridCv[0]/ExperienceEntries[9]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[2]/Align[0]/LayoutAnchorNode[0]/marker[0]/SectionNode[0]", + "entityName" : null, + "entityKind" : "SectionNode", + "parentPath" : "VioletGridCv[0]/ExperienceEntries[9]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[2]/Align[0]/LayoutAnchorNode[0]/marker[0]", + "childIndex" : 0, + "depth" : 12, + "layer" : 12, + "computedX" : 101.987, + "computedY" : 372.729, + "placementX" : 101.987, + "placementY" : 372.729, + "placementWidth" : 7.617, + "placementHeight" : 9.461, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 7.617, + "contentHeight" : 9.461, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 1.843, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "VioletGridCv[0]/ExperienceEntries[9]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[2]/Align[0]/LayoutAnchorNode[0]/marker[0]/SectionNode[0]/Marker_1[0]", + "entityName" : "Marker_1", + "entityKind" : "ParagraphNode", + "parentPath" : "VioletGridCv[0]/ExperienceEntries[9]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[2]/Align[0]/LayoutAnchorNode[0]/marker[0]/SectionNode[0]", + "childIndex" : 0, + "depth" : 13, + "layer" : 13, + "computedX" : 101.987, + "computedY" : 372.729, + "placementX" : 101.987, + "placementY" : 372.729, + "placementWidth" : 7.617, + "placementHeight" : 7.617, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 7.617, + "contentHeight" : 7.617, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "VioletGridCv[0]/ExperienceEntries[9]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[3]", + "entityName" : null, + "entityKind" : "SectionNode", + "parentPath" : "VioletGridCv[0]/ExperienceEntries[9]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]", + "childIndex" : 3, + "depth" : 8, + "layer" : 8, + "computedX" : 109.604, + "computedY" : 382.189, + "placementX" : 109.604, + "placementY" : 382.189, + "placementWidth" : 0.0, + "placementHeight" : 0.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 0.0, + "contentHeight" : 0.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "VioletGridCv[0]/ExperienceEntries[9]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[4]", + "entityName" : null, + "entityKind" : "SectionNode", + "parentPath" : "VioletGridCv[0]/ExperienceEntries[9]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]", + "childIndex" : 4, + "depth" : 8, + "layer" : 8, + "computedX" : 125.826, + "computedY" : 323.119, + "placementX" : 125.826, + "placementY" : 323.119, + "placementWidth" : 439.535, + "placementHeight" : 59.071, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 439.535, + "contentHeight" : 59.071, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "VioletGridCv[0]/ExperienceEntries[9]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[4]/TitleTable_1[0]", + "entityName" : "TitleTable_1", + "entityKind" : "TableNode", + "parentPath" : "VioletGridCv[0]/ExperienceEntries[9]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[4]", + "childIndex" : 0, + "depth" : 9, + "layer" : 9, + "computedX" : 125.826, + "computedY" : 370.885, + "placementX" : 125.826, + "placementY" : 370.885, + "placementWidth" : 439.535, + "placementHeight" : 11.304, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 439.535, + "contentHeight" : 11.304, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "VioletGridCv[0]/ExperienceEntries[9]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[4]/Highlights_1[1]", + "entityName" : "Highlights_1", + "entityKind" : "TableNode", + "parentPath" : "VioletGridCv[0]/ExperienceEntries[9]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[4]", + "childIndex" : 1, + "depth" : 9, + "layer" : 9, + "computedX" : 125.826, + "computedY" : 323.119, + "placementX" : 125.826, + "placementY" : 323.119, + "placementWidth" : 439.535, + "placementHeight" : 44.325, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 439.535, + "contentHeight" : 44.325, + "margin" : { + "top" : 3.442, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "VioletGridCv[0]/ExperienceEntries[9]/SectionNode[0]/LayoutAnchorNode[2]", + "entityName" : null, + "entityKind" : "LayoutAnchorNode", + "parentPath" : "VioletGridCv[0]/ExperienceEntries[9]/SectionNode[0]", + "childIndex" : 2, + "depth" : 4, + "layer" : 4, + "computedX" : 29.905, + "computedY" : 262.073, + "placementX" : 29.905, + "placementY" : 262.073, + "placementWidth" : 535.466, + "placementHeight" : 47.504, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 535.466, + "contentHeight" : 47.504, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "VioletGridCv[0]/ExperienceEntries[9]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]", + "entityName" : null, + "entityKind" : "SectionNode", + "parentPath" : "VioletGridCv[0]/ExperienceEntries[9]/SectionNode[0]/LayoutAnchorNode[2]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 29.905, + "computedY" : 262.073, + "placementX" : 29.905, + "placementY" : 262.073, + "placementWidth" : 535.466, + "placementHeight" : 47.504, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 535.466, + "contentHeight" : 47.504, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "VioletGridCv[0]/ExperienceEntries[9]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]", + "entityName" : null, + "entityKind" : "HorizontalBandsNode", + "parentPath" : "VioletGridCv[0]/ExperienceEntries[9]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 29.905, + "computedY" : 262.073, + "placementX" : 29.905, + "placementY" : 262.073, + "placementWidth" : 535.466, + "placementHeight" : 47.504, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 535.466, + "contentHeight" : 47.504, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "VioletGridCv[0]/ExperienceEntries[9]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]", + "entityName" : null, + "entityKind" : "RowNode", + "parentPath" : "VioletGridCv[0]/ExperienceEntries[9]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 29.905, + "computedY" : 262.073, + "placementX" : 29.905, + "placementY" : 262.073, + "placementWidth" : 535.466, + "placementHeight" : 47.504, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 535.466, + "contentHeight" : 47.504, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "VioletGridCv[0]/ExperienceEntries[9]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]", + "entityName" : null, + "entityKind" : "SectionNode", + "parentPath" : "VioletGridCv[0]/ExperienceEntries[9]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 29.905, + "computedY" : 298.723, + "placementX" : 29.905, + "placementY" : 298.723, + "placementWidth" : 47.893, + "placementHeight" : 10.854, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 47.893, + "contentHeight" : 10.854, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.45, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "VioletGridCv[0]/ExperienceEntries[9]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]/Period_2[0]", + "entityName" : "Period_2", + "entityKind" : "ParagraphNode", + "parentPath" : "VioletGridCv[0]/ExperienceEntries[9]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[0]", + "childIndex" : 0, + "depth" : 9, + "layer" : 9, + "computedX" : 29.905, + "computedY" : 298.723, + "placementX" : 29.905, + "placementY" : 298.723, + "placementWidth" : 47.893, + "placementHeight" : 10.404, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 47.893, + "contentHeight" : 10.404, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "VioletGridCv[0]/ExperienceEntries[9]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[1]", + "entityName" : null, + "entityKind" : "SectionNode", + "parentPath" : "VioletGridCv[0]/ExperienceEntries[9]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]", + "childIndex" : 1, + "depth" : 8, + "layer" : 8, + "computedX" : 91.83, + "computedY" : 309.577, + "placementX" : 91.83, + "placementY" : 309.577, + "placementWidth" : 0.0, + "placementHeight" : 0.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 0.0, + "contentHeight" : 0.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "VioletGridCv[0]/ExperienceEntries[9]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[2]", + "entityName" : null, + "entityKind" : "SectionNode", + "parentPath" : "VioletGridCv[0]/ExperienceEntries[9]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]", + "childIndex" : 2, + "depth" : 8, + "layer" : 8, + "computedX" : 101.987, + "computedY" : 298.273, + "placementX" : 101.987, + "placementY" : 298.273, + "placementWidth" : 7.617, + "placementHeight" : 11.304, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 7.617, + "contentHeight" : 11.304, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "VioletGridCv[0]/ExperienceEntries[9]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[2]/Align[0]", + "entityName" : null, + "entityKind" : "Align", + "parentPath" : "VioletGridCv[0]/ExperienceEntries[9]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[2]", + "childIndex" : 0, + "depth" : 9, + "layer" : 9, + "computedX" : 101.987, + "computedY" : 298.273, + "placementX" : 101.987, + "placementY" : 298.273, + "placementWidth" : 7.617, + "placementHeight" : 11.304, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 7.617, + "contentHeight" : 11.304, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "VioletGridCv[0]/ExperienceEntries[9]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[2]/Align[0]/LayoutAnchorNode[0]", + "entityName" : null, + "entityKind" : "LayoutAnchorNode", + "parentPath" : "VioletGridCv[0]/ExperienceEntries[9]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[2]/Align[0]", + "childIndex" : 0, + "depth" : 10, + "layer" : 10, + "computedX" : 101.987, + "computedY" : 298.273, + "placementX" : 101.987, + "placementY" : 298.273, + "placementWidth" : 7.617, + "placementHeight" : 11.304, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 7.617, + "contentHeight" : 11.304, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "VioletGridCv[0]/ExperienceEntries[9]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[2]/Align[0]/LayoutAnchorNode[0]/marker[0]", + "entityName" : "marker", + "entityKind" : "CanvasLayerNode", + "parentPath" : "VioletGridCv[0]/ExperienceEntries[9]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[2]/Align[0]/LayoutAnchorNode[0]", + "childIndex" : 0, + "depth" : 11, + "layer" : 11, + "computedX" : 101.987, + "computedY" : 298.273, + "placementX" : 101.987, + "placementY" : 298.273, + "placementWidth" : 7.617, + "placementHeight" : 11.304, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 7.617, + "contentHeight" : 11.304, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "VioletGridCv[0]/ExperienceEntries[9]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[2]/Align[0]/LayoutAnchorNode[0]/marker[0]/SectionNode[0]", + "entityName" : null, + "entityKind" : "SectionNode", + "parentPath" : "VioletGridCv[0]/ExperienceEntries[9]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[2]/Align[0]/LayoutAnchorNode[0]/marker[0]", + "childIndex" : 0, + "depth" : 12, + "layer" : 12, + "computedX" : 101.987, + "computedY" : 300.116, + "placementX" : 101.987, + "placementY" : 300.116, + "placementWidth" : 7.617, + "placementHeight" : 9.461, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 7.617, + "contentHeight" : 9.461, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 1.843, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "VioletGridCv[0]/ExperienceEntries[9]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[2]/Align[0]/LayoutAnchorNode[0]/marker[0]/SectionNode[0]/Marker_2[0]", + "entityName" : "Marker_2", + "entityKind" : "ParagraphNode", + "parentPath" : "VioletGridCv[0]/ExperienceEntries[9]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[2]/Align[0]/LayoutAnchorNode[0]/marker[0]/SectionNode[0]", + "childIndex" : 0, + "depth" : 13, + "layer" : 13, + "computedX" : 101.987, + "computedY" : 300.116, + "placementX" : 101.987, + "placementY" : 300.116, + "placementWidth" : 7.617, + "placementHeight" : 7.617, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 7.617, + "contentHeight" : 7.617, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "VioletGridCv[0]/ExperienceEntries[9]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[3]", + "entityName" : null, + "entityKind" : "SectionNode", + "parentPath" : "VioletGridCv[0]/ExperienceEntries[9]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]", + "childIndex" : 3, + "depth" : 8, + "layer" : 8, + "computedX" : 109.604, + "computedY" : 309.577, + "placementX" : 109.604, + "placementY" : 309.577, + "placementWidth" : 0.0, + "placementHeight" : 0.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 0.0, + "contentHeight" : 0.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "VioletGridCv[0]/ExperienceEntries[9]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[4]", + "entityName" : null, + "entityKind" : "SectionNode", + "parentPath" : "VioletGridCv[0]/ExperienceEntries[9]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]", + "childIndex" : 4, + "depth" : 8, + "layer" : 8, + "computedX" : 125.826, + "computedY" : 262.073, + "placementX" : 125.826, + "placementY" : 262.073, + "placementWidth" : 439.535, + "placementHeight" : 47.504, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 439.535, + "contentHeight" : 47.504, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "VioletGridCv[0]/ExperienceEntries[9]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[4]/TitleTable_2[0]", + "entityName" : "TitleTable_2", + "entityKind" : "TableNode", + "parentPath" : "VioletGridCv[0]/ExperienceEntries[9]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[4]", + "childIndex" : 0, + "depth" : 9, + "layer" : 9, + "computedX" : 125.826, + "computedY" : 298.273, + "placementX" : 125.826, + "placementY" : 298.273, + "placementWidth" : 439.535, + "placementHeight" : 11.304, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 439.535, + "contentHeight" : 11.304, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "VioletGridCv[0]/ExperienceEntries[9]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[4]/Highlights_2[1]", + "entityName" : "Highlights_2", + "entityKind" : "TableNode", + "parentPath" : "VioletGridCv[0]/ExperienceEntries[9]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/HorizontalBandsNode[0]/RowNode[0]/SectionNode[4]", + "childIndex" : 1, + "depth" : 9, + "layer" : 9, + "computedX" : 125.826, + "computedY" : 262.073, + "placementX" : 125.826, + "placementY" : 262.073, + "placementWidth" : 439.535, + "placementHeight" : 32.758, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 439.535, + "contentHeight" : 32.758, + "margin" : { + "top" : 3.442, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "VioletGridCv[0]/ProjectsHeading[10]", + "entityName" : "ProjectsHeading", + "entityKind" : "RowNode", + "parentPath" : "VioletGridCv[0]", + "childIndex" : 10, + "depth" : 2, + "layer" : 2, + "computedX" : 29.905, + "computedY" : 231.717, + "placementX" : 29.905, + "placementY" : 231.717, + "placementWidth" : 535.466, + "placementHeight" : 14.783, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 535.466, + "contentHeight" : 14.783, + "margin" : { + "top" : 15.573, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "VioletGridCv[0]/ProjectsHeading[10]/ProjectsHeadingText[0]", + "entityName" : "ProjectsHeadingText", + "entityKind" : "SectionNode", + "parentPath" : "VioletGridCv[0]/ProjectsHeading[10]", + "childIndex" : 0, + "depth" : 3, + "layer" : 3, + "computedX" : 29.905, + "computedY" : 231.717, + "placementX" : 29.905, + "placementY" : 231.717, + "placementWidth" : 105.864, + "placementHeight" : 14.783, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 105.864, + "contentHeight" : 14.783, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "VioletGridCv[0]/ProjectsHeading[10]/ProjectsHeadingText[0]/ProjectsHeadingLabel[0]", + "entityName" : "ProjectsHeadingLabel", + "entityKind" : "ParagraphNode", + "parentPath" : "VioletGridCv[0]/ProjectsHeading[10]/ProjectsHeadingText[0]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 29.905, + "computedY" : 231.717, + "placementX" : 29.905, + "placementY" : 231.717, + "placementWidth" : 105.864, + "placementHeight" : 14.783, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 105.864, + "contentHeight" : 14.783, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "VioletGridCv[0]/ProjectsHeading[10]/ProjectsHeadingRule[1]", + "entityName" : "ProjectsHeadingRule", + "entityKind" : "SectionNode", + "parentPath" : "VioletGridCv[0]/ProjectsHeading[10]", + "childIndex" : 1, + "depth" : 3, + "layer" : 3, + "computedX" : 135.769, + "computedY" : 237.883, + "placementX" : 135.769, + "placementY" : 237.883, + "placementWidth" : 429.602, + "placementHeight" : 8.617, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 429.602, + "contentHeight" : 8.617, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 7.617, + "right" : 0.0, + "bottom" : 0.0, + "left" : 11.285 + } + }, { + "path" : "VioletGridCv[0]/ProjectsHeading[10]/ProjectsHeadingRule[1]/ProjectsHeadingRuleLine[0]", + "entityName" : "ProjectsHeadingRuleLine", + "entityKind" : "LineNode", + "parentPath" : "VioletGridCv[0]/ProjectsHeading[10]/ProjectsHeadingRule[1]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 147.054, + "computedY" : 237.883, + "placementX" : 147.054, + "placementY" : 237.883, + "placementWidth" : 418.317, + "placementHeight" : 1.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 418.317, + "contentHeight" : 1.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "VioletGridCv[0]/Project_0[11]", + "entityName" : "Project_0", + "entityKind" : "RowNode", + "parentPath" : "VioletGridCv[0]", + "childIndex" : 11, + "depth" : 2, + "layer" : 2, + "computedX" : 29.905, + "computedY" : 177.452, + "placementX" : 29.905, + "placementY" : 177.452, + "placementWidth" : 535.466, + "placementHeight" : 45.745, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 535.466, + "contentHeight" : 45.745, + "margin" : { + "top" : 8.52, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "VioletGridCv[0]/Project_0[11]/ProjectThumb_0[0]", + "entityName" : "ProjectThumb_0", + "entityKind" : "SectionNode", + "parentPath" : "VioletGridCv[0]/Project_0[11]", + "childIndex" : 0, + "depth" : 3, + "layer" : 3, + "computedX" : 29.905, + "computedY" : 180.314, + "placementX" : 29.905, + "placementY" : 180.314, + "placementWidth" : 56.989, + "placementHeight" : 42.882, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 56.989, + "contentHeight" : 42.882, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 11.849 + } + }, { + "path" : "VioletGridCv[0]/Project_0[11]/ProjectThumb_0[0]/ProjectTile_0[0]", + "entityName" : "ProjectTile_0", + "entityKind" : "ShapeContainerNode", + "parentPath" : "VioletGridCv[0]/Project_0[11]/ProjectThumb_0[0]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 41.754, + "computedY" : 180.314, + "placementX" : 41.754, + "placementY" : 180.314, + "placementWidth" : 45.139, + "placementHeight" : 42.882, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 45.139, + "contentHeight" : 42.882, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "VioletGridCv[0]/Project_0[11]/ProjectThumb_0[0]/ProjectTile_0[0]/ProjectGlyph_0[0]", + "entityName" : "ProjectGlyph_0", + "entityKind" : "SectionNode", + "parentPath" : "VioletGridCv[0]/Project_0[11]/ProjectThumb_0[0]/ProjectTile_0[0]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 50.324, + "computedY" : 187.755, + "placementX" : 50.324, + "placementY" : 187.755, + "placementWidth" : 28.0, + "placementHeight" : 28.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 28.0, + "contentHeight" : 28.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "VioletGridCv[0]/Project_0[11]/ProjectThumb_0[0]/ProjectTile_0[0]/ProjectGlyph_0[0]/SvgIcon[0]", + "entityName" : "SvgIcon", + "entityKind" : "LayerStackNode", + "parentPath" : "VioletGridCv[0]/Project_0[11]/ProjectThumb_0[0]/ProjectTile_0[0]/ProjectGlyph_0[0]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 50.324, + "computedY" : 187.755, + "placementX" : 50.324, + "placementY" : 187.755, + "placementWidth" : 28.0, + "placementHeight" : 28.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 28.0, + "contentHeight" : 28.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "VioletGridCv[0]/Project_0[11]/ProjectThumb_0[0]/ProjectTile_0[0]/ProjectGlyph_0[0]/SvgIcon[0]/SvgLayer0[0]", + "entityName" : "SvgLayer0", + "entityKind" : "Path", + "parentPath" : "VioletGridCv[0]/Project_0[11]/ProjectThumb_0[0]/ProjectTile_0[0]/ProjectGlyph_0[0]/SvgIcon[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 50.324, + "computedY" : 187.755, + "placementX" : 50.324, + "placementY" : 187.755, + "placementWidth" : 28.0, + "placementHeight" : 28.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 28.0, + "contentHeight" : 28.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "VioletGridCv[0]/Project_0[11]/ProjectCopy_0[1]", + "entityName" : "ProjectCopy_0", + "entityKind" : "SectionNode", + "parentPath" : "VioletGridCv[0]/Project_0[11]", + "childIndex" : 1, + "depth" : 3, + "layer" : 3, + "computedX" : 108.052, + "computedY" : 177.452, + "placementX" : 108.052, + "placementY" : 177.452, + "placementWidth" : 457.309, + "placementHeight" : 45.745, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 457.309, + "contentHeight" : 45.745, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 19.466 + } + }, { + "path" : "VioletGridCv[0]/Project_0[11]/ProjectCopy_0[1]/ProjectTitle_0[0]", + "entityName" : "ProjectTitle_0", + "entityKind" : "TableNode", + "parentPath" : "VioletGridCv[0]/Project_0[11]/ProjectCopy_0[1]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 127.519, + "computedY" : 211.652, + "placementX" : 127.519, + "placementY" : 211.652, + "placementWidth" : 437.842, + "placementHeight" : 11.544, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 437.842, + "contentHeight" : 11.544, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "VioletGridCv[0]/Project_0[11]/ProjectCopy_0[1]/ProjectBody_0[1]", + "entityName" : "ProjectBody_0", + "entityKind" : "ParagraphNode", + "parentPath" : "VioletGridCv[0]/Project_0[11]/ProjectCopy_0[1]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 127.519, + "computedY" : 177.452, + "placementX" : 127.519, + "placementY" : 177.452, + "placementWidth" : 424.042, + "placementHeight" : 32.169, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 424.042, + "contentHeight" : 32.169, + "margin" : { + "top" : 2.031, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "VioletGridCv[0]/Project_1[12]", + "entityName" : "Project_1", + "entityKind" : "RowNode", + "parentPath" : "VioletGridCv[0]", + "childIndex" : 12, + "depth" : 2, + "layer" : 2, + "computedX" : 29.905, + "computedY" : 124.751, + "placementX" : 29.905, + "placementY" : 124.751, + "placementWidth" : 535.466, + "placementHeight" : 42.882, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 535.466, + "contentHeight" : 42.882, + "margin" : { + "top" : 9.818, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "VioletGridCv[0]/Project_1[12]/ProjectThumb_1[0]", + "entityName" : "ProjectThumb_1", + "entityKind" : "SectionNode", + "parentPath" : "VioletGridCv[0]/Project_1[12]", + "childIndex" : 0, + "depth" : 3, + "layer" : 3, + "computedX" : 29.905, + "computedY" : 124.751, + "placementX" : 29.905, + "placementY" : 124.751, + "placementWidth" : 56.989, + "placementHeight" : 42.882, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 56.989, + "contentHeight" : 42.882, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 11.849 + } + }, { + "path" : "VioletGridCv[0]/Project_1[12]/ProjectThumb_1[0]/ProjectTile_1[0]", + "entityName" : "ProjectTile_1", + "entityKind" : "ShapeContainerNode", + "parentPath" : "VioletGridCv[0]/Project_1[12]/ProjectThumb_1[0]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 41.754, + "computedY" : 124.751, + "placementX" : 41.754, + "placementY" : 124.751, + "placementWidth" : 45.139, + "placementHeight" : 42.882, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 45.139, + "contentHeight" : 42.882, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "VioletGridCv[0]/Project_1[12]/ProjectThumb_1[0]/ProjectTile_1[0]/ProjectGlyph_1[0]", + "entityName" : "ProjectGlyph_1", + "entityKind" : "SectionNode", + "parentPath" : "VioletGridCv[0]/Project_1[12]/ProjectThumb_1[0]/ProjectTile_1[0]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 50.324, + "computedY" : 132.193, + "placementX" : 50.324, + "placementY" : 132.193, + "placementWidth" : 28.0, + "placementHeight" : 28.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 28.0, + "contentHeight" : 28.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "VioletGridCv[0]/Project_1[12]/ProjectThumb_1[0]/ProjectTile_1[0]/ProjectGlyph_1[0]/SvgIcon[0]", + "entityName" : "SvgIcon", + "entityKind" : "LayerStackNode", + "parentPath" : "VioletGridCv[0]/Project_1[12]/ProjectThumb_1[0]/ProjectTile_1[0]/ProjectGlyph_1[0]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 50.324, + "computedY" : 132.193, + "placementX" : 50.324, + "placementY" : 132.193, + "placementWidth" : 28.0, + "placementHeight" : 28.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 28.0, + "contentHeight" : 28.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "VioletGridCv[0]/Project_1[12]/ProjectThumb_1[0]/ProjectTile_1[0]/ProjectGlyph_1[0]/SvgIcon[0]/SvgLayer0[0]", + "entityName" : "SvgLayer0", + "entityKind" : "Path", + "parentPath" : "VioletGridCv[0]/Project_1[12]/ProjectThumb_1[0]/ProjectTile_1[0]/ProjectGlyph_1[0]/SvgIcon[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 50.324, + "computedY" : 132.193, + "placementX" : 50.324, + "placementY" : 132.193, + "placementWidth" : 28.0, + "placementHeight" : 28.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 28.0, + "contentHeight" : 28.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "VioletGridCv[0]/Project_1[12]/ProjectCopy_1[1]", + "entityName" : "ProjectCopy_1", + "entityKind" : "SectionNode", + "parentPath" : "VioletGridCv[0]/Project_1[12]", + "childIndex" : 1, + "depth" : 3, + "layer" : 3, + "computedX" : 108.052, + "computedY" : 132.892, + "placementX" : 108.052, + "placementY" : 132.892, + "placementWidth" : 457.309, + "placementHeight" : 34.742, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 457.309, + "contentHeight" : 34.742, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 19.466 + } + }, { + "path" : "VioletGridCv[0]/Project_1[12]/ProjectCopy_1[1]/ProjectTitle_1[0]", + "entityName" : "ProjectTitle_1", + "entityKind" : "TableNode", + "parentPath" : "VioletGridCv[0]/Project_1[12]/ProjectCopy_1[1]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 127.519, + "computedY" : 156.09, + "placementX" : 127.519, + "placementY" : 156.09, + "placementWidth" : 437.842, + "placementHeight" : 11.544, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 437.842, + "contentHeight" : 11.544, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "VioletGridCv[0]/Project_1[12]/ProjectCopy_1[1]/ProjectBody_1[1]", + "entityName" : "ProjectBody_1", + "entityKind" : "ParagraphNode", + "parentPath" : "VioletGridCv[0]/Project_1[12]/ProjectCopy_1[1]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 127.519, + "computedY" : 132.892, + "placementX" : 127.519, + "placementY" : 132.892, + "placementWidth" : 424.178, + "placementHeight" : 21.167, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 424.178, + "contentHeight" : 21.167, + "margin" : { + "top" : 2.031, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "VioletGridCv[0]/CredentialHeadings[13]", + "entityName" : "CredentialHeadings", + "entityKind" : "RowNode", + "parentPath" : "VioletGridCv[0]", + "childIndex" : 13, + "depth" : 2, + "layer" : 2, + "computedX" : 29.905, + "computedY" : 101.109, + "placementX" : 29.905, + "placementY" : 101.109, + "placementWidth" : 535.466, + "placementHeight" : 14.783, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 535.466, + "contentHeight" : 14.783, + "margin" : { + "top" : 8.859, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "VioletGridCv[0]/CredentialHeadings[13]/EducationHeadingLabel[0]", + "entityName" : "EducationHeadingLabel", + "entityKind" : "ParagraphNode", + "parentPath" : "VioletGridCv[0]/CredentialHeadings[13]", + "childIndex" : 0, + "depth" : 3, + "layer" : 3, + "computedX" : 29.905, + "computedY" : 101.109, + "placementX" : 29.905, + "placementY" : 101.109, + "placementWidth" : 62.401, + "placementHeight" : 14.783, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 62.401, + "contentHeight" : 14.783, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "VioletGridCv[0]/CredentialHeadings[13]/EducationHeadingRule[1]", + "entityName" : "EducationHeadingRule", + "entityKind" : "LineNode", + "parentPath" : "VioletGridCv[0]/CredentialHeadings[13]", + "childIndex" : 1, + "depth" : 3, + "layer" : 3, + "computedX" : 103.591, + "computedY" : 107.276, + "placementX" : 103.591, + "placementY" : 107.276, + "placementWidth" : 171.131, + "placementHeight" : 1.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 171.131, + "contentHeight" : 1.0, + "margin" : { + "top" : 7.617, + "right" : 0.0, + "bottom" : 0.0, + "left" : 11.285 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "VioletGridCv[0]/CredentialHeadings[13]/SpacerNode[2]", + "entityName" : null, + "entityKind" : "SpacerNode", + "parentPath" : "VioletGridCv[0]/CredentialHeadings[13]", + "childIndex" : 2, + "depth" : 3, + "layer" : 3, + "computedX" : 274.722, + "computedY" : 115.893, + "placementX" : 274.722, + "placementY" : 115.893, + "placementWidth" : 39.497, + "placementHeight" : 0.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 39.497, + "contentHeight" : 0.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "VioletGridCv[0]/CredentialHeadings[13]/LanguagesHeadingLabel[3]", + "entityName" : "LanguagesHeadingLabel", + "entityKind" : "ParagraphNode", + "parentPath" : "VioletGridCv[0]/CredentialHeadings[13]", + "childIndex" : 3, + "depth" : 3, + "layer" : 3, + "computedX" : 314.219, + "computedY" : 101.109, + "placementX" : 314.219, + "placementY" : 101.109, + "placementWidth" : 63.291, + "placementHeight" : 14.783, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 63.291, + "contentHeight" : 14.783, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "VioletGridCv[0]/CredentialHeadings[13]/LanguagesHeadingRule[4]", + "entityName" : "LanguagesHeadingRule", + "entityKind" : "LineNode", + "parentPath" : "VioletGridCv[0]/CredentialHeadings[13]", + "childIndex" : 4, + "depth" : 3, + "layer" : 3, + "computedX" : 388.794, + "computedY" : 107.276, + "placementX" : 388.794, + "placementY" : 107.276, + "placementWidth" : 176.576, + "placementHeight" : 1.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 176.576, + "contentHeight" : 1.0, + "margin" : { + "top" : 7.617, + "right" : 0.0, + "bottom" : 0.0, + "left" : 11.285 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "VioletGridCv[0]/CredentialBodies[14]", + "entityName" : "CredentialBodies", + "entityKind" : "RowNode", + "parentPath" : "VioletGridCv[0]", + "childIndex" : 14, + "depth" : 2, + "layer" : 2, + "computedX" : 29.905, + "computedY" : 50.973, + "placementX" : 29.905, + "placementY" : 50.973, + "placementWidth" : 535.466, + "placementHeight" : 42.519, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 535.466, + "contentHeight" : 42.519, + "margin" : { + "top" : 7.617, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "VioletGridCv[0]/CredentialBodies[14]/Education[0]", + "entityName" : "Education", + "entityKind" : "SectionNode", + "parentPath" : "VioletGridCv[0]/CredentialBodies[14]", + "childIndex" : 0, + "depth" : 3, + "layer" : 3, + "computedX" : 29.905, + "computedY" : 52.849, + "placementX" : 29.905, + "placementY" : 52.849, + "placementWidth" : 242.614, + "placementHeight" : 40.643, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 242.614, + "contentHeight" : 40.643, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "VioletGridCv[0]/CredentialBodies[14]/Education[0]/EducationBody[0]", + "entityName" : "EducationBody", + "entityKind" : "LayerStackNode", + "parentPath" : "VioletGridCv[0]/CredentialBodies[14]/Education[0]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 29.905, + "computedY" : 52.849, + "placementX" : 29.905, + "placementY" : 52.849, + "placementWidth" : 242.614, + "placementHeight" : 40.643, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 242.614, + "contentHeight" : 40.643, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "VioletGridCv[0]/CredentialBodies[14]/Education[0]/EducationBody[0]/EducationLines[0]", + "entityName" : "EducationLines", + "entityKind" : "TableNode", + "parentPath" : "VioletGridCv[0]/CredentialBodies[14]/Education[0]/EducationBody[0]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 81.251, + "computedY" : 52.849, + "placementX" : 81.251, + "placementY" : 52.849, + "placementWidth" : 191.268, + "placementHeight" : 38.33, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 191.268, + "contentHeight" : 38.33, + "margin" : { + "top" : 2.313, + "right" : 0.0, + "bottom" : 0.0, + "left" : 51.346 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "VioletGridCv[0]/CredentialBodies[14]/Education[0]/EducationBody[0]/EducationBadge[1]", + "entityName" : "EducationBadge", + "entityKind" : "ShapeContainerNode", + "parentPath" : "VioletGridCv[0]/CredentialBodies[14]/Education[0]/EducationBody[0]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 29.905, + "computedY" : 52.867, + "placementX" : 29.905, + "placementY" : 52.867, + "placementWidth" : 40.625, + "placementHeight" : 40.625, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 40.625, + "contentHeight" : 40.625, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "VioletGridCv[0]/CredentialBodies[14]/Education[0]/EducationBody[0]/EducationBadge[1]/EducationGlyph[0]", + "entityName" : "EducationGlyph", + "entityKind" : "SectionNode", + "parentPath" : "VioletGridCv[0]/CredentialBodies[14]/Education[0]/EducationBody[0]/EducationBadge[1]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 37.218, + "computedY" : 60.179, + "placementX" : 37.218, + "placementY" : 60.179, + "placementWidth" : 26.0, + "placementHeight" : 26.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 26.0, + "contentHeight" : 26.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "VioletGridCv[0]/CredentialBodies[14]/Education[0]/EducationBody[0]/EducationBadge[1]/EducationGlyph[0]/SvgIcon[0]", + "entityName" : "SvgIcon", + "entityKind" : "LayerStackNode", + "parentPath" : "VioletGridCv[0]/CredentialBodies[14]/Education[0]/EducationBody[0]/EducationBadge[1]/EducationGlyph[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 37.218, + "computedY" : 60.179, + "placementX" : 37.218, + "placementY" : 60.179, + "placementWidth" : 26.0, + "placementHeight" : 26.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 26.0, + "contentHeight" : 26.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "VioletGridCv[0]/CredentialBodies[14]/Education[0]/EducationBody[0]/EducationBadge[1]/EducationGlyph[0]/SvgIcon[0]/SvgLayer0[0]", + "entityName" : "SvgLayer0", + "entityKind" : "Path", + "parentPath" : "VioletGridCv[0]/CredentialBodies[14]/Education[0]/EducationBody[0]/EducationBadge[1]/EducationGlyph[0]/SvgIcon[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 37.218, + "computedY" : 60.179, + "placementX" : 37.218, + "placementY" : 60.179, + "placementWidth" : 26.0, + "placementHeight" : 26.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 26.0, + "contentHeight" : 26.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "VioletGridCv[0]/CredentialBodies[14]/CredentialGutter[1]", + "entityName" : "CredentialGutter", + "entityKind" : "SectionNode", + "parentPath" : "VioletGridCv[0]/CredentialBodies[14]", + "childIndex" : 1, + "depth" : 3, + "layer" : 3, + "computedX" : 272.529, + "computedY" : 92.492, + "placementX" : 272.529, + "placementY" : 92.492, + "placementWidth" : 39.497, + "placementHeight" : 1.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 39.497, + "contentHeight" : 1.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "VioletGridCv[0]/CredentialBodies[14]/CredentialGutter[1]/SpacerNode[0]", + "entityName" : null, + "entityKind" : "SpacerNode", + "parentPath" : "VioletGridCv[0]/CredentialBodies[14]/CredentialGutter[1]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 272.529, + "computedY" : 92.492, + "placementX" : 272.529, + "placementY" : 92.492, + "placementWidth" : 39.497, + "placementHeight" : 1.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 39.497, + "contentHeight" : 1.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "VioletGridCv[0]/CredentialBodies[14]/Languages[2]", + "entityName" : "Languages", + "entityKind" : "SectionNode", + "parentPath" : "VioletGridCv[0]/CredentialBodies[14]", + "childIndex" : 2, + "depth" : 3, + "layer" : 3, + "computedX" : 312.026, + "computedY" : 50.973, + "placementX" : 312.026, + "placementY" : 50.973, + "placementWidth" : 253.325, + "placementHeight" : 42.519, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 253.325, + "contentHeight" : 42.519, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "VioletGridCv[0]/CredentialBodies[14]/Languages[2]/LanguageTable[0]", + "entityName" : "LanguageTable", + "entityKind" : "TableNode", + "parentPath" : "VioletGridCv[0]/CredentialBodies[14]/Languages[2]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 312.026, + "computedY" : 50.973, + "placementX" : 312.026, + "placementY" : 50.973, + "placementWidth" : 253.325, + "placementHeight" : 41.221, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 253.325, + "contentHeight" : 41.221, + "margin" : { + "top" : 1.298, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "VioletGridCv[0]/QuoteBand[15]", + "entityName" : "QuoteBand", + "entityKind" : "SectionNode", + "parentPath" : "VioletGridCv[0]", + "childIndex" : 15, + "depth" : 2, + "layer" : 2, + "computedX" : 29.905, + "computedY" : 10.348, + "placementX" : 29.905, + "placementY" : 10.348, + "placementWidth" : 414.689, + "placementHeight" : 27.084, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 414.689, + "contentHeight" : 27.084, + "margin" : { + "top" : 13.542, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 6.492, + "right" : 22.57, + "bottom" : 6.492, + "left" : 22.57 + } + }, { + "path" : "VioletGridCv[0]/QuoteBand[15]/QuoteLine[0]", + "entityName" : "QuoteLine", + "entityKind" : "ParagraphNode", + "parentPath" : "VioletGridCv[0]/QuoteBand[15]", + "childIndex" : 0, + "depth" : 3, + "layer" : 3, + "computedX" : 52.475, + "computedY" : 16.839, + "placementX" : 52.475, + "placementY" : 16.839, + "placementWidth" : 369.55, + "placementHeight" : 14.1, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 369.55, + "contentHeight" : 14.1, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + } ] +} diff --git a/qa/src/test/resources/layout-snapshots/canonical-templates/invoice/classic_invoice_layout.json b/qa/src/test/resources/layout-snapshots/canonical-templates/invoice/classic_invoice_layout.json new file mode 100644 index 000000000..2bfc96b9b --- /dev/null +++ b/qa/src/test/resources/layout-snapshots/canonical-templates/invoice/classic_invoice_layout.json @@ -0,0 +1,1007 @@ +{ + "formatVersion" : "2.0", + "canvas" : { + "pageWidth" : 595.276, + "pageHeight" : 841.89, + "innerWidth" : 595.276, + "innerHeight" : 841.89, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, + "totalPages" : 1, + "nodes" : [ { + "path" : "Invoice[0]", + "entityName" : "Invoice", + "entityKind" : "ContainerNode", + "parentPath" : null, + "childIndex" : 0, + "depth" : 1, + "layer" : 1, + "computedX" : 0.0, + "computedY" : 191.765, + "placementX" : 0.0, + "placementY" : 191.765, + "placementWidth" : 595.276, + "placementHeight" : 650.125, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 595.276, + "contentHeight" : 650.125, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 24.0, + "right" : 24.0, + "bottom" : 24.0, + "left" : 24.0 + } + }, { + "path" : "Invoice[0]/Header[0]", + "entityName" : "Header", + "entityKind" : "RowNode", + "parentPath" : "Invoice[0]", + "childIndex" : 0, + "depth" : 2, + "layer" : 2, + "computedX" : 24.0, + "computedY" : 769.49, + "placementX" : 24.0, + "placementY" : 769.49, + "placementWidth" : 547.276, + "placementHeight" : 48.4, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 547.276, + "contentHeight" : 48.4, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Invoice[0]/Header[0]/HeaderLeft[0]", + "entityName" : "HeaderLeft", + "entityKind" : "SectionNode", + "parentPath" : "Invoice[0]/Header[0]", + "childIndex" : 0, + "depth" : 3, + "layer" : 3, + "computedX" : 24.0, + "computedY" : 790.915, + "placementX" : 24.0, + "placementY" : 790.915, + "placementWidth" : 185.13, + "placementHeight" : 26.975, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 185.13, + "contentHeight" : 26.975, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Invoice[0]/Header[0]/HeaderLeft[0]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "Invoice[0]/Header[0]/HeaderLeft[0]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 24.0, + "computedY" : 802.165, + "placementX" : 24.0, + "placementY" : 802.165, + "placementWidth" : 185.13, + "placementHeight" : 15.725, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 185.13, + "contentHeight" : 15.725, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Invoice[0]/Header[0]/HeaderLeft[0]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "Invoice[0]/Header[0]/HeaderLeft[0]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 24.0, + "computedY" : 790.915, + "placementX" : 24.0, + "placementY" : 790.915, + "placementWidth" : 73.38, + "placementHeight" : 9.25, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 73.38, + "contentHeight" : 9.25, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Invoice[0]/Header[0]/HeaderRight[1]", + "entityName" : "HeaderRight", + "entityKind" : "SectionNode", + "parentPath" : "Invoice[0]/Header[0]", + "childIndex" : 1, + "depth" : 3, + "layer" : 3, + "computedX" : 306.638, + "computedY" : 769.49, + "placementX" : 306.638, + "placementY" : 769.49, + "placementWidth" : 115.136, + "placementHeight" : 48.4, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 115.136, + "contentHeight" : 48.4, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Invoice[0]/Header[0]/HeaderRight[1]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "Invoice[0]/Header[0]/HeaderRight[1]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 306.638, + "computedY" : 791.99, + "placementX" : 306.638, + "placementY" : 791.99, + "placementWidth" : 115.136, + "placementHeight" : 25.9, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 115.136, + "contentHeight" : 25.9, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Invoice[0]/Header[0]/HeaderRight[1]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "Invoice[0]/Header[0]/HeaderRight[1]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 306.638, + "computedY" : 780.74, + "placementX" : 306.638, + "placementY" : 780.74, + "placementWidth" : 60.58, + "placementHeight" : 9.25, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 60.58, + "contentHeight" : 9.25, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Invoice[0]/Header[0]/HeaderRight[1]/ParagraphNode[2]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "Invoice[0]/Header[0]/HeaderRight[1]", + "childIndex" : 2, + "depth" : 4, + "layer" : 4, + "computedX" : 306.638, + "computedY" : 769.49, + "placementX" : 306.638, + "placementY" : 769.49, + "placementWidth" : 86.72, + "placementHeight" : 9.25, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 86.72, + "contentHeight" : 9.25, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Invoice[0]/Hero[1]", + "entityName" : "Hero", + "entityKind" : "SectionNode", + "parentPath" : "Invoice[0]", + "childIndex" : 1, + "depth" : 2, + "layer" : 2, + "computedX" : 24.0, + "computedY" : 696.365, + "placementX" : 24.0, + "placementY" : 696.365, + "placementWidth" : 538.468, + "placementHeight" : 57.125, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 538.468, + "contentHeight" : 57.125, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 14.0, + "right" : 14.0, + "bottom" : 14.0, + "left" : 14.0 + } + }, { + "path" : "Invoice[0]/Hero[1]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "Invoice[0]/Hero[1]", + "childIndex" : 0, + "depth" : 3, + "layer" : 3, + "computedX" : 38.0, + "computedY" : 729.315, + "placementX" : 38.0, + "placementY" : 729.315, + "placementWidth" : 62.942, + "placementHeight" : 10.175, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 62.942, + "contentHeight" : 10.175, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Invoice[0]/Hero[1]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "Invoice[0]/Hero[1]", + "childIndex" : 1, + "depth" : 3, + "layer" : 3, + "computedX" : 38.0, + "computedY" : 710.365, + "placementX" : 38.0, + "placementY" : 710.365, + "placementWidth" : 510.468, + "placementHeight" : 12.95, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 510.468, + "contentHeight" : 12.95, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Invoice[0]/Parties[2]", + "entityName" : "Parties", + "entityKind" : "RowNode", + "parentPath" : "Invoice[0]", + "childIndex" : 2, + "depth" : 2, + "layer" : 2, + "computedX" : 24.0, + "computedY" : 599.94, + "placementX" : 24.0, + "placementY" : 599.94, + "placementWidth" : 547.276, + "placementHeight" : 80.425, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 547.276, + "contentHeight" : 80.425, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Invoice[0]/Parties[2]/BillTo[0]", + "entityName" : "BillTo", + "entityKind" : "SectionNode", + "parentPath" : "Invoice[0]/Parties[2]", + "childIndex" : 0, + "depth" : 3, + "layer" : 3, + "computedX" : 24.0, + "computedY" : 599.94, + "placementX" : 24.0, + "placementY" : 599.94, + "placementWidth" : 115.709, + "placementHeight" : 80.425, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 115.709, + "contentHeight" : 80.425, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Invoice[0]/Parties[2]/BillTo[0]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "Invoice[0]/Parties[2]/BillTo[0]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 24.0, + "computedY" : 670.19, + "placementX" : 24.0, + "placementY" : 670.19, + "placementWidth" : 42.779, + "placementHeight" : 10.175, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 42.779, + "contentHeight" : 10.175, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Invoice[0]/Parties[2]/BillTo[0]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "Invoice[0]/Parties[2]/BillTo[0]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 24.0, + "computedY" : 658.015, + "placementX" : 24.0, + "placementY" : 658.015, + "placementWidth" : 102.685, + "placementHeight" : 10.175, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 102.685, + "contentHeight" : 10.175, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Invoice[0]/Parties[2]/BillTo[0]/ParagraphNode[2]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "Invoice[0]/Parties[2]/BillTo[0]", + "childIndex" : 2, + "depth" : 4, + "layer" : 4, + "computedX" : 24.0, + "computedY" : 599.94, + "placementX" : 24.0, + "placementY" : 599.94, + "placementWidth" : 115.709, + "placementHeight" : 56.075, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 115.709, + "contentHeight" : 56.075, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Invoice[0]/Parties[2]/From[1]", + "entityName" : "From", + "entityKind" : "SectionNode", + "parentPath" : "Invoice[0]/Parties[2]", + "childIndex" : 1, + "depth" : 3, + "layer" : 3, + "computedX" : 306.638, + "computedY" : 599.94, + "placementX" : 306.638, + "placementY" : 599.94, + "placementWidth" : 132.825, + "placementHeight" : 80.425, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 132.825, + "contentHeight" : 80.425, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Invoice[0]/Parties[2]/From[1]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "Invoice[0]/Parties[2]/From[1]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 306.638, + "computedY" : 670.19, + "placementX" : 306.638, + "placementY" : 670.19, + "placementWidth" : 32.384, + "placementHeight" : 10.175, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 32.384, + "contentHeight" : 10.175, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Invoice[0]/Parties[2]/From[1]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "Invoice[0]/Parties[2]/From[1]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 306.638, + "computedY" : 658.015, + "placementX" : 306.638, + "placementY" : 658.015, + "placementWidth" : 119.79, + "placementHeight" : 10.175, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 119.79, + "contentHeight" : 10.175, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Invoice[0]/Parties[2]/From[1]/ParagraphNode[2]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "Invoice[0]/Parties[2]/From[1]", + "childIndex" : 2, + "depth" : 4, + "layer" : 4, + "computedX" : 306.638, + "computedY" : 599.94, + "placementX" : 306.638, + "placementY" : 599.94, + "placementWidth" : 132.825, + "placementHeight" : 56.075, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 132.825, + "contentHeight" : 56.075, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Invoice[0]/LineItems[3]", + "entityName" : "LineItems", + "entityKind" : "TableNode", + "parentPath" : "Invoice[0]", + "childIndex" : 3, + "depth" : 2, + "layer" : 2, + "computedX" : 24.0, + "computedY" : 449.965, + "placementX" : 24.0, + "placementY" : 449.965, + "placementWidth" : 400.84, + "placementHeight" : 133.975, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 400.84, + "contentHeight" : 133.975, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Invoice[0]/Summary[4]", + "entityName" : "Summary", + "entityKind" : "SectionNode", + "parentPath" : "Invoice[0]", + "childIndex" : 4, + "depth" : 2, + "layer" : 2, + "computedX" : 24.0, + "computedY" : 353.89, + "placementX" : 24.0, + "placementY" : 353.89, + "placementWidth" : 262.0, + "placementHeight" : 80.075, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 262.0, + "contentHeight" : 80.075, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Invoice[0]/Summary[4]/SummaryTable[0]", + "entityName" : "SummaryTable", + "entityKind" : "TableNode", + "parentPath" : "Invoice[0]/Summary[4]", + "childIndex" : 0, + "depth" : 3, + "layer" : 3, + "computedX" : 24.0, + "computedY" : 353.89, + "placementX" : 24.0, + "placementY" : 353.89, + "placementWidth" : 262.0, + "placementHeight" : 80.075, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 262.0, + "contentHeight" : 80.075, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Invoice[0]/Footer[5]", + "entityName" : "Footer", + "entityKind" : "SectionNode", + "parentPath" : "Invoice[0]", + "childIndex" : 5, + "depth" : 2, + "layer" : 2, + "computedX" : 24.0, + "computedY" : 215.765, + "placementX" : 24.0, + "placementY" : 215.765, + "placementWidth" : 547.276, + "placementHeight" : 122.125, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 547.276, + "contentHeight" : 122.125, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Invoice[0]/Footer[5]/FooterRow[0]", + "entityName" : "FooterRow", + "entityKind" : "RowNode", + "parentPath" : "Invoice[0]/Footer[5]", + "childIndex" : 0, + "depth" : 3, + "layer" : 3, + "computedX" : 24.0, + "computedY" : 247.015, + "placementX" : 24.0, + "placementY" : 247.015, + "placementWidth" : 547.276, + "placementHeight" : 90.875, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 547.276, + "contentHeight" : 90.875, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Invoice[0]/Footer[5]/FooterRow[0]/InvoiceNotes[0]", + "entityName" : "InvoiceNotes", + "entityKind" : "SectionNode", + "parentPath" : "Invoice[0]/Footer[5]/FooterRow[0]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 24.0, + "computedY" : 272.915, + "placementX" : 24.0, + "placementY" : 272.915, + "placementWidth" : 264.214, + "placementHeight" : 64.975, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 264.214, + "contentHeight" : 64.975, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 8.0 + } + }, { + "path" : "Invoice[0]/Footer[5]/FooterRow[0]/InvoiceNotes[0]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "Invoice[0]/Footer[5]/FooterRow[0]/InvoiceNotes[0]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 32.0, + "computedY" : 327.715, + "placementX" : 32.0, + "placementY" : 327.715, + "placementWidth" : 30.558, + "placementHeight" : 10.175, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 30.558, + "contentHeight" : 10.175, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Invoice[0]/Footer[5]/FooterRow[0]/InvoiceNotes[0]/ListNode[1]", + "entityName" : null, + "entityKind" : "ListNode", + "parentPath" : "Invoice[0]/Footer[5]/FooterRow[0]/InvoiceNotes[0]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 32.0, + "computedY" : 272.915, + "placementX" : 32.0, + "placementY" : 272.915, + "placementWidth" : 256.214, + "placementHeight" : 51.8, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 256.214, + "contentHeight" : 51.8, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Invoice[0]/Footer[5]/FooterRow[0]/InvoicePaymentTerms[1]", + "entityName" : "InvoicePaymentTerms", + "entityKind" : "SectionNode", + "parentPath" : "Invoice[0]/Footer[5]/FooterRow[0]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 306.638, + "computedY" : 247.015, + "placementX" : 306.638, + "placementY" : 247.015, + "placementWidth" : 253.35, + "placementHeight" : 90.875, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 253.35, + "contentHeight" : 90.875, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 8.0 + } + }, { + "path" : "Invoice[0]/Footer[5]/FooterRow[0]/InvoicePaymentTerms[1]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "Invoice[0]/Footer[5]/FooterRow[0]/InvoicePaymentTerms[1]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 314.638, + "computedY" : 327.715, + "placementX" : 314.638, + "placementY" : 327.715, + "placementWidth" : 78.859, + "placementHeight" : 10.175, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 78.859, + "contentHeight" : 10.175, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Invoice[0]/Footer[5]/FooterRow[0]/InvoicePaymentTerms[1]/ListNode[1]", + "entityName" : null, + "entityKind" : "ListNode", + "parentPath" : "Invoice[0]/Footer[5]/FooterRow[0]/InvoicePaymentTerms[1]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 314.638, + "computedY" : 247.015, + "placementX" : 314.638, + "placementY" : 247.015, + "placementWidth" : 245.35, + "placementHeight" : 77.7, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 245.35, + "contentHeight" : 77.7, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Invoice[0]/Footer[5]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "Invoice[0]/Footer[5]", + "childIndex" : 1, + "depth" : 3, + "layer" : 3, + "computedX" : 24.0, + "computedY" : 215.765, + "placementX" : 24.0, + "placementY" : 215.765, + "placementWidth" : 335.71, + "placementHeight" : 9.25, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 335.71, + "contentHeight" : 9.25, + "margin" : { + "top" : 14.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + } ] +} diff --git a/qa/src/test/resources/layout-snapshots/canonical-templates/invoice/classic_invoice_stress_layout.json b/qa/src/test/resources/layout-snapshots/canonical-templates/invoice/classic_invoice_stress_layout.json new file mode 100644 index 000000000..e76739bd6 --- /dev/null +++ b/qa/src/test/resources/layout-snapshots/canonical-templates/invoice/classic_invoice_stress_layout.json @@ -0,0 +1,1007 @@ +{ + "formatVersion" : "2.0", + "canvas" : { + "pageWidth" : 595.276, + "pageHeight" : 841.89, + "innerWidth" : 595.276, + "innerHeight" : 841.89, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, + "totalPages" : 2, + "nodes" : [ { + "path" : "Invoice[0]", + "entityName" : "Invoice", + "entityKind" : "ContainerNode", + "parentPath" : null, + "childIndex" : 0, + "depth" : 1, + "layer" : 1, + "computedX" : 0.0, + "computedY" : -752.535, + "placementX" : 0.0, + "placementY" : -752.535, + "placementWidth" : 595.276, + "placementHeight" : 1594.425, + "startPage" : 0, + "endPage" : 1, + "contentWidth" : 595.276, + "contentHeight" : 1594.425, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 24.0, + "right" : 24.0, + "bottom" : 24.0, + "left" : 24.0 + } + }, { + "path" : "Invoice[0]/Header[0]", + "entityName" : "Header", + "entityKind" : "RowNode", + "parentPath" : "Invoice[0]", + "childIndex" : 0, + "depth" : 2, + "layer" : 2, + "computedX" : 24.0, + "computedY" : 769.49, + "placementX" : 24.0, + "placementY" : 769.49, + "placementWidth" : 547.276, + "placementHeight" : 48.4, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 547.276, + "contentHeight" : 48.4, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Invoice[0]/Header[0]/HeaderLeft[0]", + "entityName" : "HeaderLeft", + "entityKind" : "SectionNode", + "parentPath" : "Invoice[0]/Header[0]", + "childIndex" : 0, + "depth" : 3, + "layer" : 3, + "computedX" : 24.0, + "computedY" : 790.915, + "placementX" : 24.0, + "placementY" : 790.915, + "placementWidth" : 185.13, + "placementHeight" : 26.975, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 185.13, + "contentHeight" : 26.975, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Invoice[0]/Header[0]/HeaderLeft[0]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "Invoice[0]/Header[0]/HeaderLeft[0]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 24.0, + "computedY" : 802.165, + "placementX" : 24.0, + "placementY" : 802.165, + "placementWidth" : 185.13, + "placementHeight" : 15.725, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 185.13, + "contentHeight" : 15.725, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Invoice[0]/Header[0]/HeaderLeft[0]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "Invoice[0]/Header[0]/HeaderLeft[0]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 24.0, + "computedY" : 790.915, + "placementX" : 24.0, + "placementY" : 790.915, + "placementWidth" : 73.38, + "placementHeight" : 9.25, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 73.38, + "contentHeight" : 9.25, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Invoice[0]/Header[0]/HeaderRight[1]", + "entityName" : "HeaderRight", + "entityKind" : "SectionNode", + "parentPath" : "Invoice[0]/Header[0]", + "childIndex" : 1, + "depth" : 3, + "layer" : 3, + "computedX" : 306.638, + "computedY" : 769.49, + "placementX" : 306.638, + "placementY" : 769.49, + "placementWidth" : 115.136, + "placementHeight" : 48.4, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 115.136, + "contentHeight" : 48.4, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Invoice[0]/Header[0]/HeaderRight[1]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "Invoice[0]/Header[0]/HeaderRight[1]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 306.638, + "computedY" : 791.99, + "placementX" : 306.638, + "placementY" : 791.99, + "placementWidth" : 115.136, + "placementHeight" : 25.9, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 115.136, + "contentHeight" : 25.9, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Invoice[0]/Header[0]/HeaderRight[1]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "Invoice[0]/Header[0]/HeaderRight[1]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 306.638, + "computedY" : 780.74, + "placementX" : 306.638, + "placementY" : 780.74, + "placementWidth" : 60.58, + "placementHeight" : 9.25, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 60.58, + "contentHeight" : 9.25, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Invoice[0]/Header[0]/HeaderRight[1]/ParagraphNode[2]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "Invoice[0]/Header[0]/HeaderRight[1]", + "childIndex" : 2, + "depth" : 4, + "layer" : 4, + "computedX" : 306.638, + "computedY" : 769.49, + "placementX" : 306.638, + "placementY" : 769.49, + "placementWidth" : 90.05, + "placementHeight" : 9.25, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 90.05, + "contentHeight" : 9.25, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Invoice[0]/Hero[1]", + "entityName" : "Hero", + "entityKind" : "SectionNode", + "parentPath" : "Invoice[0]", + "childIndex" : 1, + "depth" : 2, + "layer" : 2, + "computedX" : 24.0, + "computedY" : 696.365, + "placementX" : 24.0, + "placementY" : 696.365, + "placementWidth" : 544.712, + "placementHeight" : 57.125, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 544.712, + "contentHeight" : 57.125, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 14.0, + "right" : 14.0, + "bottom" : 14.0, + "left" : 14.0 + } + }, { + "path" : "Invoice[0]/Hero[1]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "Invoice[0]/Hero[1]", + "childIndex" : 0, + "depth" : 3, + "layer" : 3, + "computedX" : 38.0, + "computedY" : 729.315, + "placementX" : 38.0, + "placementY" : 729.315, + "placementWidth" : 62.942, + "placementHeight" : 10.175, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 62.942, + "contentHeight" : 10.175, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Invoice[0]/Hero[1]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "Invoice[0]/Hero[1]", + "childIndex" : 1, + "depth" : 3, + "layer" : 3, + "computedX" : 38.0, + "computedY" : 710.365, + "placementX" : 38.0, + "placementY" : 710.365, + "placementWidth" : 516.712, + "placementHeight" : 12.95, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 516.712, + "contentHeight" : 12.95, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Invoice[0]/Parties[2]", + "entityName" : "Parties", + "entityKind" : "RowNode", + "parentPath" : "Invoice[0]", + "childIndex" : 2, + "depth" : 2, + "layer" : 2, + "computedX" : 24.0, + "computedY" : 599.94, + "placementX" : 24.0, + "placementY" : 599.94, + "placementWidth" : 547.276, + "placementHeight" : 80.425, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 547.276, + "contentHeight" : 80.425, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Invoice[0]/Parties[2]/BillTo[0]", + "entityName" : "BillTo", + "entityKind" : "SectionNode", + "parentPath" : "Invoice[0]/Parties[2]", + "childIndex" : 0, + "depth" : 3, + "layer" : 3, + "computedX" : 24.0, + "computedY" : 599.94, + "placementX" : 24.0, + "placementY" : 599.94, + "placementWidth" : 115.709, + "placementHeight" : 80.425, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 115.709, + "contentHeight" : 80.425, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Invoice[0]/Parties[2]/BillTo[0]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "Invoice[0]/Parties[2]/BillTo[0]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 24.0, + "computedY" : 670.19, + "placementX" : 24.0, + "placementY" : 670.19, + "placementWidth" : 42.779, + "placementHeight" : 10.175, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 42.779, + "contentHeight" : 10.175, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Invoice[0]/Parties[2]/BillTo[0]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "Invoice[0]/Parties[2]/BillTo[0]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 24.0, + "computedY" : 658.015, + "placementX" : 24.0, + "placementY" : 658.015, + "placementWidth" : 102.685, + "placementHeight" : 10.175, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 102.685, + "contentHeight" : 10.175, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Invoice[0]/Parties[2]/BillTo[0]/ParagraphNode[2]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "Invoice[0]/Parties[2]/BillTo[0]", + "childIndex" : 2, + "depth" : 4, + "layer" : 4, + "computedX" : 24.0, + "computedY" : 599.94, + "placementX" : 24.0, + "placementY" : 599.94, + "placementWidth" : 115.709, + "placementHeight" : 56.075, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 115.709, + "contentHeight" : 56.075, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Invoice[0]/Parties[2]/From[1]", + "entityName" : "From", + "entityKind" : "SectionNode", + "parentPath" : "Invoice[0]/Parties[2]", + "childIndex" : 1, + "depth" : 3, + "layer" : 3, + "computedX" : 306.638, + "computedY" : 599.94, + "placementX" : 306.638, + "placementY" : 599.94, + "placementWidth" : 132.825, + "placementHeight" : 80.425, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 132.825, + "contentHeight" : 80.425, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Invoice[0]/Parties[2]/From[1]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "Invoice[0]/Parties[2]/From[1]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 306.638, + "computedY" : 670.19, + "placementX" : 306.638, + "placementY" : 670.19, + "placementWidth" : 32.384, + "placementHeight" : 10.175, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 32.384, + "contentHeight" : 10.175, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Invoice[0]/Parties[2]/From[1]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "Invoice[0]/Parties[2]/From[1]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 306.638, + "computedY" : 658.015, + "placementX" : 306.638, + "placementY" : 658.015, + "placementWidth" : 119.79, + "placementHeight" : 10.175, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 119.79, + "contentHeight" : 10.175, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Invoice[0]/Parties[2]/From[1]/ParagraphNode[2]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "Invoice[0]/Parties[2]/From[1]", + "childIndex" : 2, + "depth" : 4, + "layer" : 4, + "computedX" : 306.638, + "computedY" : 599.94, + "placementX" : 306.638, + "placementY" : 599.94, + "placementWidth" : 132.825, + "placementHeight" : 56.075, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 132.825, + "contentHeight" : 56.075, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Invoice[0]/LineItems[3]", + "entityName" : "LineItems", + "entityKind" : "TableNode", + "parentPath" : "Invoice[0]", + "childIndex" : 3, + "depth" : 2, + "layer" : 2, + "computedX" : 24.0, + "computedY" : 18.765, + "placementX" : 24.0, + "placementY" : 18.765, + "placementWidth" : 475.502, + "placementHeight" : 1104.175, + "startPage" : 0, + "endPage" : 1, + "contentWidth" : 475.502, + "contentHeight" : 1104.175, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Invoice[0]/Summary[4]", + "entityName" : "Summary", + "entityKind" : "SectionNode", + "parentPath" : "Invoice[0]", + "childIndex" : 4, + "depth" : 2, + "layer" : 2, + "computedX" : 24.0, + "computedY" : 180.64, + "placementX" : 24.0, + "placementY" : 180.64, + "placementWidth" : 262.0, + "placementHeight" : 80.075, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 262.0, + "contentHeight" : 80.075, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Invoice[0]/Summary[4]/SummaryTable[0]", + "entityName" : "SummaryTable", + "entityKind" : "TableNode", + "parentPath" : "Invoice[0]/Summary[4]", + "childIndex" : 0, + "depth" : 3, + "layer" : 3, + "computedX" : 24.0, + "computedY" : 180.64, + "placementX" : 24.0, + "placementY" : 180.64, + "placementWidth" : 262.0, + "placementHeight" : 80.075, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 262.0, + "contentHeight" : 80.075, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Invoice[0]/Footer[5]", + "entityName" : "Footer", + "entityKind" : "SectionNode", + "parentPath" : "Invoice[0]", + "childIndex" : 5, + "depth" : 2, + "layer" : 2, + "computedX" : 24.0, + "computedY" : 68.415, + "placementX" : 24.0, + "placementY" : 68.415, + "placementWidth" : 547.276, + "placementHeight" : 96.225, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 547.276, + "contentHeight" : 96.225, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Invoice[0]/Footer[5]/FooterRow[0]", + "entityName" : "FooterRow", + "entityKind" : "RowNode", + "parentPath" : "Invoice[0]/Footer[5]", + "childIndex" : 0, + "depth" : 3, + "layer" : 3, + "computedX" : 24.0, + "computedY" : 99.665, + "placementX" : 24.0, + "placementY" : 99.665, + "placementWidth" : 547.276, + "placementHeight" : 64.975, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 547.276, + "contentHeight" : 64.975, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Invoice[0]/Footer[5]/FooterRow[0]/InvoiceNotes[0]", + "entityName" : "InvoiceNotes", + "entityKind" : "SectionNode", + "parentPath" : "Invoice[0]/Footer[5]/FooterRow[0]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 24.0, + "computedY" : 99.665, + "placementX" : 24.0, + "placementY" : 99.665, + "placementWidth" : 250.2, + "placementHeight" : 64.975, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 250.2, + "contentHeight" : 64.975, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 8.0 + } + }, { + "path" : "Invoice[0]/Footer[5]/FooterRow[0]/InvoiceNotes[0]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "Invoice[0]/Footer[5]/FooterRow[0]/InvoiceNotes[0]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 32.0, + "computedY" : 154.465, + "placementX" : 32.0, + "placementY" : 154.465, + "placementWidth" : 30.558, + "placementHeight" : 10.175, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 30.558, + "contentHeight" : 10.175, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Invoice[0]/Footer[5]/FooterRow[0]/InvoiceNotes[0]/ListNode[1]", + "entityName" : null, + "entityKind" : "ListNode", + "parentPath" : "Invoice[0]/Footer[5]/FooterRow[0]/InvoiceNotes[0]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 32.0, + "computedY" : 99.665, + "placementX" : 32.0, + "placementY" : 99.665, + "placementWidth" : 242.2, + "placementHeight" : 51.8, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 242.2, + "contentHeight" : 51.8, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Invoice[0]/Footer[5]/FooterRow[0]/InvoicePaymentTerms[1]", + "entityName" : "InvoicePaymentTerms", + "entityKind" : "SectionNode", + "parentPath" : "Invoice[0]/Footer[5]/FooterRow[0]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 306.638, + "computedY" : 99.665, + "placementX" : 306.638, + "placementY" : 99.665, + "placementWidth" : 253.35, + "placementHeight" : 64.975, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 253.35, + "contentHeight" : 64.975, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 8.0 + } + }, { + "path" : "Invoice[0]/Footer[5]/FooterRow[0]/InvoicePaymentTerms[1]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "Invoice[0]/Footer[5]/FooterRow[0]/InvoicePaymentTerms[1]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 314.638, + "computedY" : 154.465, + "placementX" : 314.638, + "placementY" : 154.465, + "placementWidth" : 78.859, + "placementHeight" : 10.175, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 78.859, + "contentHeight" : 10.175, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Invoice[0]/Footer[5]/FooterRow[0]/InvoicePaymentTerms[1]/ListNode[1]", + "entityName" : null, + "entityKind" : "ListNode", + "parentPath" : "Invoice[0]/Footer[5]/FooterRow[0]/InvoicePaymentTerms[1]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 314.638, + "computedY" : 99.665, + "placementX" : 314.638, + "placementY" : 99.665, + "placementWidth" : 245.35, + "placementHeight" : 51.8, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 245.35, + "contentHeight" : 51.8, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Invoice[0]/Footer[5]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "Invoice[0]/Footer[5]", + "childIndex" : 1, + "depth" : 3, + "layer" : 3, + "computedX" : 24.0, + "computedY" : 68.415, + "placementX" : 24.0, + "placementY" : 68.415, + "placementWidth" : 335.71, + "placementHeight" : 9.25, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 335.71, + "contentHeight" : 9.25, + "margin" : { + "top" : 14.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + } ] +} diff --git a/qa/src/test/resources/layout-snapshots/canonical-templates/invoice/consulting_invoice_layout.json b/qa/src/test/resources/layout-snapshots/canonical-templates/invoice/consulting_invoice_layout.json new file mode 100644 index 000000000..71f81a1bc --- /dev/null +++ b/qa/src/test/resources/layout-snapshots/canonical-templates/invoice/consulting_invoice_layout.json @@ -0,0 +1,2867 @@ +{ + "formatVersion" : "2.0", + "canvas" : { + "pageWidth" : 595.276, + "pageHeight" : 841.89, + "innerWidth" : 534.276, + "innerHeight" : 780.89, + "margin" : { + "top" : 30.5, + "right" : 30.5, + "bottom" : 30.5, + "left" : 30.5 + } + }, + "totalPages" : 1, + "nodes" : [ { + "path" : "ConsultingInvoice[0]", + "entityName" : "ConsultingInvoice", + "entityKind" : "ContainerNode", + "parentPath" : null, + "childIndex" : 0, + "depth" : 1, + "layer" : 1, + "computedX" : 30.5, + "computedY" : 169.316, + "placementX" : 30.5, + "placementY" : 169.316, + "placementWidth" : 534.276, + "placementHeight" : 642.074, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 534.276, + "contentHeight" : 642.074, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/Masthead[0]", + "entityName" : "Masthead", + "entityKind" : "RowNode", + "parentPath" : "ConsultingInvoice[0]", + "childIndex" : 0, + "depth" : 2, + "layer" : 2, + "computedX" : 30.5, + "computedY" : 675.406, + "placementX" : 30.5, + "placementY" : 675.406, + "placementWidth" : 534.276, + "placementHeight" : 135.984, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 534.276, + "contentHeight" : 135.984, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/Masthead[0]/SupplierHeader[0]", + "entityName" : "SupplierHeader", + "entityKind" : "SectionNode", + "parentPath" : "ConsultingInvoice[0]/Masthead[0]", + "childIndex" : 0, + "depth" : 3, + "layer" : 3, + "computedX" : 30.5, + "computedY" : 682.39, + "placementX" : 30.5, + "placementY" : 682.39, + "placementWidth" : 259.731, + "placementHeight" : 129.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 259.731, + "contentHeight" : 129.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/Masthead[0]/SupplierHeader[0]/BrandLogoBox[0]", + "entityName" : "BrandLogoBox", + "entityKind" : "ShapeContainerNode", + "parentPath" : "ConsultingInvoice[0]/Masthead[0]/SupplierHeader[0]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 30.5, + "computedY" : 747.39, + "placementX" : 30.5, + "placementY" : 747.39, + "placementWidth" : 259.731, + "placementHeight" : 64.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 259.731, + "contentHeight" : 64.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/Masthead[0]/SupplierHeader[0]/BrandLogoBox[0]/BrandLogo[0]", + "entityName" : "BrandLogo", + "entityKind" : "ImageNode", + "parentPath" : "ConsultingInvoice[0]/Masthead[0]/SupplierHeader[0]/BrandLogoBox[0]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 30.5, + "computedY" : 747.39, + "placementX" : 30.5, + "placementY" : 747.39, + "placementWidth" : 164.874, + "placementHeight" : 64.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 164.874, + "contentHeight" : 64.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/Masthead[0]/SupplierHeader[0]/ContactBand[1]", + "entityName" : "ContactBand", + "entityKind" : "ShapeContainerNode", + "parentPath" : "ConsultingInvoice[0]/Masthead[0]/SupplierHeader[0]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 30.5, + "computedY" : 727.39, + "placementX" : 30.5, + "placementY" : 727.39, + "placementWidth" : 259.731, + "placementHeight" : 18.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 259.731, + "contentHeight" : 18.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/Masthead[0]/SupplierHeader[0]/ContactBand[1]/Icon-location[0]", + "entityName" : "Icon-location", + "entityKind" : "ImageNode", + "parentPath" : "ConsultingInvoice[0]/Masthead[0]/SupplierHeader[0]/ContactBand[1]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 30.5, + "computedY" : 731.39, + "placementX" : 30.5, + "placementY" : 731.39, + "placementWidth" : 10.0, + "placementHeight" : 10.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 10.0, + "contentHeight" : 10.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/Masthead[0]/SupplierHeader[0]/ContactBand[1]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "ConsultingInvoice[0]/Masthead[0]/SupplierHeader[0]/ContactBand[1]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 53.5, + "computedY" : 725.088, + "placementX" : 53.5, + "placementY" : 725.088, + "placementWidth" : 100.555, + "placementHeight" : 20.302, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 100.555, + "contentHeight" : 20.302, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/Masthead[0]/SupplierHeader[0]/ContactBand[2]", + "entityName" : "ContactBand", + "entityKind" : "ShapeContainerNode", + "parentPath" : "ConsultingInvoice[0]/Masthead[0]/SupplierHeader[0]", + "childIndex" : 2, + "depth" : 4, + "layer" : 4, + "computedX" : 30.5, + "computedY" : 712.39, + "placementX" : 30.5, + "placementY" : 712.39, + "placementWidth" : 259.731, + "placementHeight" : 13.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 259.731, + "contentHeight" : 13.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/Masthead[0]/SupplierHeader[0]/ContactBand[2]/Icon-phone[0]", + "entityName" : "Icon-phone", + "entityKind" : "ImageNode", + "parentPath" : "ConsultingInvoice[0]/Masthead[0]/SupplierHeader[0]/ContactBand[2]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 30.5, + "computedY" : 714.29, + "placementX" : 30.5, + "placementY" : 714.29, + "placementWidth" : 9.2, + "placementHeight" : 9.2, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 9.2, + "contentHeight" : 9.2, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/Masthead[0]/SupplierHeader[0]/ContactBand[2]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "ConsultingInvoice[0]/Masthead[0]/SupplierHeader[0]/ContactBand[2]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 53.5, + "computedY" : 714.102, + "placementX" : 53.5, + "placementY" : 714.102, + "placementWidth" : 53.974, + "placementHeight" : 9.576, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 53.974, + "contentHeight" : 9.576, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/Masthead[0]/SupplierHeader[0]/ContactBand[3]", + "entityName" : "ContactBand", + "entityKind" : "ShapeContainerNode", + "parentPath" : "ConsultingInvoice[0]/Masthead[0]/SupplierHeader[0]", + "childIndex" : 3, + "depth" : 4, + "layer" : 4, + "computedX" : 30.5, + "computedY" : 697.39, + "placementX" : 30.5, + "placementY" : 697.39, + "placementWidth" : 259.731, + "placementHeight" : 13.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 259.731, + "contentHeight" : 13.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/Masthead[0]/SupplierHeader[0]/ContactBand[3]/Icon-email[0]", + "entityName" : "Icon-email", + "entityKind" : "ImageNode", + "parentPath" : "ConsultingInvoice[0]/Masthead[0]/SupplierHeader[0]/ContactBand[3]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 30.5, + "computedY" : 699.29, + "placementX" : 30.5, + "placementY" : 699.29, + "placementWidth" : 9.2, + "placementHeight" : 9.2, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 9.2, + "contentHeight" : 9.2, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/Masthead[0]/SupplierHeader[0]/ContactBand[3]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "ConsultingInvoice[0]/Masthead[0]/SupplierHeader[0]/ContactBand[3]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 53.5, + "computedY" : 699.102, + "placementX" : 53.5, + "placementY" : 699.102, + "placementWidth" : 86.464, + "placementHeight" : 9.576, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 86.464, + "contentHeight" : 9.576, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/Masthead[0]/SupplierHeader[0]/ContactBand[4]", + "entityName" : "ContactBand", + "entityKind" : "ShapeContainerNode", + "parentPath" : "ConsultingInvoice[0]/Masthead[0]/SupplierHeader[0]", + "childIndex" : 4, + "depth" : 4, + "layer" : 4, + "computedX" : 30.5, + "computedY" : 682.39, + "placementX" : 30.5, + "placementY" : 682.39, + "placementWidth" : 259.731, + "placementHeight" : 13.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 259.731, + "contentHeight" : 13.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/Masthead[0]/SupplierHeader[0]/ContactBand[4]/Icon-website[0]", + "entityName" : "Icon-website", + "entityKind" : "ImageNode", + "parentPath" : "ConsultingInvoice[0]/Masthead[0]/SupplierHeader[0]/ContactBand[4]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 30.5, + "computedY" : 684.19, + "placementX" : 30.5, + "placementY" : 684.19, + "placementWidth" : 9.4, + "placementHeight" : 9.4, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 9.4, + "contentHeight" : 9.4, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/Masthead[0]/SupplierHeader[0]/ContactBand[4]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "ConsultingInvoice[0]/Masthead[0]/SupplierHeader[0]/ContactBand[4]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 53.5, + "computedY" : 684.102, + "placementX" : 53.5, + "placementY" : 684.102, + "placementWidth" : 63.174, + "placementHeight" : 9.576, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 63.174, + "contentHeight" : 9.576, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/Masthead[0]/InvoiceHeader[1]", + "entityName" : "InvoiceHeader", + "entityKind" : "SectionNode", + "parentPath" : "ConsultingInvoice[0]/Masthead[0]", + "childIndex" : 1, + "depth" : 3, + "layer" : 3, + "computedX" : 315.231, + "computedY" : 675.406, + "placementX" : 315.231, + "placementY" : 675.406, + "placementWidth" : 249.545, + "placementHeight" : 135.984, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 249.545, + "contentHeight" : 135.984, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 24.0 + } + }, { + "path" : "ConsultingInvoice[0]/Masthead[0]/InvoiceHeader[1]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "ConsultingInvoice[0]/Masthead[0]/InvoiceHeader[1]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 339.231, + "computedY" : 778.406, + "placementX" : 339.231, + "placementY" : 778.406, + "placementWidth" : 128.001, + "placementHeight" : 32.984, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 128.001, + "contentHeight" : 32.984, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 8.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/Masthead[0]/InvoiceHeader[1]/MetadataBand[1]", + "entityName" : "MetadataBand", + "entityKind" : "ShapeContainerNode", + "parentPath" : "ConsultingInvoice[0]/Masthead[0]/InvoiceHeader[1]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 339.231, + "computedY" : 751.406, + "placementX" : 339.231, + "placementY" : 751.406, + "placementWidth" : 225.545, + "placementHeight" : 18.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 225.545, + "contentHeight" : 18.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/Masthead[0]/InvoiceHeader[1]/MetadataBand[1]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "ConsultingInvoice[0]/Masthead[0]/InvoiceHeader[1]/MetadataBand[1]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 339.231, + "computedY" : 755.618, + "placementX" : 339.231, + "placementY" : 755.618, + "placementWidth" : 57.812, + "placementHeight" : 9.576, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 57.812, + "contentHeight" : 9.576, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/Masthead[0]/InvoiceHeader[1]/MetadataBand[1]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "ConsultingInvoice[0]/Masthead[0]/InvoiceHeader[1]/MetadataBand[1]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 447.492, + "computedY" : 755.086, + "placementX" : 447.492, + "placementY" : 755.086, + "placementWidth" : 57.509, + "placementHeight" : 10.64, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 57.509, + "contentHeight" : 10.64, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/Masthead[0]/InvoiceHeader[1]/MetadataBand[2]", + "entityName" : "MetadataBand", + "entityKind" : "ShapeContainerNode", + "parentPath" : "ConsultingInvoice[0]/Masthead[0]/InvoiceHeader[1]", + "childIndex" : 2, + "depth" : 4, + "layer" : 4, + "computedX" : 339.231, + "computedY" : 732.406, + "placementX" : 339.231, + "placementY" : 732.406, + "placementWidth" : 225.545, + "placementHeight" : 18.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 225.545, + "contentHeight" : 18.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/Masthead[0]/InvoiceHeader[1]/MetadataBand[2]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "ConsultingInvoice[0]/Masthead[0]/InvoiceHeader[1]/MetadataBand[2]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 339.231, + "computedY" : 736.618, + "placementX" : 339.231, + "placementY" : 736.618, + "placementWidth" : 38.475, + "placementHeight" : 9.576, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 38.475, + "contentHeight" : 9.576, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/Masthead[0]/InvoiceHeader[1]/MetadataBand[2]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "ConsultingInvoice[0]/Masthead[0]/InvoiceHeader[1]/MetadataBand[2]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 447.492, + "computedY" : 736.086, + "placementX" : 447.492, + "placementY" : 736.086, + "placementWidth" : 47.5, + "placementHeight" : 10.64, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 47.5, + "contentHeight" : 10.64, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/Masthead[0]/InvoiceHeader[1]/MetadataBand[3]", + "entityName" : "MetadataBand", + "entityKind" : "ShapeContainerNode", + "parentPath" : "ConsultingInvoice[0]/Masthead[0]/InvoiceHeader[1]", + "childIndex" : 3, + "depth" : 4, + "layer" : 4, + "computedX" : 339.231, + "computedY" : 713.406, + "placementX" : 339.231, + "placementY" : 713.406, + "placementWidth" : 225.545, + "placementHeight" : 18.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 225.545, + "contentHeight" : 18.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/Masthead[0]/InvoiceHeader[1]/MetadataBand[3]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "ConsultingInvoice[0]/Masthead[0]/InvoiceHeader[1]/MetadataBand[3]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 339.231, + "computedY" : 717.618, + "placementX" : 339.231, + "placementY" : 717.618, + "placementWidth" : 33.796, + "placementHeight" : 9.576, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 33.796, + "contentHeight" : 9.576, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/Masthead[0]/InvoiceHeader[1]/MetadataBand[3]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "ConsultingInvoice[0]/Masthead[0]/InvoiceHeader[1]/MetadataBand[3]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 447.492, + "computedY" : 717.086, + "placementX" : 447.492, + "placementY" : 717.086, + "placementWidth" : 50.396, + "placementHeight" : 10.64, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 50.396, + "contentHeight" : 10.64, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/Masthead[0]/InvoiceHeader[1]/MetadataBand[4]", + "entityName" : "MetadataBand", + "entityKind" : "ShapeContainerNode", + "parentPath" : "ConsultingInvoice[0]/Masthead[0]/InvoiceHeader[1]", + "childIndex" : 4, + "depth" : 4, + "layer" : 4, + "computedX" : 339.231, + "computedY" : 694.406, + "placementX" : 339.231, + "placementY" : 694.406, + "placementWidth" : 225.545, + "placementHeight" : 18.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 225.545, + "contentHeight" : 18.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/Masthead[0]/InvoiceHeader[1]/MetadataBand[4]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "ConsultingInvoice[0]/Masthead[0]/InvoiceHeader[1]/MetadataBand[4]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 339.231, + "computedY" : 698.618, + "placementX" : 339.231, + "placementY" : 698.618, + "placementWidth" : 26.635, + "placementHeight" : 9.576, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 26.635, + "contentHeight" : 9.576, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/Masthead[0]/InvoiceHeader[1]/MetadataBand[4]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "ConsultingInvoice[0]/Masthead[0]/InvoiceHeader[1]/MetadataBand[4]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 447.492, + "computedY" : 698.086, + "placementX" : 447.492, + "placementY" : 698.086, + "placementWidth" : 109.235, + "placementHeight" : 10.64, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 109.235, + "contentHeight" : 10.64, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/Masthead[0]/InvoiceHeader[1]/MetadataBand[5]", + "entityName" : "MetadataBand", + "entityKind" : "ShapeContainerNode", + "parentPath" : "ConsultingInvoice[0]/Masthead[0]/InvoiceHeader[1]", + "childIndex" : 5, + "depth" : 4, + "layer" : 4, + "computedX" : 339.231, + "computedY" : 675.406, + "placementX" : 339.231, + "placementY" : 675.406, + "placementWidth" : 225.545, + "placementHeight" : 18.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 225.545, + "contentHeight" : 18.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/Masthead[0]/InvoiceHeader[1]/MetadataBand[5]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "ConsultingInvoice[0]/Masthead[0]/InvoiceHeader[1]/MetadataBand[5]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 339.231, + "computedY" : 679.618, + "placementX" : 339.231, + "placementY" : 679.618, + "placementWidth" : 41.82, + "placementHeight" : 9.576, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 41.82, + "contentHeight" : 9.576, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/Masthead[0]/InvoiceHeader[1]/MetadataBand[5]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "ConsultingInvoice[0]/Masthead[0]/InvoiceHeader[1]/MetadataBand[5]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 447.492, + "computedY" : 679.086, + "placementX" : 447.492, + "placementY" : 679.086, + "placementWidth" : 32.665, + "placementHeight" : 10.64, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 32.665, + "contentHeight" : 10.64, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/SpacerNode[1]", + "entityName" : null, + "entityKind" : "SpacerNode", + "parentPath" : "ConsultingInvoice[0]", + "childIndex" : 1, + "depth" : 2, + "layer" : 2, + "computedX" : 30.5, + "computedY" : 659.406, + "placementX" : 30.5, + "placementY" : 659.406, + "placementWidth" : 0.0, + "placementHeight" : 16.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 0.0, + "contentHeight" : 16.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/PartiesAndSummary[2]", + "entityName" : "PartiesAndSummary", + "entityKind" : "RowNode", + "parentPath" : "ConsultingInvoice[0]", + "childIndex" : 2, + "depth" : 2, + "layer" : 2, + "computedX" : 30.5, + "computedY" : 561.63, + "placementX" : 30.5, + "placementY" : 561.63, + "placementWidth" : 534.276, + "placementHeight" : 97.776, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 534.276, + "contentHeight" : 97.776, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/PartiesAndSummary[2]/BilledTo[0]", + "entityName" : "BilledTo", + "entityKind" : "SectionNode", + "parentPath" : "ConsultingInvoice[0]/PartiesAndSummary[2]", + "childIndex" : 0, + "depth" : 3, + "layer" : 3, + "computedX" : 30.5, + "computedY" : 561.63, + "placementX" : 30.5, + "placementY" : 561.63, + "placementWidth" : 116.538, + "placementHeight" : 97.776, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 116.538, + "contentHeight" : 97.776, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/PartiesAndSummary[2]/BilledTo[0]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "ConsultingInvoice[0]/PartiesAndSummary[2]/BilledTo[0]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 30.5, + "computedY" : 647.17, + "placementX" : 30.5, + "placementY" : 647.17, + "placementWidth" : 56.469, + "placementHeight" : 12.236, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 56.469, + "contentHeight" : 12.236, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/PartiesAndSummary[2]/BilledTo[0]/LineNode[1]", + "entityName" : null, + "entityKind" : "LineNode", + "parentPath" : "ConsultingInvoice[0]/PartiesAndSummary[2]/BilledTo[0]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 30.5, + "computedY" : 640.47, + "placementX" : 30.5, + "placementY" : 640.47, + "placementWidth" : 30.0, + "placementHeight" : 1.2, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 30.0, + "contentHeight" : 1.2, + "margin" : { + "top" : 4.0, + "right" : 0.0, + "bottom" : 4.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/PartiesAndSummary[2]/BilledTo[0]/ParagraphNode[2]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "ConsultingInvoice[0]/PartiesAndSummary[2]/BilledTo[0]", + "childIndex" : 2, + "depth" : 4, + "layer" : 4, + "computedX" : 30.5, + "computedY" : 624.33, + "placementX" : 30.5, + "placementY" : 624.33, + "placementWidth" : 97.219, + "placementHeight" : 10.64, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 97.219, + "contentHeight" : 10.64, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/PartiesAndSummary[2]/BilledTo[0]/ParagraphNode[3]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "ConsultingInvoice[0]/PartiesAndSummary[2]/BilledTo[0]", + "childIndex" : 3, + "depth" : 4, + "layer" : 4, + "computedX" : 30.5, + "computedY" : 612.19, + "placementX" : 30.5, + "placementY" : 612.19, + "placementWidth" : 116.538, + "placementHeight" : 10.64, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 116.538, + "contentHeight" : 10.64, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/PartiesAndSummary[2]/BilledTo[0]/ParagraphNode[4]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "ConsultingInvoice[0]/PartiesAndSummary[2]/BilledTo[0]", + "childIndex" : 4, + "depth" : 4, + "layer" : 4, + "computedX" : 30.5, + "computedY" : 578.77, + "placementX" : 30.5, + "placementY" : 578.77, + "placementWidth" : 76.129, + "placementHeight" : 31.92, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 76.129, + "contentHeight" : 31.92, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/PartiesAndSummary[2]/BilledTo[0]/ParagraphNode[5]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "ConsultingInvoice[0]/PartiesAndSummary[2]/BilledTo[0]", + "childIndex" : 5, + "depth" : 4, + "layer" : 4, + "computedX" : 30.5, + "computedY" : 561.63, + "placementX" : 30.5, + "placementY" : 561.63, + "placementWidth" : 114.008, + "placementHeight" : 10.64, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 114.008, + "contentHeight" : 10.64, + "margin" : { + "top" : 5.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/PartiesAndSummary[2]/InvoiceSummary[1]", + "entityName" : "InvoiceSummary", + "entityKind" : "SectionNode", + "parentPath" : "ConsultingInvoice[0]/PartiesAndSummary[2]", + "childIndex" : 1, + "depth" : 3, + "layer" : 3, + "computedX" : 340.102, + "computedY" : 601.7, + "placementX" : 340.102, + "placementY" : 601.7, + "placementWidth" : 205.246, + "placementHeight" : 57.706, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 205.246, + "contentHeight" : 57.706, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/PartiesAndSummary[2]/InvoiceSummary[1]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "ConsultingInvoice[0]/PartiesAndSummary[2]/InvoiceSummary[1]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 340.102, + "computedY" : 647.17, + "placementX" : 340.102, + "placementY" : 647.17, + "placementWidth" : 109.748, + "placementHeight" : 12.236, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 109.748, + "contentHeight" : 12.236, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/PartiesAndSummary[2]/InvoiceSummary[1]/LineNode[1]", + "entityName" : null, + "entityKind" : "LineNode", + "parentPath" : "ConsultingInvoice[0]/PartiesAndSummary[2]/InvoiceSummary[1]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 340.102, + "computedY" : 640.97, + "placementX" : 340.102, + "placementY" : 640.97, + "placementWidth" : 30.0, + "placementHeight" : 1.2, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 30.0, + "contentHeight" : 1.2, + "margin" : { + "top" : 4.0, + "right" : 0.0, + "bottom" : 4.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/PartiesAndSummary[2]/InvoiceSummary[1]/ParagraphNode[2]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "ConsultingInvoice[0]/PartiesAndSummary[2]/InvoiceSummary[1]", + "childIndex" : 2, + "depth" : 4, + "layer" : 4, + "computedX" : 340.102, + "computedY" : 613.34, + "placementX" : 340.102, + "placementY" : 613.34, + "placementWidth" : 205.246, + "placementHeight" : 22.63, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 205.246, + "contentHeight" : 22.63, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/PartiesAndSummary[2]/InvoiceSummary[1]/ParagraphNode[3]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "ConsultingInvoice[0]/PartiesAndSummary[2]/InvoiceSummary[1]", + "childIndex" : 3, + "depth" : 4, + "layer" : 4, + "computedX" : 340.102, + "computedY" : 601.7, + "placementX" : 340.102, + "placementY" : 601.7, + "placementWidth" : 100.259, + "placementHeight" : 10.64, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 100.259, + "contentHeight" : 10.64, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/SpacerNode[3]", + "entityName" : null, + "entityKind" : "SpacerNode", + "parentPath" : "ConsultingInvoice[0]", + "childIndex" : 3, + "depth" : 2, + "layer" : 2, + "computedX" : 30.5, + "computedY" : 551.63, + "placementX" : 30.5, + "placementY" : 551.63, + "placementWidth" : 0.0, + "placementHeight" : 10.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 0.0, + "contentHeight" : 10.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/LineItems[4]", + "entityName" : "LineItems", + "entityKind" : "TableNode", + "parentPath" : "ConsultingInvoice[0]", + "childIndex" : 4, + "depth" : 2, + "layer" : 2, + "computedX" : 30.5, + "computedY" : 368.328, + "placementX" : 30.5, + "placementY" : 368.328, + "placementWidth" : 534.276, + "placementHeight" : 183.302, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 534.276, + "contentHeight" : 183.302, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/Totals[5]", + "entityName" : "Totals", + "entityKind" : "SectionNode", + "parentPath" : "ConsultingInvoice[0]", + "childIndex" : 5, + "depth" : 2, + "layer" : 2, + "computedX" : 340.38, + "computedY" : 299.328, + "placementX" : 340.38, + "placementY" : 299.328, + "placementWidth" : 224.396, + "placementHeight" : 63.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 224.396, + "contentHeight" : 63.0, + "margin" : { + "top" : 6.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 309.88 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/Totals[5]/TotalRow[0]", + "entityName" : "TotalRow", + "entityKind" : "ShapeContainerNode", + "parentPath" : "ConsultingInvoice[0]/Totals[5]", + "childIndex" : 0, + "depth" : 3, + "layer" : 3, + "computedX" : 340.38, + "computedY" : 347.328, + "placementX" : 340.38, + "placementY" : 347.328, + "placementWidth" : 224.396, + "placementHeight" : 15.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 224.396, + "contentHeight" : 15.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/Totals[5]/TotalRow[0]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "ConsultingInvoice[0]/Totals[5]/TotalRow[0]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 340.38, + "computedY" : 350.04, + "placementX" : 340.38, + "placementY" : 350.04, + "placementWidth" : 48.301, + "placementHeight" : 9.576, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 48.301, + "contentHeight" : 9.576, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 13.0 + } + }, { + "path" : "ConsultingInvoice[0]/Totals[5]/TotalRow[0]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "ConsultingInvoice[0]/Totals[5]/TotalRow[0]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 340.38, + "computedY" : 349.508, + "placementX" : 340.38, + "placementY" : 349.508, + "placementWidth" : 224.396, + "placementHeight" : 10.64, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 224.396, + "contentHeight" : 10.64, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 10.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/Totals[5]/TotalRow[1]", + "entityName" : "TotalRow", + "entityKind" : "ShapeContainerNode", + "parentPath" : "ConsultingInvoice[0]/Totals[5]", + "childIndex" : 1, + "depth" : 3, + "layer" : 3, + "computedX" : 340.38, + "computedY" : 331.328, + "placementX" : 340.38, + "placementY" : 331.328, + "placementWidth" : 224.396, + "placementHeight" : 15.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 224.396, + "contentHeight" : 15.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/Totals[5]/TotalRow[1]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "ConsultingInvoice[0]/Totals[5]/TotalRow[1]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 340.38, + "computedY" : 334.04, + "placementX" : 340.38, + "placementY" : 334.04, + "placementWidth" : 47.419, + "placementHeight" : 9.576, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 47.419, + "contentHeight" : 9.576, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 13.0 + } + }, { + "path" : "ConsultingInvoice[0]/Totals[5]/TotalRow[1]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "ConsultingInvoice[0]/Totals[5]/TotalRow[1]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 340.38, + "computedY" : 333.508, + "placementX" : 340.38, + "placementY" : 333.508, + "placementWidth" : 224.396, + "placementHeight" : 10.64, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 224.396, + "contentHeight" : 10.64, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 10.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/Totals[5]/LineNode[2]", + "entityName" : null, + "entityKind" : "LineNode", + "parentPath" : "ConsultingInvoice[0]/Totals[5]", + "childIndex" : 2, + "depth" : 3, + "layer" : 3, + "computedX" : 340.38, + "computedY" : 327.328, + "placementX" : 340.38, + "placementY" : 327.328, + "placementWidth" : 224.396, + "placementHeight" : 1.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 224.396, + "contentHeight" : 1.0, + "margin" : { + "top" : 2.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/Totals[5]/TotalDue[3]", + "entityName" : "TotalDue", + "entityKind" : "ShapeContainerNode", + "parentPath" : "ConsultingInvoice[0]/Totals[5]", + "childIndex" : 3, + "depth" : 3, + "layer" : 3, + "computedX" : 340.38, + "computedY" : 299.328, + "placementX" : 340.38, + "placementY" : 299.328, + "placementWidth" : 224.396, + "placementHeight" : 27.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 224.396, + "contentHeight" : 27.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/Totals[5]/TotalDue[3]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "ConsultingInvoice[0]/Totals[5]/TotalDue[3]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 340.38, + "computedY" : 307.508, + "placementX" : 340.38, + "placementY" : 307.508, + "placementWidth" : 79.135, + "placementHeight" : 10.64, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 79.135, + "contentHeight" : 10.64, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 13.0 + } + }, { + "path" : "ConsultingInvoice[0]/Totals[5]/TotalDue[3]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "ConsultingInvoice[0]/Totals[5]/TotalDue[3]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 340.38, + "computedY" : 303.252, + "placementX" : 340.38, + "placementY" : 303.252, + "placementWidth" : 224.396, + "placementHeight" : 19.152, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 224.396, + "contentHeight" : 19.152, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 10.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/LineNode[6]", + "entityName" : null, + "entityKind" : "LineNode", + "parentPath" : "ConsultingInvoice[0]", + "childIndex" : 6, + "depth" : 2, + "layer" : 2, + "computedX" : 30.5, + "computedY" : 293.328, + "placementX" : 30.5, + "placementY" : 293.328, + "placementWidth" : 534.276, + "placementHeight" : 1.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 534.276, + "contentHeight" : 1.0, + "margin" : { + "top" : 5.0, + "right" : 0.0, + "bottom" : 5.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/PaymentAndNotes[7]", + "entityName" : "PaymentAndNotes", + "entityKind" : "RowNode", + "parentPath" : "ConsultingInvoice[0]", + "childIndex" : 7, + "depth" : 2, + "layer" : 2, + "computedX" : 30.5, + "computedY" : 169.316, + "placementX" : 30.5, + "placementY" : 169.316, + "placementWidth" : 534.276, + "placementHeight" : 119.012, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 534.276, + "contentHeight" : 119.012, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/PaymentAndNotes[7]/PaymentInformation[0]", + "entityName" : "PaymentInformation", + "entityKind" : "SectionNode", + "parentPath" : "ConsultingInvoice[0]/PaymentAndNotes[7]", + "childIndex" : 0, + "depth" : 3, + "layer" : 3, + "computedX" : 30.5, + "computedY" : 169.316, + "placementX" : 30.5, + "placementY" : 169.316, + "placementWidth" : 276.629, + "placementHeight" : 119.012, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 276.629, + "contentHeight" : 119.012, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/PaymentAndNotes[7]/PaymentInformation[0]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "ConsultingInvoice[0]/PaymentAndNotes[7]/PaymentInformation[0]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 30.5, + "computedY" : 276.092, + "placementX" : 30.5, + "placementY" : 276.092, + "placementWidth" : 139.604, + "placementHeight" : 12.236, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 139.604, + "contentHeight" : 12.236, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/PaymentAndNotes[7]/PaymentInformation[0]/LineNode[1]", + "entityName" : null, + "entityKind" : "LineNode", + "parentPath" : "ConsultingInvoice[0]/PaymentAndNotes[7]/PaymentInformation[0]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 30.5, + "computedY" : 267.892, + "placementX" : 30.5, + "placementY" : 267.892, + "placementWidth" : 30.0, + "placementHeight" : 1.2, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 30.0, + "contentHeight" : 1.2, + "margin" : { + "top" : 4.0, + "right" : 0.0, + "bottom" : 4.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/PaymentAndNotes[7]/PaymentInformation[0]/BankInformationBand[2]", + "entityName" : "BankInformationBand", + "entityKind" : "ShapeContainerNode", + "parentPath" : "ConsultingInvoice[0]/PaymentAndNotes[7]/PaymentInformation[0]", + "childIndex" : 2, + "depth" : 4, + "layer" : 4, + "computedX" : 30.5, + "computedY" : 185.892, + "placementX" : 30.5, + "placementY" : 185.892, + "placementWidth" : 276.629, + "placementHeight" : 75.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 276.629, + "contentHeight" : 75.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/PaymentAndNotes[7]/PaymentInformation[0]/BankInformationBand[2]/BankBadge[0]", + "entityName" : "BankBadge", + "entityKind" : "ShapeContainerNode", + "parentPath" : "ConsultingInvoice[0]/PaymentAndNotes[7]/PaymentInformation[0]/BankInformationBand[2]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 30.5, + "computedY" : 204.392, + "placementX" : 30.5, + "placementY" : 204.392, + "placementWidth" : 38.0, + "placementHeight" : 38.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 38.0, + "contentHeight" : 38.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/PaymentAndNotes[7]/PaymentInformation[0]/BankInformationBand[2]/BankBadge[0]/Icon-bank[0]", + "entityName" : "Icon-bank", + "entityKind" : "ImageNode", + "parentPath" : "ConsultingInvoice[0]/PaymentAndNotes[7]/PaymentInformation[0]/BankInformationBand[2]/BankBadge[0]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 39.75, + "computedY" : 213.642, + "placementX" : 39.75, + "placementY" : 213.642, + "placementWidth" : 19.5, + "placementHeight" : 19.5, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 19.5, + "contentHeight" : 19.5, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/PaymentAndNotes[7]/PaymentInformation[0]/BankInformationBand[2]/BankDetails[1]", + "entityName" : "BankDetails", + "entityKind" : "SectionNode", + "parentPath" : "ConsultingInvoice[0]/PaymentAndNotes[7]/PaymentInformation[0]/BankInformationBand[2]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 88.5, + "computedY" : 185.892, + "placementX" : 88.5, + "placementY" : 185.892, + "placementWidth" : 215.629, + "placementHeight" : 75.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 215.629, + "contentHeight" : 75.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/PaymentAndNotes[7]/PaymentInformation[0]/BankInformationBand[2]/BankDetails[1]/BankField[0]", + "entityName" : "BankField", + "entityKind" : "ShapeContainerNode", + "parentPath" : "ConsultingInvoice[0]/PaymentAndNotes[7]/PaymentInformation[0]/BankInformationBand[2]/BankDetails[1]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 88.5, + "computedY" : 245.892, + "placementX" : 88.5, + "placementY" : 245.892, + "placementWidth" : 215.629, + "placementHeight" : 15.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 215.629, + "contentHeight" : 15.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/PaymentAndNotes[7]/PaymentInformation[0]/BankInformationBand[2]/BankDetails[1]/BankField[0]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "ConsultingInvoice[0]/PaymentAndNotes[7]/PaymentInformation[0]/BankInformationBand[2]/BankDetails[1]/BankField[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 88.5, + "computedY" : 248.604, + "placementX" : 88.5, + "placementY" : 248.604, + "placementWidth" : 42.627, + "placementHeight" : 9.576, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 42.627, + "contentHeight" : 9.576, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/PaymentAndNotes[7]/PaymentInformation[0]/BankInformationBand[2]/BankDetails[1]/BankField[0]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "ConsultingInvoice[0]/PaymentAndNotes[7]/PaymentInformation[0]/BankInformationBand[2]/BankDetails[1]/BankField[0]", + "childIndex" : 1, + "depth" : 7, + "layer" : 7, + "computedX" : 164.5, + "computedY" : 248.604, + "placementX" : 164.5, + "placementY" : 248.604, + "placementWidth" : 47.545, + "placementHeight" : 9.576, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 47.545, + "contentHeight" : 9.576, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/PaymentAndNotes[7]/PaymentInformation[0]/BankInformationBand[2]/BankDetails[1]/BankField[1]", + "entityName" : "BankField", + "entityKind" : "ShapeContainerNode", + "parentPath" : "ConsultingInvoice[0]/PaymentAndNotes[7]/PaymentInformation[0]/BankInformationBand[2]/BankDetails[1]", + "childIndex" : 1, + "depth" : 6, + "layer" : 6, + "computedX" : 88.5, + "computedY" : 230.892, + "placementX" : 88.5, + "placementY" : 230.892, + "placementWidth" : 215.629, + "placementHeight" : 15.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 215.629, + "contentHeight" : 15.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/PaymentAndNotes[7]/PaymentInformation[0]/BankInformationBand[2]/BankDetails[1]/BankField[1]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "ConsultingInvoice[0]/PaymentAndNotes[7]/PaymentInformation[0]/BankInformationBand[2]/BankDetails[1]/BankField[1]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 88.5, + "computedY" : 233.604, + "placementX" : 88.5, + "placementY" : 233.604, + "placementWidth" : 54.31, + "placementHeight" : 9.576, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 54.31, + "contentHeight" : 9.576, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/PaymentAndNotes[7]/PaymentInformation[0]/BankInformationBand[2]/BankDetails[1]/BankField[1]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "ConsultingInvoice[0]/PaymentAndNotes[7]/PaymentInformation[0]/BankInformationBand[2]/BankDetails[1]/BankField[1]", + "childIndex" : 1, + "depth" : 7, + "layer" : 7, + "computedX" : 164.5, + "computedY" : 233.604, + "placementX" : 164.5, + "placementY" : 233.604, + "placementWidth" : 98.845, + "placementHeight" : 9.576, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 98.845, + "contentHeight" : 9.576, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/PaymentAndNotes[7]/PaymentInformation[0]/BankInformationBand[2]/BankDetails[1]/BankField[2]", + "entityName" : "BankField", + "entityKind" : "ShapeContainerNode", + "parentPath" : "ConsultingInvoice[0]/PaymentAndNotes[7]/PaymentInformation[0]/BankInformationBand[2]/BankDetails[1]", + "childIndex" : 2, + "depth" : 6, + "layer" : 6, + "computedX" : 88.5, + "computedY" : 215.892, + "placementX" : 88.5, + "placementY" : 215.892, + "placementWidth" : 215.629, + "placementHeight" : 15.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 215.629, + "contentHeight" : 15.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/PaymentAndNotes[7]/PaymentInformation[0]/BankInformationBand[2]/BankDetails[1]/BankField[2]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "ConsultingInvoice[0]/PaymentAndNotes[7]/PaymentInformation[0]/BankInformationBand[2]/BankDetails[1]/BankField[2]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 88.5, + "computedY" : 218.604, + "placementX" : 88.5, + "placementY" : 218.604, + "placementWidth" : 15.164, + "placementHeight" : 9.576, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 15.164, + "contentHeight" : 9.576, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/PaymentAndNotes[7]/PaymentInformation[0]/BankInformationBand[2]/BankDetails[1]/BankField[2]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "ConsultingInvoice[0]/PaymentAndNotes[7]/PaymentInformation[0]/BankInformationBand[2]/BankDetails[1]/BankField[2]", + "childIndex" : 1, + "depth" : 7, + "layer" : 7, + "computedX" : 164.5, + "computedY" : 218.604, + "placementX" : 164.5, + "placementY" : 218.604, + "placementWidth" : 26.861, + "placementHeight" : 9.576, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 26.861, + "contentHeight" : 9.576, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/PaymentAndNotes[7]/PaymentInformation[0]/BankInformationBand[2]/BankDetails[1]/BankField[3]", + "entityName" : "BankField", + "entityKind" : "ShapeContainerNode", + "parentPath" : "ConsultingInvoice[0]/PaymentAndNotes[7]/PaymentInformation[0]/BankInformationBand[2]/BankDetails[1]", + "childIndex" : 3, + "depth" : 6, + "layer" : 6, + "computedX" : 88.5, + "computedY" : 200.892, + "placementX" : 88.5, + "placementY" : 200.892, + "placementWidth" : 215.629, + "placementHeight" : 15.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 215.629, + "contentHeight" : 15.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/PaymentAndNotes[7]/PaymentInformation[0]/BankInformationBand[2]/BankDetails[1]/BankField[3]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "ConsultingInvoice[0]/PaymentAndNotes[7]/PaymentInformation[0]/BankInformationBand[2]/BankDetails[1]/BankField[3]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 88.5, + "computedY" : 203.604, + "placementX" : 88.5, + "placementY" : 203.604, + "placementWidth" : 61.847, + "placementHeight" : 9.576, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 61.847, + "contentHeight" : 9.576, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/PaymentAndNotes[7]/PaymentInformation[0]/BankInformationBand[2]/BankDetails[1]/BankField[3]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "ConsultingInvoice[0]/PaymentAndNotes[7]/PaymentInformation[0]/BankInformationBand[2]/BankDetails[1]/BankField[3]", + "childIndex" : 1, + "depth" : 7, + "layer" : 7, + "computedX" : 164.5, + "computedY" : 203.604, + "placementX" : 164.5, + "placementY" : 203.604, + "placementWidth" : 31.143, + "placementHeight" : 9.576, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 31.143, + "contentHeight" : 9.576, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/PaymentAndNotes[7]/PaymentInformation[0]/BankInformationBand[2]/BankDetails[1]/BankField[4]", + "entityName" : "BankField", + "entityKind" : "ShapeContainerNode", + "parentPath" : "ConsultingInvoice[0]/PaymentAndNotes[7]/PaymentInformation[0]/BankInformationBand[2]/BankDetails[1]", + "childIndex" : 4, + "depth" : 6, + "layer" : 6, + "computedX" : 88.5, + "computedY" : 185.892, + "placementX" : 88.5, + "placementY" : 185.892, + "placementWidth" : 215.629, + "placementHeight" : 15.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 215.629, + "contentHeight" : 15.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/PaymentAndNotes[7]/PaymentInformation[0]/BankInformationBand[2]/BankDetails[1]/BankField[4]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "ConsultingInvoice[0]/PaymentAndNotes[7]/PaymentInformation[0]/BankInformationBand[2]/BankDetails[1]/BankField[4]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 88.5, + "computedY" : 188.604, + "placementX" : 88.5, + "placementY" : 188.604, + "placementWidth" : 37.394, + "placementHeight" : 9.576, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 37.394, + "contentHeight" : 9.576, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/PaymentAndNotes[7]/PaymentInformation[0]/BankInformationBand[2]/BankDetails[1]/BankField[4]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "ConsultingInvoice[0]/PaymentAndNotes[7]/PaymentInformation[0]/BankInformationBand[2]/BankDetails[1]/BankField[4]", + "childIndex" : 1, + "depth" : 7, + "layer" : 7, + "computedX" : 164.5, + "computedY" : 188.604, + "placementX" : 164.5, + "placementY" : 188.604, + "placementWidth" : 51.758, + "placementHeight" : 9.576, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 51.758, + "contentHeight" : 9.576, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/PaymentAndNotes[7]/PaymentInformation[0]/ParagraphNode[3]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "ConsultingInvoice[0]/PaymentAndNotes[7]/PaymentInformation[0]", + "childIndex" : 3, + "depth" : 4, + "layer" : 4, + "computedX" : 30.5, + "computedY" : 169.316, + "placementX" : 30.5, + "placementY" : 169.316, + "placementWidth" : 246.452, + "placementHeight" : 9.576, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 246.452, + "contentHeight" : 9.576, + "margin" : { + "top" : 4.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/PaymentAndNotes[7]/Notes[1]", + "entityName" : "Notes", + "entityKind" : "SectionNode", + "parentPath" : "ConsultingInvoice[0]/PaymentAndNotes[7]", + "childIndex" : 1, + "depth" : 3, + "layer" : 3, + "computedX" : 329.129, + "computedY" : 175.732, + "placementX" : 329.129, + "placementY" : 175.732, + "placementWidth" : 235.647, + "placementHeight" : 112.596, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 235.647, + "contentHeight" : 112.596, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 24.0 + } + }, { + "path" : "ConsultingInvoice[0]/PaymentAndNotes[7]/Notes[1]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "ConsultingInvoice[0]/PaymentAndNotes[7]/Notes[1]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 353.129, + "computedY" : 276.092, + "placementX" : 353.129, + "placementY" : 276.092, + "placementWidth" : 36.122, + "placementHeight" : 12.236, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 36.122, + "contentHeight" : 12.236, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/PaymentAndNotes[7]/Notes[1]/LineNode[1]", + "entityName" : null, + "entityKind" : "LineNode", + "parentPath" : "ConsultingInvoice[0]/PaymentAndNotes[7]/Notes[1]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 353.129, + "computedY" : 267.892, + "placementX" : 353.129, + "placementY" : 267.892, + "placementWidth" : 30.0, + "placementHeight" : 1.2, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 30.0, + "contentHeight" : 1.2, + "margin" : { + "top" : 4.0, + "right" : 0.0, + "bottom" : 4.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/PaymentAndNotes[7]/Notes[1]/ParagraphNode[2]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "ConsultingInvoice[0]/PaymentAndNotes[7]/Notes[1]", + "childIndex" : 2, + "depth" : 4, + "layer" : 4, + "computedX" : 353.129, + "computedY" : 250.252, + "placementX" : 353.129, + "placementY" : 250.252, + "placementWidth" : 107.0, + "placementHeight" : 10.64, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 107.0, + "contentHeight" : 10.64, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/PaymentAndNotes[7]/Notes[1]/ParagraphNode[3]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "ConsultingInvoice[0]/PaymentAndNotes[7]/Notes[1]", + "childIndex" : 3, + "depth" : 4, + "layer" : 4, + "computedX" : 353.129, + "computedY" : 212.732, + "placementX" : 353.129, + "placementY" : 212.732, + "placementWidth" : 210.11, + "placementHeight" : 34.52, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 210.11, + "contentHeight" : 34.52, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/PaymentAndNotes[7]/Notes[1]/PaymentTerms[4]", + "entityName" : "PaymentTerms", + "entityKind" : "ShapeContainerNode", + "parentPath" : "ConsultingInvoice[0]/PaymentAndNotes[7]/Notes[1]", + "childIndex" : 4, + "depth" : 4, + "layer" : 4, + "computedX" : 353.129, + "computedY" : 175.732, + "placementX" : 353.129, + "placementY" : 175.732, + "placementWidth" : 211.647, + "placementHeight" : 34.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 211.647, + "contentHeight" : 34.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/PaymentAndNotes[7]/Notes[1]/PaymentTerms[4]/CalendarBadge[0]", + "entityName" : "CalendarBadge", + "entityKind" : "ShapeContainerNode", + "parentPath" : "ConsultingInvoice[0]/PaymentAndNotes[7]/Notes[1]/PaymentTerms[4]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 361.129, + "computedY" : 181.232, + "placementX" : 361.129, + "placementY" : 181.232, + "placementWidth" : 23.0, + "placementHeight" : 23.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 23.0, + "contentHeight" : 23.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/PaymentAndNotes[7]/Notes[1]/PaymentTerms[4]/CalendarBadge[0]/Icon-payment-calendar[0]", + "entityName" : "Icon-payment-calendar", + "entityKind" : "ImageNode", + "parentPath" : "ConsultingInvoice[0]/PaymentAndNotes[7]/Notes[1]/PaymentTerms[4]/CalendarBadge[0]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 366.379, + "computedY" : 186.482, + "placementX" : 366.379, + "placementY" : 186.482, + "placementWidth" : 12.5, + "placementHeight" : 12.5, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 12.5, + "contentHeight" : 12.5, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/PaymentAndNotes[7]/Notes[1]/PaymentTerms[4]/DueNotice[1]", + "entityName" : "DueNotice", + "entityKind" : "ParagraphNode", + "parentPath" : "ConsultingInvoice[0]/PaymentAndNotes[7]/Notes[1]/PaymentTerms[4]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 395.129, + "computedY" : 188.689, + "placementX" : 395.129, + "placementY" : 188.689, + "placementWidth" : 150.049, + "placementHeight" : 8.086, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 150.049, + "contentHeight" : 8.086, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + } ] +} diff --git a/qa/src/test/resources/layout-snapshots/canonical-templates/invoice/consulting_invoice_overflow_layout.json b/qa/src/test/resources/layout-snapshots/canonical-templates/invoice/consulting_invoice_overflow_layout.json new file mode 100644 index 000000000..fda4ec506 --- /dev/null +++ b/qa/src/test/resources/layout-snapshots/canonical-templates/invoice/consulting_invoice_overflow_layout.json @@ -0,0 +1,2867 @@ +{ + "formatVersion" : "2.0", + "canvas" : { + "pageWidth" : 595.276, + "pageHeight" : 841.89, + "innerWidth" : 534.276, + "innerHeight" : 780.89, + "margin" : { + "top" : 30.5, + "right" : 30.5, + "bottom" : 30.5, + "left" : 30.5 + } + }, + "totalPages" : 2, + "nodes" : [ { + "path" : "ConsultingInvoice[0]", + "entityName" : "ConsultingInvoice", + "entityKind" : "ContainerNode", + "parentPath" : null, + "childIndex" : 0, + "depth" : 1, + "layer" : 1, + "computedX" : 30.5, + "computedY" : -478.114, + "placementX" : 30.5, + "placementY" : -478.114, + "placementWidth" : 534.276, + "placementHeight" : 1289.504, + "startPage" : 0, + "endPage" : 1, + "contentWidth" : 534.276, + "contentHeight" : 1289.504, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/Masthead[0]", + "entityName" : "Masthead", + "entityKind" : "RowNode", + "parentPath" : "ConsultingInvoice[0]", + "childIndex" : 0, + "depth" : 2, + "layer" : 2, + "computedX" : 30.5, + "computedY" : 675.406, + "placementX" : 30.5, + "placementY" : 675.406, + "placementWidth" : 534.276, + "placementHeight" : 135.984, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 534.276, + "contentHeight" : 135.984, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/Masthead[0]/SupplierHeader[0]", + "entityName" : "SupplierHeader", + "entityKind" : "SectionNode", + "parentPath" : "ConsultingInvoice[0]/Masthead[0]", + "childIndex" : 0, + "depth" : 3, + "layer" : 3, + "computedX" : 30.5, + "computedY" : 682.39, + "placementX" : 30.5, + "placementY" : 682.39, + "placementWidth" : 259.731, + "placementHeight" : 129.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 259.731, + "contentHeight" : 129.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/Masthead[0]/SupplierHeader[0]/BrandLogoBox[0]", + "entityName" : "BrandLogoBox", + "entityKind" : "ShapeContainerNode", + "parentPath" : "ConsultingInvoice[0]/Masthead[0]/SupplierHeader[0]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 30.5, + "computedY" : 747.39, + "placementX" : 30.5, + "placementY" : 747.39, + "placementWidth" : 259.731, + "placementHeight" : 64.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 259.731, + "contentHeight" : 64.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/Masthead[0]/SupplierHeader[0]/BrandLogoBox[0]/BrandLogo[0]", + "entityName" : "BrandLogo", + "entityKind" : "ImageNode", + "parentPath" : "ConsultingInvoice[0]/Masthead[0]/SupplierHeader[0]/BrandLogoBox[0]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 30.5, + "computedY" : 747.39, + "placementX" : 30.5, + "placementY" : 747.39, + "placementWidth" : 164.874, + "placementHeight" : 64.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 164.874, + "contentHeight" : 64.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/Masthead[0]/SupplierHeader[0]/ContactBand[1]", + "entityName" : "ContactBand", + "entityKind" : "ShapeContainerNode", + "parentPath" : "ConsultingInvoice[0]/Masthead[0]/SupplierHeader[0]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 30.5, + "computedY" : 727.39, + "placementX" : 30.5, + "placementY" : 727.39, + "placementWidth" : 259.731, + "placementHeight" : 18.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 259.731, + "contentHeight" : 18.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/Masthead[0]/SupplierHeader[0]/ContactBand[1]/Icon-location[0]", + "entityName" : "Icon-location", + "entityKind" : "ImageNode", + "parentPath" : "ConsultingInvoice[0]/Masthead[0]/SupplierHeader[0]/ContactBand[1]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 30.5, + "computedY" : 731.39, + "placementX" : 30.5, + "placementY" : 731.39, + "placementWidth" : 10.0, + "placementHeight" : 10.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 10.0, + "contentHeight" : 10.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/Masthead[0]/SupplierHeader[0]/ContactBand[1]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "ConsultingInvoice[0]/Masthead[0]/SupplierHeader[0]/ContactBand[1]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 53.5, + "computedY" : 725.088, + "placementX" : 53.5, + "placementY" : 725.088, + "placementWidth" : 100.555, + "placementHeight" : 20.302, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 100.555, + "contentHeight" : 20.302, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/Masthead[0]/SupplierHeader[0]/ContactBand[2]", + "entityName" : "ContactBand", + "entityKind" : "ShapeContainerNode", + "parentPath" : "ConsultingInvoice[0]/Masthead[0]/SupplierHeader[0]", + "childIndex" : 2, + "depth" : 4, + "layer" : 4, + "computedX" : 30.5, + "computedY" : 712.39, + "placementX" : 30.5, + "placementY" : 712.39, + "placementWidth" : 259.731, + "placementHeight" : 13.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 259.731, + "contentHeight" : 13.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/Masthead[0]/SupplierHeader[0]/ContactBand[2]/Icon-phone[0]", + "entityName" : "Icon-phone", + "entityKind" : "ImageNode", + "parentPath" : "ConsultingInvoice[0]/Masthead[0]/SupplierHeader[0]/ContactBand[2]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 30.5, + "computedY" : 714.29, + "placementX" : 30.5, + "placementY" : 714.29, + "placementWidth" : 9.2, + "placementHeight" : 9.2, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 9.2, + "contentHeight" : 9.2, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/Masthead[0]/SupplierHeader[0]/ContactBand[2]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "ConsultingInvoice[0]/Masthead[0]/SupplierHeader[0]/ContactBand[2]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 53.5, + "computedY" : 714.102, + "placementX" : 53.5, + "placementY" : 714.102, + "placementWidth" : 53.974, + "placementHeight" : 9.576, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 53.974, + "contentHeight" : 9.576, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/Masthead[0]/SupplierHeader[0]/ContactBand[3]", + "entityName" : "ContactBand", + "entityKind" : "ShapeContainerNode", + "parentPath" : "ConsultingInvoice[0]/Masthead[0]/SupplierHeader[0]", + "childIndex" : 3, + "depth" : 4, + "layer" : 4, + "computedX" : 30.5, + "computedY" : 697.39, + "placementX" : 30.5, + "placementY" : 697.39, + "placementWidth" : 259.731, + "placementHeight" : 13.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 259.731, + "contentHeight" : 13.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/Masthead[0]/SupplierHeader[0]/ContactBand[3]/Icon-email[0]", + "entityName" : "Icon-email", + "entityKind" : "ImageNode", + "parentPath" : "ConsultingInvoice[0]/Masthead[0]/SupplierHeader[0]/ContactBand[3]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 30.5, + "computedY" : 699.29, + "placementX" : 30.5, + "placementY" : 699.29, + "placementWidth" : 9.2, + "placementHeight" : 9.2, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 9.2, + "contentHeight" : 9.2, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/Masthead[0]/SupplierHeader[0]/ContactBand[3]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "ConsultingInvoice[0]/Masthead[0]/SupplierHeader[0]/ContactBand[3]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 53.5, + "computedY" : 699.102, + "placementX" : 53.5, + "placementY" : 699.102, + "placementWidth" : 86.464, + "placementHeight" : 9.576, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 86.464, + "contentHeight" : 9.576, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/Masthead[0]/SupplierHeader[0]/ContactBand[4]", + "entityName" : "ContactBand", + "entityKind" : "ShapeContainerNode", + "parentPath" : "ConsultingInvoice[0]/Masthead[0]/SupplierHeader[0]", + "childIndex" : 4, + "depth" : 4, + "layer" : 4, + "computedX" : 30.5, + "computedY" : 682.39, + "placementX" : 30.5, + "placementY" : 682.39, + "placementWidth" : 259.731, + "placementHeight" : 13.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 259.731, + "contentHeight" : 13.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/Masthead[0]/SupplierHeader[0]/ContactBand[4]/Icon-website[0]", + "entityName" : "Icon-website", + "entityKind" : "ImageNode", + "parentPath" : "ConsultingInvoice[0]/Masthead[0]/SupplierHeader[0]/ContactBand[4]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 30.5, + "computedY" : 684.19, + "placementX" : 30.5, + "placementY" : 684.19, + "placementWidth" : 9.4, + "placementHeight" : 9.4, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 9.4, + "contentHeight" : 9.4, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/Masthead[0]/SupplierHeader[0]/ContactBand[4]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "ConsultingInvoice[0]/Masthead[0]/SupplierHeader[0]/ContactBand[4]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 53.5, + "computedY" : 684.102, + "placementX" : 53.5, + "placementY" : 684.102, + "placementWidth" : 63.174, + "placementHeight" : 9.576, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 63.174, + "contentHeight" : 9.576, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/Masthead[0]/InvoiceHeader[1]", + "entityName" : "InvoiceHeader", + "entityKind" : "SectionNode", + "parentPath" : "ConsultingInvoice[0]/Masthead[0]", + "childIndex" : 1, + "depth" : 3, + "layer" : 3, + "computedX" : 315.231, + "computedY" : 675.406, + "placementX" : 315.231, + "placementY" : 675.406, + "placementWidth" : 249.545, + "placementHeight" : 135.984, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 249.545, + "contentHeight" : 135.984, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 24.0 + } + }, { + "path" : "ConsultingInvoice[0]/Masthead[0]/InvoiceHeader[1]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "ConsultingInvoice[0]/Masthead[0]/InvoiceHeader[1]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 339.231, + "computedY" : 778.406, + "placementX" : 339.231, + "placementY" : 778.406, + "placementWidth" : 128.001, + "placementHeight" : 32.984, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 128.001, + "contentHeight" : 32.984, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 8.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/Masthead[0]/InvoiceHeader[1]/MetadataBand[1]", + "entityName" : "MetadataBand", + "entityKind" : "ShapeContainerNode", + "parentPath" : "ConsultingInvoice[0]/Masthead[0]/InvoiceHeader[1]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 339.231, + "computedY" : 751.406, + "placementX" : 339.231, + "placementY" : 751.406, + "placementWidth" : 225.545, + "placementHeight" : 18.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 225.545, + "contentHeight" : 18.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/Masthead[0]/InvoiceHeader[1]/MetadataBand[1]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "ConsultingInvoice[0]/Masthead[0]/InvoiceHeader[1]/MetadataBand[1]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 339.231, + "computedY" : 755.618, + "placementX" : 339.231, + "placementY" : 755.618, + "placementWidth" : 57.812, + "placementHeight" : 9.576, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 57.812, + "contentHeight" : 9.576, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/Masthead[0]/InvoiceHeader[1]/MetadataBand[1]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "ConsultingInvoice[0]/Masthead[0]/InvoiceHeader[1]/MetadataBand[1]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 447.492, + "computedY" : 755.086, + "placementX" : 447.492, + "placementY" : 755.086, + "placementWidth" : 57.509, + "placementHeight" : 10.64, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 57.509, + "contentHeight" : 10.64, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/Masthead[0]/InvoiceHeader[1]/MetadataBand[2]", + "entityName" : "MetadataBand", + "entityKind" : "ShapeContainerNode", + "parentPath" : "ConsultingInvoice[0]/Masthead[0]/InvoiceHeader[1]", + "childIndex" : 2, + "depth" : 4, + "layer" : 4, + "computedX" : 339.231, + "computedY" : 732.406, + "placementX" : 339.231, + "placementY" : 732.406, + "placementWidth" : 225.545, + "placementHeight" : 18.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 225.545, + "contentHeight" : 18.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/Masthead[0]/InvoiceHeader[1]/MetadataBand[2]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "ConsultingInvoice[0]/Masthead[0]/InvoiceHeader[1]/MetadataBand[2]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 339.231, + "computedY" : 736.618, + "placementX" : 339.231, + "placementY" : 736.618, + "placementWidth" : 38.475, + "placementHeight" : 9.576, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 38.475, + "contentHeight" : 9.576, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/Masthead[0]/InvoiceHeader[1]/MetadataBand[2]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "ConsultingInvoice[0]/Masthead[0]/InvoiceHeader[1]/MetadataBand[2]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 447.492, + "computedY" : 736.086, + "placementX" : 447.492, + "placementY" : 736.086, + "placementWidth" : 47.5, + "placementHeight" : 10.64, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 47.5, + "contentHeight" : 10.64, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/Masthead[0]/InvoiceHeader[1]/MetadataBand[3]", + "entityName" : "MetadataBand", + "entityKind" : "ShapeContainerNode", + "parentPath" : "ConsultingInvoice[0]/Masthead[0]/InvoiceHeader[1]", + "childIndex" : 3, + "depth" : 4, + "layer" : 4, + "computedX" : 339.231, + "computedY" : 713.406, + "placementX" : 339.231, + "placementY" : 713.406, + "placementWidth" : 225.545, + "placementHeight" : 18.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 225.545, + "contentHeight" : 18.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/Masthead[0]/InvoiceHeader[1]/MetadataBand[3]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "ConsultingInvoice[0]/Masthead[0]/InvoiceHeader[1]/MetadataBand[3]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 339.231, + "computedY" : 717.618, + "placementX" : 339.231, + "placementY" : 717.618, + "placementWidth" : 33.796, + "placementHeight" : 9.576, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 33.796, + "contentHeight" : 9.576, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/Masthead[0]/InvoiceHeader[1]/MetadataBand[3]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "ConsultingInvoice[0]/Masthead[0]/InvoiceHeader[1]/MetadataBand[3]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 447.492, + "computedY" : 717.086, + "placementX" : 447.492, + "placementY" : 717.086, + "placementWidth" : 50.396, + "placementHeight" : 10.64, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 50.396, + "contentHeight" : 10.64, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/Masthead[0]/InvoiceHeader[1]/MetadataBand[4]", + "entityName" : "MetadataBand", + "entityKind" : "ShapeContainerNode", + "parentPath" : "ConsultingInvoice[0]/Masthead[0]/InvoiceHeader[1]", + "childIndex" : 4, + "depth" : 4, + "layer" : 4, + "computedX" : 339.231, + "computedY" : 694.406, + "placementX" : 339.231, + "placementY" : 694.406, + "placementWidth" : 225.545, + "placementHeight" : 18.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 225.545, + "contentHeight" : 18.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/Masthead[0]/InvoiceHeader[1]/MetadataBand[4]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "ConsultingInvoice[0]/Masthead[0]/InvoiceHeader[1]/MetadataBand[4]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 339.231, + "computedY" : 698.618, + "placementX" : 339.231, + "placementY" : 698.618, + "placementWidth" : 26.635, + "placementHeight" : 9.576, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 26.635, + "contentHeight" : 9.576, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/Masthead[0]/InvoiceHeader[1]/MetadataBand[4]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "ConsultingInvoice[0]/Masthead[0]/InvoiceHeader[1]/MetadataBand[4]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 447.492, + "computedY" : 698.086, + "placementX" : 447.492, + "placementY" : 698.086, + "placementWidth" : 109.235, + "placementHeight" : 10.64, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 109.235, + "contentHeight" : 10.64, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/Masthead[0]/InvoiceHeader[1]/MetadataBand[5]", + "entityName" : "MetadataBand", + "entityKind" : "ShapeContainerNode", + "parentPath" : "ConsultingInvoice[0]/Masthead[0]/InvoiceHeader[1]", + "childIndex" : 5, + "depth" : 4, + "layer" : 4, + "computedX" : 339.231, + "computedY" : 675.406, + "placementX" : 339.231, + "placementY" : 675.406, + "placementWidth" : 225.545, + "placementHeight" : 18.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 225.545, + "contentHeight" : 18.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/Masthead[0]/InvoiceHeader[1]/MetadataBand[5]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "ConsultingInvoice[0]/Masthead[0]/InvoiceHeader[1]/MetadataBand[5]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 339.231, + "computedY" : 679.618, + "placementX" : 339.231, + "placementY" : 679.618, + "placementWidth" : 41.82, + "placementHeight" : 9.576, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 41.82, + "contentHeight" : 9.576, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/Masthead[0]/InvoiceHeader[1]/MetadataBand[5]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "ConsultingInvoice[0]/Masthead[0]/InvoiceHeader[1]/MetadataBand[5]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 447.492, + "computedY" : 679.086, + "placementX" : 447.492, + "placementY" : 679.086, + "placementWidth" : 32.665, + "placementHeight" : 10.64, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 32.665, + "contentHeight" : 10.64, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/SpacerNode[1]", + "entityName" : null, + "entityKind" : "SpacerNode", + "parentPath" : "ConsultingInvoice[0]", + "childIndex" : 1, + "depth" : 2, + "layer" : 2, + "computedX" : 30.5, + "computedY" : 659.406, + "placementX" : 30.5, + "placementY" : 659.406, + "placementWidth" : 0.0, + "placementHeight" : 16.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 0.0, + "contentHeight" : 16.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/PartiesAndSummary[2]", + "entityName" : "PartiesAndSummary", + "entityKind" : "RowNode", + "parentPath" : "ConsultingInvoice[0]", + "childIndex" : 2, + "depth" : 2, + "layer" : 2, + "computedX" : 30.5, + "computedY" : 561.63, + "placementX" : 30.5, + "placementY" : 561.63, + "placementWidth" : 534.276, + "placementHeight" : 97.776, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 534.276, + "contentHeight" : 97.776, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/PartiesAndSummary[2]/BilledTo[0]", + "entityName" : "BilledTo", + "entityKind" : "SectionNode", + "parentPath" : "ConsultingInvoice[0]/PartiesAndSummary[2]", + "childIndex" : 0, + "depth" : 3, + "layer" : 3, + "computedX" : 30.5, + "computedY" : 561.63, + "placementX" : 30.5, + "placementY" : 561.63, + "placementWidth" : 116.538, + "placementHeight" : 97.776, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 116.538, + "contentHeight" : 97.776, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/PartiesAndSummary[2]/BilledTo[0]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "ConsultingInvoice[0]/PartiesAndSummary[2]/BilledTo[0]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 30.5, + "computedY" : 647.17, + "placementX" : 30.5, + "placementY" : 647.17, + "placementWidth" : 56.469, + "placementHeight" : 12.236, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 56.469, + "contentHeight" : 12.236, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/PartiesAndSummary[2]/BilledTo[0]/LineNode[1]", + "entityName" : null, + "entityKind" : "LineNode", + "parentPath" : "ConsultingInvoice[0]/PartiesAndSummary[2]/BilledTo[0]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 30.5, + "computedY" : 640.47, + "placementX" : 30.5, + "placementY" : 640.47, + "placementWidth" : 30.0, + "placementHeight" : 1.2, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 30.0, + "contentHeight" : 1.2, + "margin" : { + "top" : 4.0, + "right" : 0.0, + "bottom" : 4.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/PartiesAndSummary[2]/BilledTo[0]/ParagraphNode[2]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "ConsultingInvoice[0]/PartiesAndSummary[2]/BilledTo[0]", + "childIndex" : 2, + "depth" : 4, + "layer" : 4, + "computedX" : 30.5, + "computedY" : 624.33, + "placementX" : 30.5, + "placementY" : 624.33, + "placementWidth" : 97.219, + "placementHeight" : 10.64, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 97.219, + "contentHeight" : 10.64, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/PartiesAndSummary[2]/BilledTo[0]/ParagraphNode[3]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "ConsultingInvoice[0]/PartiesAndSummary[2]/BilledTo[0]", + "childIndex" : 3, + "depth" : 4, + "layer" : 4, + "computedX" : 30.5, + "computedY" : 612.19, + "placementX" : 30.5, + "placementY" : 612.19, + "placementWidth" : 116.538, + "placementHeight" : 10.64, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 116.538, + "contentHeight" : 10.64, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/PartiesAndSummary[2]/BilledTo[0]/ParagraphNode[4]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "ConsultingInvoice[0]/PartiesAndSummary[2]/BilledTo[0]", + "childIndex" : 4, + "depth" : 4, + "layer" : 4, + "computedX" : 30.5, + "computedY" : 578.77, + "placementX" : 30.5, + "placementY" : 578.77, + "placementWidth" : 76.129, + "placementHeight" : 31.92, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 76.129, + "contentHeight" : 31.92, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/PartiesAndSummary[2]/BilledTo[0]/ParagraphNode[5]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "ConsultingInvoice[0]/PartiesAndSummary[2]/BilledTo[0]", + "childIndex" : 5, + "depth" : 4, + "layer" : 4, + "computedX" : 30.5, + "computedY" : 561.63, + "placementX" : 30.5, + "placementY" : 561.63, + "placementWidth" : 114.008, + "placementHeight" : 10.64, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 114.008, + "contentHeight" : 10.64, + "margin" : { + "top" : 5.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/PartiesAndSummary[2]/InvoiceSummary[1]", + "entityName" : "InvoiceSummary", + "entityKind" : "SectionNode", + "parentPath" : "ConsultingInvoice[0]/PartiesAndSummary[2]", + "childIndex" : 1, + "depth" : 3, + "layer" : 3, + "computedX" : 340.102, + "computedY" : 601.7, + "placementX" : 340.102, + "placementY" : 601.7, + "placementWidth" : 205.246, + "placementHeight" : 57.706, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 205.246, + "contentHeight" : 57.706, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/PartiesAndSummary[2]/InvoiceSummary[1]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "ConsultingInvoice[0]/PartiesAndSummary[2]/InvoiceSummary[1]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 340.102, + "computedY" : 647.17, + "placementX" : 340.102, + "placementY" : 647.17, + "placementWidth" : 109.748, + "placementHeight" : 12.236, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 109.748, + "contentHeight" : 12.236, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/PartiesAndSummary[2]/InvoiceSummary[1]/LineNode[1]", + "entityName" : null, + "entityKind" : "LineNode", + "parentPath" : "ConsultingInvoice[0]/PartiesAndSummary[2]/InvoiceSummary[1]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 340.102, + "computedY" : 640.97, + "placementX" : 340.102, + "placementY" : 640.97, + "placementWidth" : 30.0, + "placementHeight" : 1.2, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 30.0, + "contentHeight" : 1.2, + "margin" : { + "top" : 4.0, + "right" : 0.0, + "bottom" : 4.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/PartiesAndSummary[2]/InvoiceSummary[1]/ParagraphNode[2]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "ConsultingInvoice[0]/PartiesAndSummary[2]/InvoiceSummary[1]", + "childIndex" : 2, + "depth" : 4, + "layer" : 4, + "computedX" : 340.102, + "computedY" : 613.34, + "placementX" : 340.102, + "placementY" : 613.34, + "placementWidth" : 205.246, + "placementHeight" : 22.63, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 205.246, + "contentHeight" : 22.63, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/PartiesAndSummary[2]/InvoiceSummary[1]/ParagraphNode[3]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "ConsultingInvoice[0]/PartiesAndSummary[2]/InvoiceSummary[1]", + "childIndex" : 3, + "depth" : 4, + "layer" : 4, + "computedX" : 340.102, + "computedY" : 601.7, + "placementX" : 340.102, + "placementY" : 601.7, + "placementWidth" : 100.259, + "placementHeight" : 10.64, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 100.259, + "contentHeight" : 10.64, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/SpacerNode[3]", + "entityName" : null, + "entityKind" : "SpacerNode", + "parentPath" : "ConsultingInvoice[0]", + "childIndex" : 3, + "depth" : 2, + "layer" : 2, + "computedX" : 30.5, + "computedY" : 551.63, + "placementX" : 30.5, + "placementY" : 551.63, + "placementWidth" : 0.0, + "placementHeight" : 10.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 0.0, + "contentHeight" : 10.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/LineItems[4]", + "entityName" : "LineItems", + "entityKind" : "TableNode", + "parentPath" : "ConsultingInvoice[0]", + "childIndex" : 4, + "depth" : 2, + "layer" : 2, + "computedX" : 30.5, + "computedY" : 60.028, + "placementX" : 30.5, + "placementY" : 60.028, + "placementWidth" : 534.276, + "placementHeight" : 830.732, + "startPage" : 0, + "endPage" : 1, + "contentWidth" : 534.276, + "contentHeight" : 830.732, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/Totals[5]", + "entityName" : "Totals", + "entityKind" : "SectionNode", + "parentPath" : "ConsultingInvoice[0]", + "childIndex" : 5, + "depth" : 2, + "layer" : 2, + "computedX" : 340.38, + "computedY" : 374.108, + "placementX" : 340.38, + "placementY" : 374.108, + "placementWidth" : 224.396, + "placementHeight" : 63.0, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 224.396, + "contentHeight" : 63.0, + "margin" : { + "top" : 6.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 309.88 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/Totals[5]/TotalRow[0]", + "entityName" : "TotalRow", + "entityKind" : "ShapeContainerNode", + "parentPath" : "ConsultingInvoice[0]/Totals[5]", + "childIndex" : 0, + "depth" : 3, + "layer" : 3, + "computedX" : 340.38, + "computedY" : 422.108, + "placementX" : 340.38, + "placementY" : 422.108, + "placementWidth" : 224.396, + "placementHeight" : 15.0, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 224.396, + "contentHeight" : 15.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/Totals[5]/TotalRow[0]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "ConsultingInvoice[0]/Totals[5]/TotalRow[0]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 340.38, + "computedY" : 424.82, + "placementX" : 340.38, + "placementY" : 424.82, + "placementWidth" : 48.301, + "placementHeight" : 9.576, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 48.301, + "contentHeight" : 9.576, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 13.0 + } + }, { + "path" : "ConsultingInvoice[0]/Totals[5]/TotalRow[0]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "ConsultingInvoice[0]/Totals[5]/TotalRow[0]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 340.38, + "computedY" : 424.288, + "placementX" : 340.38, + "placementY" : 424.288, + "placementWidth" : 224.396, + "placementHeight" : 10.64, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 224.396, + "contentHeight" : 10.64, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 10.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/Totals[5]/TotalRow[1]", + "entityName" : "TotalRow", + "entityKind" : "ShapeContainerNode", + "parentPath" : "ConsultingInvoice[0]/Totals[5]", + "childIndex" : 1, + "depth" : 3, + "layer" : 3, + "computedX" : 340.38, + "computedY" : 406.108, + "placementX" : 340.38, + "placementY" : 406.108, + "placementWidth" : 224.396, + "placementHeight" : 15.0, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 224.396, + "contentHeight" : 15.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/Totals[5]/TotalRow[1]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "ConsultingInvoice[0]/Totals[5]/TotalRow[1]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 340.38, + "computedY" : 408.82, + "placementX" : 340.38, + "placementY" : 408.82, + "placementWidth" : 47.419, + "placementHeight" : 9.576, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 47.419, + "contentHeight" : 9.576, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 13.0 + } + }, { + "path" : "ConsultingInvoice[0]/Totals[5]/TotalRow[1]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "ConsultingInvoice[0]/Totals[5]/TotalRow[1]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 340.38, + "computedY" : 408.288, + "placementX" : 340.38, + "placementY" : 408.288, + "placementWidth" : 224.396, + "placementHeight" : 10.64, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 224.396, + "contentHeight" : 10.64, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 10.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/Totals[5]/LineNode[2]", + "entityName" : null, + "entityKind" : "LineNode", + "parentPath" : "ConsultingInvoice[0]/Totals[5]", + "childIndex" : 2, + "depth" : 3, + "layer" : 3, + "computedX" : 340.38, + "computedY" : 402.108, + "placementX" : 340.38, + "placementY" : 402.108, + "placementWidth" : 224.396, + "placementHeight" : 1.0, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 224.396, + "contentHeight" : 1.0, + "margin" : { + "top" : 2.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/Totals[5]/TotalDue[3]", + "entityName" : "TotalDue", + "entityKind" : "ShapeContainerNode", + "parentPath" : "ConsultingInvoice[0]/Totals[5]", + "childIndex" : 3, + "depth" : 3, + "layer" : 3, + "computedX" : 340.38, + "computedY" : 374.108, + "placementX" : 340.38, + "placementY" : 374.108, + "placementWidth" : 224.396, + "placementHeight" : 27.0, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 224.396, + "contentHeight" : 27.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/Totals[5]/TotalDue[3]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "ConsultingInvoice[0]/Totals[5]/TotalDue[3]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 340.38, + "computedY" : 382.288, + "placementX" : 340.38, + "placementY" : 382.288, + "placementWidth" : 79.135, + "placementHeight" : 10.64, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 79.135, + "contentHeight" : 10.64, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 13.0 + } + }, { + "path" : "ConsultingInvoice[0]/Totals[5]/TotalDue[3]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "ConsultingInvoice[0]/Totals[5]/TotalDue[3]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 340.38, + "computedY" : 378.032, + "placementX" : 340.38, + "placementY" : 378.032, + "placementWidth" : 224.396, + "placementHeight" : 19.152, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 224.396, + "contentHeight" : 19.152, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 10.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/LineNode[6]", + "entityName" : null, + "entityKind" : "LineNode", + "parentPath" : "ConsultingInvoice[0]", + "childIndex" : 6, + "depth" : 2, + "layer" : 2, + "computedX" : 30.5, + "computedY" : 368.108, + "placementX" : 30.5, + "placementY" : 368.108, + "placementWidth" : 534.276, + "placementHeight" : 1.0, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 534.276, + "contentHeight" : 1.0, + "margin" : { + "top" : 5.0, + "right" : 0.0, + "bottom" : 5.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/PaymentAndNotes[7]", + "entityName" : "PaymentAndNotes", + "entityKind" : "RowNode", + "parentPath" : "ConsultingInvoice[0]", + "childIndex" : 7, + "depth" : 2, + "layer" : 2, + "computedX" : 30.5, + "computedY" : 244.096, + "placementX" : 30.5, + "placementY" : 244.096, + "placementWidth" : 534.276, + "placementHeight" : 119.012, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 534.276, + "contentHeight" : 119.012, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/PaymentAndNotes[7]/PaymentInformation[0]", + "entityName" : "PaymentInformation", + "entityKind" : "SectionNode", + "parentPath" : "ConsultingInvoice[0]/PaymentAndNotes[7]", + "childIndex" : 0, + "depth" : 3, + "layer" : 3, + "computedX" : 30.5, + "computedY" : 244.096, + "placementX" : 30.5, + "placementY" : 244.096, + "placementWidth" : 276.629, + "placementHeight" : 119.012, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 276.629, + "contentHeight" : 119.012, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/PaymentAndNotes[7]/PaymentInformation[0]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "ConsultingInvoice[0]/PaymentAndNotes[7]/PaymentInformation[0]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 30.5, + "computedY" : 350.872, + "placementX" : 30.5, + "placementY" : 350.872, + "placementWidth" : 139.604, + "placementHeight" : 12.236, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 139.604, + "contentHeight" : 12.236, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/PaymentAndNotes[7]/PaymentInformation[0]/LineNode[1]", + "entityName" : null, + "entityKind" : "LineNode", + "parentPath" : "ConsultingInvoice[0]/PaymentAndNotes[7]/PaymentInformation[0]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 30.5, + "computedY" : 342.672, + "placementX" : 30.5, + "placementY" : 342.672, + "placementWidth" : 30.0, + "placementHeight" : 1.2, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 30.0, + "contentHeight" : 1.2, + "margin" : { + "top" : 4.0, + "right" : 0.0, + "bottom" : 4.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/PaymentAndNotes[7]/PaymentInformation[0]/BankInformationBand[2]", + "entityName" : "BankInformationBand", + "entityKind" : "ShapeContainerNode", + "parentPath" : "ConsultingInvoice[0]/PaymentAndNotes[7]/PaymentInformation[0]", + "childIndex" : 2, + "depth" : 4, + "layer" : 4, + "computedX" : 30.5, + "computedY" : 260.672, + "placementX" : 30.5, + "placementY" : 260.672, + "placementWidth" : 276.629, + "placementHeight" : 75.0, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 276.629, + "contentHeight" : 75.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/PaymentAndNotes[7]/PaymentInformation[0]/BankInformationBand[2]/BankBadge[0]", + "entityName" : "BankBadge", + "entityKind" : "ShapeContainerNode", + "parentPath" : "ConsultingInvoice[0]/PaymentAndNotes[7]/PaymentInformation[0]/BankInformationBand[2]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 30.5, + "computedY" : 279.172, + "placementX" : 30.5, + "placementY" : 279.172, + "placementWidth" : 38.0, + "placementHeight" : 38.0, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 38.0, + "contentHeight" : 38.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/PaymentAndNotes[7]/PaymentInformation[0]/BankInformationBand[2]/BankBadge[0]/Icon-bank[0]", + "entityName" : "Icon-bank", + "entityKind" : "ImageNode", + "parentPath" : "ConsultingInvoice[0]/PaymentAndNotes[7]/PaymentInformation[0]/BankInformationBand[2]/BankBadge[0]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 39.75, + "computedY" : 288.422, + "placementX" : 39.75, + "placementY" : 288.422, + "placementWidth" : 19.5, + "placementHeight" : 19.5, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 19.5, + "contentHeight" : 19.5, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/PaymentAndNotes[7]/PaymentInformation[0]/BankInformationBand[2]/BankDetails[1]", + "entityName" : "BankDetails", + "entityKind" : "SectionNode", + "parentPath" : "ConsultingInvoice[0]/PaymentAndNotes[7]/PaymentInformation[0]/BankInformationBand[2]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 88.5, + "computedY" : 260.672, + "placementX" : 88.5, + "placementY" : 260.672, + "placementWidth" : 215.629, + "placementHeight" : 75.0, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 215.629, + "contentHeight" : 75.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/PaymentAndNotes[7]/PaymentInformation[0]/BankInformationBand[2]/BankDetails[1]/BankField[0]", + "entityName" : "BankField", + "entityKind" : "ShapeContainerNode", + "parentPath" : "ConsultingInvoice[0]/PaymentAndNotes[7]/PaymentInformation[0]/BankInformationBand[2]/BankDetails[1]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 88.5, + "computedY" : 320.672, + "placementX" : 88.5, + "placementY" : 320.672, + "placementWidth" : 215.629, + "placementHeight" : 15.0, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 215.629, + "contentHeight" : 15.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/PaymentAndNotes[7]/PaymentInformation[0]/BankInformationBand[2]/BankDetails[1]/BankField[0]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "ConsultingInvoice[0]/PaymentAndNotes[7]/PaymentInformation[0]/BankInformationBand[2]/BankDetails[1]/BankField[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 88.5, + "computedY" : 323.384, + "placementX" : 88.5, + "placementY" : 323.384, + "placementWidth" : 42.627, + "placementHeight" : 9.576, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 42.627, + "contentHeight" : 9.576, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/PaymentAndNotes[7]/PaymentInformation[0]/BankInformationBand[2]/BankDetails[1]/BankField[0]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "ConsultingInvoice[0]/PaymentAndNotes[7]/PaymentInformation[0]/BankInformationBand[2]/BankDetails[1]/BankField[0]", + "childIndex" : 1, + "depth" : 7, + "layer" : 7, + "computedX" : 164.5, + "computedY" : 323.384, + "placementX" : 164.5, + "placementY" : 323.384, + "placementWidth" : 47.545, + "placementHeight" : 9.576, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 47.545, + "contentHeight" : 9.576, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/PaymentAndNotes[7]/PaymentInformation[0]/BankInformationBand[2]/BankDetails[1]/BankField[1]", + "entityName" : "BankField", + "entityKind" : "ShapeContainerNode", + "parentPath" : "ConsultingInvoice[0]/PaymentAndNotes[7]/PaymentInformation[0]/BankInformationBand[2]/BankDetails[1]", + "childIndex" : 1, + "depth" : 6, + "layer" : 6, + "computedX" : 88.5, + "computedY" : 305.672, + "placementX" : 88.5, + "placementY" : 305.672, + "placementWidth" : 215.629, + "placementHeight" : 15.0, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 215.629, + "contentHeight" : 15.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/PaymentAndNotes[7]/PaymentInformation[0]/BankInformationBand[2]/BankDetails[1]/BankField[1]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "ConsultingInvoice[0]/PaymentAndNotes[7]/PaymentInformation[0]/BankInformationBand[2]/BankDetails[1]/BankField[1]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 88.5, + "computedY" : 308.384, + "placementX" : 88.5, + "placementY" : 308.384, + "placementWidth" : 54.31, + "placementHeight" : 9.576, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 54.31, + "contentHeight" : 9.576, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/PaymentAndNotes[7]/PaymentInformation[0]/BankInformationBand[2]/BankDetails[1]/BankField[1]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "ConsultingInvoice[0]/PaymentAndNotes[7]/PaymentInformation[0]/BankInformationBand[2]/BankDetails[1]/BankField[1]", + "childIndex" : 1, + "depth" : 7, + "layer" : 7, + "computedX" : 164.5, + "computedY" : 308.384, + "placementX" : 164.5, + "placementY" : 308.384, + "placementWidth" : 98.845, + "placementHeight" : 9.576, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 98.845, + "contentHeight" : 9.576, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/PaymentAndNotes[7]/PaymentInformation[0]/BankInformationBand[2]/BankDetails[1]/BankField[2]", + "entityName" : "BankField", + "entityKind" : "ShapeContainerNode", + "parentPath" : "ConsultingInvoice[0]/PaymentAndNotes[7]/PaymentInformation[0]/BankInformationBand[2]/BankDetails[1]", + "childIndex" : 2, + "depth" : 6, + "layer" : 6, + "computedX" : 88.5, + "computedY" : 290.672, + "placementX" : 88.5, + "placementY" : 290.672, + "placementWidth" : 215.629, + "placementHeight" : 15.0, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 215.629, + "contentHeight" : 15.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/PaymentAndNotes[7]/PaymentInformation[0]/BankInformationBand[2]/BankDetails[1]/BankField[2]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "ConsultingInvoice[0]/PaymentAndNotes[7]/PaymentInformation[0]/BankInformationBand[2]/BankDetails[1]/BankField[2]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 88.5, + "computedY" : 293.384, + "placementX" : 88.5, + "placementY" : 293.384, + "placementWidth" : 15.164, + "placementHeight" : 9.576, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 15.164, + "contentHeight" : 9.576, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/PaymentAndNotes[7]/PaymentInformation[0]/BankInformationBand[2]/BankDetails[1]/BankField[2]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "ConsultingInvoice[0]/PaymentAndNotes[7]/PaymentInformation[0]/BankInformationBand[2]/BankDetails[1]/BankField[2]", + "childIndex" : 1, + "depth" : 7, + "layer" : 7, + "computedX" : 164.5, + "computedY" : 293.384, + "placementX" : 164.5, + "placementY" : 293.384, + "placementWidth" : 26.861, + "placementHeight" : 9.576, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 26.861, + "contentHeight" : 9.576, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/PaymentAndNotes[7]/PaymentInformation[0]/BankInformationBand[2]/BankDetails[1]/BankField[3]", + "entityName" : "BankField", + "entityKind" : "ShapeContainerNode", + "parentPath" : "ConsultingInvoice[0]/PaymentAndNotes[7]/PaymentInformation[0]/BankInformationBand[2]/BankDetails[1]", + "childIndex" : 3, + "depth" : 6, + "layer" : 6, + "computedX" : 88.5, + "computedY" : 275.672, + "placementX" : 88.5, + "placementY" : 275.672, + "placementWidth" : 215.629, + "placementHeight" : 15.0, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 215.629, + "contentHeight" : 15.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/PaymentAndNotes[7]/PaymentInformation[0]/BankInformationBand[2]/BankDetails[1]/BankField[3]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "ConsultingInvoice[0]/PaymentAndNotes[7]/PaymentInformation[0]/BankInformationBand[2]/BankDetails[1]/BankField[3]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 88.5, + "computedY" : 278.384, + "placementX" : 88.5, + "placementY" : 278.384, + "placementWidth" : 61.847, + "placementHeight" : 9.576, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 61.847, + "contentHeight" : 9.576, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/PaymentAndNotes[7]/PaymentInformation[0]/BankInformationBand[2]/BankDetails[1]/BankField[3]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "ConsultingInvoice[0]/PaymentAndNotes[7]/PaymentInformation[0]/BankInformationBand[2]/BankDetails[1]/BankField[3]", + "childIndex" : 1, + "depth" : 7, + "layer" : 7, + "computedX" : 164.5, + "computedY" : 278.384, + "placementX" : 164.5, + "placementY" : 278.384, + "placementWidth" : 31.143, + "placementHeight" : 9.576, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 31.143, + "contentHeight" : 9.576, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/PaymentAndNotes[7]/PaymentInformation[0]/BankInformationBand[2]/BankDetails[1]/BankField[4]", + "entityName" : "BankField", + "entityKind" : "ShapeContainerNode", + "parentPath" : "ConsultingInvoice[0]/PaymentAndNotes[7]/PaymentInformation[0]/BankInformationBand[2]/BankDetails[1]", + "childIndex" : 4, + "depth" : 6, + "layer" : 6, + "computedX" : 88.5, + "computedY" : 260.672, + "placementX" : 88.5, + "placementY" : 260.672, + "placementWidth" : 215.629, + "placementHeight" : 15.0, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 215.629, + "contentHeight" : 15.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/PaymentAndNotes[7]/PaymentInformation[0]/BankInformationBand[2]/BankDetails[1]/BankField[4]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "ConsultingInvoice[0]/PaymentAndNotes[7]/PaymentInformation[0]/BankInformationBand[2]/BankDetails[1]/BankField[4]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 88.5, + "computedY" : 263.384, + "placementX" : 88.5, + "placementY" : 263.384, + "placementWidth" : 37.394, + "placementHeight" : 9.576, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 37.394, + "contentHeight" : 9.576, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/PaymentAndNotes[7]/PaymentInformation[0]/BankInformationBand[2]/BankDetails[1]/BankField[4]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "ConsultingInvoice[0]/PaymentAndNotes[7]/PaymentInformation[0]/BankInformationBand[2]/BankDetails[1]/BankField[4]", + "childIndex" : 1, + "depth" : 7, + "layer" : 7, + "computedX" : 164.5, + "computedY" : 263.384, + "placementX" : 164.5, + "placementY" : 263.384, + "placementWidth" : 51.758, + "placementHeight" : 9.576, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 51.758, + "contentHeight" : 9.576, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/PaymentAndNotes[7]/PaymentInformation[0]/ParagraphNode[3]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "ConsultingInvoice[0]/PaymentAndNotes[7]/PaymentInformation[0]", + "childIndex" : 3, + "depth" : 4, + "layer" : 4, + "computedX" : 30.5, + "computedY" : 244.096, + "placementX" : 30.5, + "placementY" : 244.096, + "placementWidth" : 246.452, + "placementHeight" : 9.576, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 246.452, + "contentHeight" : 9.576, + "margin" : { + "top" : 4.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/PaymentAndNotes[7]/Notes[1]", + "entityName" : "Notes", + "entityKind" : "SectionNode", + "parentPath" : "ConsultingInvoice[0]/PaymentAndNotes[7]", + "childIndex" : 1, + "depth" : 3, + "layer" : 3, + "computedX" : 329.129, + "computedY" : 250.512, + "placementX" : 329.129, + "placementY" : 250.512, + "placementWidth" : 235.647, + "placementHeight" : 112.596, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 235.647, + "contentHeight" : 112.596, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 24.0 + } + }, { + "path" : "ConsultingInvoice[0]/PaymentAndNotes[7]/Notes[1]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "ConsultingInvoice[0]/PaymentAndNotes[7]/Notes[1]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 353.129, + "computedY" : 350.872, + "placementX" : 353.129, + "placementY" : 350.872, + "placementWidth" : 36.122, + "placementHeight" : 12.236, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 36.122, + "contentHeight" : 12.236, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/PaymentAndNotes[7]/Notes[1]/LineNode[1]", + "entityName" : null, + "entityKind" : "LineNode", + "parentPath" : "ConsultingInvoice[0]/PaymentAndNotes[7]/Notes[1]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 353.129, + "computedY" : 342.672, + "placementX" : 353.129, + "placementY" : 342.672, + "placementWidth" : 30.0, + "placementHeight" : 1.2, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 30.0, + "contentHeight" : 1.2, + "margin" : { + "top" : 4.0, + "right" : 0.0, + "bottom" : 4.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/PaymentAndNotes[7]/Notes[1]/ParagraphNode[2]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "ConsultingInvoice[0]/PaymentAndNotes[7]/Notes[1]", + "childIndex" : 2, + "depth" : 4, + "layer" : 4, + "computedX" : 353.129, + "computedY" : 325.032, + "placementX" : 353.129, + "placementY" : 325.032, + "placementWidth" : 107.0, + "placementHeight" : 10.64, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 107.0, + "contentHeight" : 10.64, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/PaymentAndNotes[7]/Notes[1]/ParagraphNode[3]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "ConsultingInvoice[0]/PaymentAndNotes[7]/Notes[1]", + "childIndex" : 3, + "depth" : 4, + "layer" : 4, + "computedX" : 353.129, + "computedY" : 287.512, + "placementX" : 353.129, + "placementY" : 287.512, + "placementWidth" : 210.11, + "placementHeight" : 34.52, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 210.11, + "contentHeight" : 34.52, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/PaymentAndNotes[7]/Notes[1]/PaymentTerms[4]", + "entityName" : "PaymentTerms", + "entityKind" : "ShapeContainerNode", + "parentPath" : "ConsultingInvoice[0]/PaymentAndNotes[7]/Notes[1]", + "childIndex" : 4, + "depth" : 4, + "layer" : 4, + "computedX" : 353.129, + "computedY" : 250.512, + "placementX" : 353.129, + "placementY" : 250.512, + "placementWidth" : 211.647, + "placementHeight" : 34.0, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 211.647, + "contentHeight" : 34.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/PaymentAndNotes[7]/Notes[1]/PaymentTerms[4]/CalendarBadge[0]", + "entityName" : "CalendarBadge", + "entityKind" : "ShapeContainerNode", + "parentPath" : "ConsultingInvoice[0]/PaymentAndNotes[7]/Notes[1]/PaymentTerms[4]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 361.129, + "computedY" : 256.012, + "placementX" : 361.129, + "placementY" : 256.012, + "placementWidth" : 23.0, + "placementHeight" : 23.0, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 23.0, + "contentHeight" : 23.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/PaymentAndNotes[7]/Notes[1]/PaymentTerms[4]/CalendarBadge[0]/Icon-payment-calendar[0]", + "entityName" : "Icon-payment-calendar", + "entityKind" : "ImageNode", + "parentPath" : "ConsultingInvoice[0]/PaymentAndNotes[7]/Notes[1]/PaymentTerms[4]/CalendarBadge[0]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 366.379, + "computedY" : 261.262, + "placementX" : 366.379, + "placementY" : 261.262, + "placementWidth" : 12.5, + "placementHeight" : 12.5, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 12.5, + "contentHeight" : 12.5, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ConsultingInvoice[0]/PaymentAndNotes[7]/Notes[1]/PaymentTerms[4]/DueNotice[1]", + "entityName" : "DueNotice", + "entityKind" : "ParagraphNode", + "parentPath" : "ConsultingInvoice[0]/PaymentAndNotes[7]/Notes[1]/PaymentTerms[4]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 395.129, + "computedY" : 263.469, + "placementX" : 395.129, + "placementY" : 263.469, + "placementWidth" : 150.049, + "placementHeight" : 8.086, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 150.049, + "contentHeight" : 8.086, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + } ] +} diff --git a/qa/src/test/resources/layout-snapshots/canonical-templates/invoice/luma_studio_invoice_layout.json b/qa/src/test/resources/layout-snapshots/canonical-templates/invoice/luma_studio_invoice_layout.json new file mode 100644 index 000000000..129d7aebb --- /dev/null +++ b/qa/src/test/resources/layout-snapshots/canonical-templates/invoice/luma_studio_invoice_layout.json @@ -0,0 +1,3677 @@ +{ + "formatVersion" : "2.0", + "canvas" : { + "pageWidth" : 595.276, + "pageHeight" : 841.89, + "innerWidth" : 402.871, + "innerHeight" : 732.911, + "margin" : { + "top" : 46.3, + "right" : 38.931, + "bottom" : 62.679, + "left" : 153.474 + } + }, + "totalPages" : 1, + "nodes" : [ { + "path" : "LumaStudioInvoice[0]", + "entityName" : "LumaStudioInvoice", + "entityKind" : "ContainerNode", + "parentPath" : null, + "childIndex" : 0, + "depth" : 1, + "layer" : 1, + "computedX" : 153.474, + "computedY" : 65.475, + "placementX" : 153.474, + "placementY" : 65.475, + "placementWidth" : 402.871, + "placementHeight" : 730.115, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 402.871, + "contentHeight" : 730.115, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Sidebar[0]", + "entityName" : "Sidebar", + "entityKind" : "ShapeContainerNode", + "parentPath" : "LumaStudioInvoice[0]", + "childIndex" : 0, + "depth" : 2, + "layer" : 2, + "computedX" : 0.0, + "computedY" : 841.49, + "placementX" : 0.0, + "placementY" : 841.49, + "placementWidth" : 132.032, + "placementHeight" : 0.4, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 132.032, + "contentHeight" : 0.4, + "margin" : { + "top" : -46.3, + "right" : 0.0, + "bottom" : 45.9, + "left" : -153.474 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Sidebar[0]/SidebarOrnament[0]", + "entityName" : "SidebarOrnament", + "entityKind" : "ShapeContainerNode", + "parentPath" : "LumaStudioInvoice[0]/Sidebar[0]", + "childIndex" : 0, + "depth" : 3, + "layer" : 3, + "computedX" : 0.0, + "computedY" : 0.0, + "placementX" : 0.0, + "placementY" : 0.0, + "placementWidth" : 132.032, + "placementHeight" : 634.667, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 132.032, + "contentHeight" : 634.667, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Sidebar[0]/SidebarOrnament[0]/OrnamentDisc[0]", + "entityName" : "OrnamentDisc", + "entityKind" : "EllipseNode", + "parentPath" : "LumaStudioInvoice[0]/Sidebar[0]/SidebarOrnament[0]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 17.489, + "computedY" : 527.868, + "placementX" : 17.489, + "placementY" : 527.868, + "placementWidth" : 205.382, + "placementHeight" : 205.382, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 205.382, + "contentHeight" : 205.382, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Sidebar[0]/SidebarOrnament[0]/OrnamentArch[1]", + "entityName" : "OrnamentArch", + "entityKind" : "ShapeNode", + "parentPath" : "LumaStudioInvoice[0]/Sidebar[0]/SidebarOrnament[0]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 15.799, + "computedY" : 0.0, + "placementX" : 15.799, + "placementY" : 0.0, + "placementWidth" : 116.234, + "placementHeight" : 236.588, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 116.234, + "contentHeight" : 236.588, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Sidebar[0]/SidebarOrnament[0]/SvgIcon[2]", + "entityName" : "SvgIcon", + "entityKind" : "LayerStackNode", + "parentPath" : "LumaStudioInvoice[0]/Sidebar[0]/SidebarOrnament[0]", + "childIndex" : 2, + "depth" : 4, + "layer" : 4, + "computedX" : 12.977, + "computedY" : 282.647, + "placementX" : 12.977, + "placementY" : 282.647, + "placementWidth" : 95.244, + "placementHeight" : 95.244, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 95.244, + "contentHeight" : 95.244, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Sidebar[0]/SidebarOrnament[0]/SvgIcon[2]/SvgLayer0[0]", + "entityName" : "SvgLayer0", + "entityKind" : "Path", + "parentPath" : "LumaStudioInvoice[0]/Sidebar[0]/SidebarOrnament[0]/SvgIcon[2]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 12.977, + "computedY" : 282.647, + "placementX" : 12.977, + "placementY" : 282.647, + "placementWidth" : 95.244, + "placementHeight" : 95.244, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 95.244, + "contentHeight" : 95.244, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Sidebar[0]/BrandBlock[1]", + "entityName" : "BrandBlock", + "entityKind" : "ShapeNode", + "parentPath" : "LumaStudioInvoice[0]/Sidebar[0]", + "childIndex" : 1, + "depth" : 3, + "layer" : 3, + "computedX" : 0.0, + "computedY" : 634.667, + "placementX" : 0.0, + "placementY" : 634.667, + "placementWidth" : 132.032, + "placementHeight" : 207.223, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 132.032, + "contentHeight" : 207.223, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Sidebar[0]/BrandLockup[2]", + "entityName" : "BrandLockup", + "entityKind" : "ShapeContainerNode", + "parentPath" : "LumaStudioInvoice[0]/Sidebar[0]", + "childIndex" : 2, + "depth" : 3, + "layer" : 3, + "computedX" : 0.0, + "computedY" : 634.667, + "placementX" : 0.0, + "placementY" : 634.667, + "placementWidth" : 132.032, + "placementHeight" : 207.223, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 132.032, + "contentHeight" : 207.223, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Sidebar[0]/BrandLockup[2]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "LumaStudioInvoice[0]/Sidebar[0]/BrandLockup[2]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 33.288, + "computedY" : 715.218, + "placementX" : 33.288, + "placementY" : 715.218, + "placementWidth" : 36.655, + "placementHeight" : 88.273, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 36.655, + "contentHeight" : 88.273, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Sidebar[0]/BrandLockup[2]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "LumaStudioInvoice[0]/Sidebar[0]/BrandLockup[2]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 33.288, + "computedY" : 705.462, + "placementX" : 33.288, + "placementY" : 705.462, + "placementWidth" : 68.585, + "placementHeight" : 47.212, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 68.585, + "contentHeight" : 47.212, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Sidebar[0]/BrandLockup[2]/LockupRule[2]", + "entityName" : "LockupRule", + "entityKind" : "LineNode", + "parentPath" : "LumaStudioInvoice[0]/Sidebar[0]/BrandLockup[2]", + "childIndex" : 2, + "depth" : 4, + "layer" : 4, + "computedX" : 33.288, + "computedY" : 704.671, + "placementX" : 33.288, + "placementY" : 704.671, + "placementWidth" : 20.311, + "placementHeight" : 1.7, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 20.311, + "contentHeight" : 1.7, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Sidebar[0]/BrandLockup[2]/Wordmark[3]", + "entityName" : "Wordmark", + "entityKind" : "ParagraphNode", + "parentPath" : "LumaStudioInvoice[0]/Sidebar[0]/BrandLockup[2]", + "childIndex" : 3, + "depth" : 4, + "layer" : 4, + "computedX" : 33.288, + "computedY" : 675.395, + "placementX" : 33.288, + "placementY" : 675.395, + "placementWidth" : 82.984, + "placementHeight" : 9.524, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 82.984, + "contentHeight" : 9.524, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Sidebar[0]/BrandLockup[2]/ParagraphNode[4]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "LumaStudioInvoice[0]/Sidebar[0]/BrandLockup[2]", + "childIndex" : 4, + "depth" : 4, + "layer" : 4, + "computedX" : 33.288, + "computedY" : 661.626, + "placementX" : 33.288, + "placementY" : 661.626, + "placementWidth" : 77.712, + "placementHeight" : 8.047, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 77.712, + "contentHeight" : 8.047, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Masthead[1]", + "entityName" : "Masthead", + "entityKind" : "RowNode", + "parentPath" : "LumaStudioInvoice[0]", + "childIndex" : 1, + "depth" : 2, + "layer" : 2, + "computedX" : 153.474, + "computedY" : 647.091, + "placementX" : 153.474, + "placementY" : 647.091, + "placementWidth" : 402.871, + "placementHeight" : 148.499, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 402.871, + "contentHeight" : 148.499, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Masthead[1]/SupplierHeader[0]", + "entityName" : "SupplierHeader", + "entityKind" : "SectionNode", + "parentPath" : "LumaStudioInvoice[0]/Masthead[1]", + "childIndex" : 0, + "depth" : 3, + "layer" : 3, + "computedX" : 153.474, + "computedY" : 655.01, + "placementX" : 153.474, + "placementY" : 655.01, + "placementWidth" : 236.404, + "placementHeight" : 140.58, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 236.404, + "contentHeight" : 140.58, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 7.334 + } + }, { + "path" : "LumaStudioInvoice[0]/Masthead[1]/SupplierHeader[0]/SupplierName[0]", + "entityName" : "SupplierName", + "entityKind" : "ParagraphNode", + "parentPath" : "LumaStudioInvoice[0]/Masthead[1]/SupplierHeader[0]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 160.808, + "computedY" : 782.164, + "placementX" : 160.808, + "placementY" : 782.164, + "placementWidth" : 94.638, + "placementHeight" : 13.425, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 94.638, + "contentHeight" : 13.425, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Masthead[1]/SupplierHeader[0]/SupplierAddress[1]", + "entityName" : "SupplierAddress", + "entityKind" : "ParagraphNode", + "parentPath" : "LumaStudioInvoice[0]/Masthead[1]/SupplierHeader[0]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 160.808, + "computedY" : 727.228, + "placementX" : 160.808, + "placementY" : 727.228, + "placementWidth" : 85.39, + "placementHeight" : 48.726, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 85.39, + "contentHeight" : 48.726, + "margin" : { + "top" : 6.21, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Masthead[1]/SupplierHeader[0]/ContactRows[2]", + "entityName" : "ContactRows", + "entityKind" : "SectionNode", + "parentPath" : "LumaStudioInvoice[0]/Masthead[1]/SupplierHeader[0]", + "childIndex" : 2, + "depth" : 4, + "layer" : 4, + "computedX" : 160.808, + "computedY" : 674.232, + "placementX" : 160.808, + "placementY" : 674.232, + "placementWidth" : 229.071, + "placementHeight" : 43.184, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 229.071, + "contentHeight" : 43.184, + "margin" : { + "top" : 9.812, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Masthead[1]/SupplierHeader[0]/ContactRows[2]/ContactRow[0]", + "entityName" : "ContactRow", + "entityKind" : "ShapeContainerNode", + "parentPath" : "LumaStudioInvoice[0]/Masthead[1]/SupplierHeader[0]/ContactRows[2]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 160.808, + "computedY" : 703.022, + "placementX" : 160.808, + "placementY" : 703.022, + "placementWidth" : 229.071, + "placementHeight" : 14.395, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 229.071, + "contentHeight" : 14.395, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Masthead[1]/SupplierHeader[0]/ContactRows[2]/ContactRow[0]/SvgIcon[0]", + "entityName" : "SvgIcon", + "entityKind" : "LayerStackNode", + "parentPath" : "LumaStudioInvoice[0]/Masthead[1]/SupplierHeader[0]/ContactRows[2]/ContactRow[0]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 160.808, + "computedY" : 705.919, + "placementX" : 160.808, + "placementY" : 705.919, + "placementWidth" : 8.6, + "placementHeight" : 8.6, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 8.6, + "contentHeight" : 8.6, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Masthead[1]/SupplierHeader[0]/ContactRows[2]/ContactRow[0]/SvgIcon[0]/SvgLayer0[0]", + "entityName" : "SvgLayer0", + "entityKind" : "Path", + "parentPath" : "LumaStudioInvoice[0]/Masthead[1]/SupplierHeader[0]/ContactRows[2]/ContactRow[0]/SvgIcon[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 160.808, + "computedY" : 705.919, + "placementX" : 160.808, + "placementY" : 705.919, + "placementWidth" : 8.6, + "placementHeight" : 8.6, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 8.6, + "contentHeight" : 8.6, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Masthead[1]/SupplierHeader[0]/ContactRows[2]/ContactRow[0]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "LumaStudioInvoice[0]/Masthead[1]/SupplierHeader[0]/ContactRows[2]/ContactRow[0]", + "childIndex" : 1, + "depth" : 6, + "layer" : 6, + "computedX" : 181.808, + "computedY" : 704.482, + "placementX" : 181.808, + "placementY" : 704.482, + "placementWidth" : 78.706, + "placementHeight" : 11.475, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 78.706, + "contentHeight" : 11.475, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Masthead[1]/SupplierHeader[0]/ContactRows[2]/ContactRow[1]", + "entityName" : "ContactRow", + "entityKind" : "ShapeContainerNode", + "parentPath" : "LumaStudioInvoice[0]/Masthead[1]/SupplierHeader[0]/ContactRows[2]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 160.808, + "computedY" : 688.627, + "placementX" : 160.808, + "placementY" : 688.627, + "placementWidth" : 229.071, + "placementHeight" : 14.395, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 229.071, + "contentHeight" : 14.395, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Masthead[1]/SupplierHeader[0]/ContactRows[2]/ContactRow[1]/SvgIcon[0]", + "entityName" : "SvgIcon", + "entityKind" : "LayerStackNode", + "parentPath" : "LumaStudioInvoice[0]/Masthead[1]/SupplierHeader[0]/ContactRows[2]/ContactRow[1]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 160.808, + "computedY" : 691.524, + "placementX" : 160.808, + "placementY" : 691.524, + "placementWidth" : 8.6, + "placementHeight" : 8.6, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 8.6, + "contentHeight" : 8.6, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Masthead[1]/SupplierHeader[0]/ContactRows[2]/ContactRow[1]/SvgIcon[0]/SvgLayer0[0]", + "entityName" : "SvgLayer0", + "entityKind" : "Path", + "parentPath" : "LumaStudioInvoice[0]/Masthead[1]/SupplierHeader[0]/ContactRows[2]/ContactRow[1]/SvgIcon[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 160.808, + "computedY" : 691.524, + "placementX" : 160.808, + "placementY" : 691.524, + "placementWidth" : 8.6, + "placementHeight" : 8.6, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 8.6, + "contentHeight" : 8.6, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Masthead[1]/SupplierHeader[0]/ContactRows[2]/ContactRow[1]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "LumaStudioInvoice[0]/Masthead[1]/SupplierHeader[0]/ContactRows[2]/ContactRow[1]", + "childIndex" : 1, + "depth" : 6, + "layer" : 6, + "computedX" : 181.808, + "computedY" : 690.087, + "placementX" : 181.808, + "placementY" : 690.087, + "placementWidth" : 95.852, + "placementHeight" : 11.475, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 95.852, + "contentHeight" : 11.475, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Masthead[1]/SupplierHeader[0]/ContactRows[2]/ContactRow[2]", + "entityName" : "ContactRow", + "entityKind" : "ShapeContainerNode", + "parentPath" : "LumaStudioInvoice[0]/Masthead[1]/SupplierHeader[0]/ContactRows[2]", + "childIndex" : 2, + "depth" : 5, + "layer" : 5, + "computedX" : 160.808, + "computedY" : 674.232, + "placementX" : 160.808, + "placementY" : 674.232, + "placementWidth" : 229.071, + "placementHeight" : 14.395, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 229.071, + "contentHeight" : 14.395, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Masthead[1]/SupplierHeader[0]/ContactRows[2]/ContactRow[2]/SvgIcon[0]", + "entityName" : "SvgIcon", + "entityKind" : "LayerStackNode", + "parentPath" : "LumaStudioInvoice[0]/Masthead[1]/SupplierHeader[0]/ContactRows[2]/ContactRow[2]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 160.808, + "computedY" : 677.129, + "placementX" : 160.808, + "placementY" : 677.129, + "placementWidth" : 8.6, + "placementHeight" : 8.6, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 8.6, + "contentHeight" : 8.6, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Masthead[1]/SupplierHeader[0]/ContactRows[2]/ContactRow[2]/SvgIcon[0]/SvgLayer0[0]", + "entityName" : "SvgLayer0", + "entityKind" : "Path", + "parentPath" : "LumaStudioInvoice[0]/Masthead[1]/SupplierHeader[0]/ContactRows[2]/ContactRow[2]/SvgIcon[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 160.808, + "computedY" : 677.129, + "placementX" : 160.808, + "placementY" : 677.129, + "placementWidth" : 8.6, + "placementHeight" : 8.6, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 8.6, + "contentHeight" : 8.6, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Masthead[1]/SupplierHeader[0]/ContactRows[2]/ContactRow[2]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "LumaStudioInvoice[0]/Masthead[1]/SupplierHeader[0]/ContactRows[2]/ContactRow[2]", + "childIndex" : 1, + "depth" : 6, + "layer" : 6, + "computedX" : 181.808, + "computedY" : 675.692, + "placementX" : 181.808, + "placementY" : 675.692, + "placementWidth" : 91.105, + "placementHeight" : 11.475, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 91.105, + "contentHeight" : 11.475, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Masthead[1]/SupplierHeader[0]/Registration[3]", + "entityName" : "Registration", + "entityKind" : "ParagraphNode", + "parentPath" : "LumaStudioInvoice[0]/Masthead[1]/SupplierHeader[0]", + "childIndex" : 3, + "depth" : 4, + "layer" : 4, + "computedX" : 160.808, + "computedY" : 655.01, + "placementX" : 160.808, + "placementY" : 655.01, + "placementWidth" : 148.091, + "placementHeight" : 8.79, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 148.091, + "contentHeight" : 8.79, + "margin" : { + "top" : 10.433, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Masthead[1]/InvoiceHeader[1]", + "entityName" : "InvoiceHeader", + "entityKind" : "SectionNode", + "parentPath" : "LumaStudioInvoice[0]/Masthead[1]", + "childIndex" : 1, + "depth" : 3, + "layer" : 3, + "computedX" : 389.878, + "computedY" : 647.091, + "placementX" : 389.878, + "placementY" : 647.091, + "placementWidth" : 166.466, + "placementHeight" : 148.499, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 166.466, + "contentHeight" : 148.499, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Masthead[1]/InvoiceHeader[1]/InvoiceTitleBlock[0]", + "entityName" : "InvoiceTitleBlock", + "entityKind" : "ShapeContainerNode", + "parentPath" : "LumaStudioInvoice[0]/Masthead[1]/InvoiceHeader[1]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 389.878, + "computedY" : 749.89, + "placementX" : 389.878, + "placementY" : 749.89, + "placementWidth" : 166.466, + "placementHeight" : 45.7, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 166.466, + "contentHeight" : 45.7, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 15.3, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Masthead[1]/InvoiceHeader[1]/InvoiceTitleBlock[0]/InvoiceTitle[0]", + "entityName" : "InvoiceTitle", + "entityKind" : "ParagraphNode", + "parentPath" : "LumaStudioInvoice[0]/Masthead[1]/InvoiceHeader[1]/InvoiceTitleBlock[0]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 389.878, + "computedY" : 736.636, + "placementX" : 389.878, + "placementY" : 736.636, + "placementWidth" : 164.751, + "placementHeight" : 55.553, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 164.751, + "contentHeight" : 55.553, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Masthead[1]/InvoiceHeader[1]/InvoiceTitleBlock[0]/TitleRule[1]", + "entityName" : "TitleRule", + "entityKind" : "LineNode", + "parentPath" : "LumaStudioInvoice[0]/Masthead[1]/InvoiceHeader[1]/InvoiceTitleBlock[0]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 389.878, + "computedY" : 749.89, + "placementX" : 389.878, + "placementY" : 749.89, + "placementWidth" : 58.7, + "placementHeight" : 1.7, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 58.7, + "contentHeight" : 1.7, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Masthead[1]/InvoiceHeader[1]/MetadataRow[1]", + "entityName" : "MetadataRow", + "entityKind" : "ShapeContainerNode", + "parentPath" : "LumaStudioInvoice[0]/Masthead[1]/InvoiceHeader[1]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 389.878, + "computedY" : 717.09, + "placementX" : 389.878, + "placementY" : 717.09, + "placementWidth" : 166.466, + "placementHeight" : 17.5, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 166.466, + "contentHeight" : 17.5, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Masthead[1]/InvoiceHeader[1]/MetadataRow[1]/MetaLabel[0]", + "entityName" : "MetaLabel", + "entityKind" : "ParagraphNode", + "parentPath" : "LumaStudioInvoice[0]/Masthead[1]/InvoiceHeader[1]/MetadataRow[1]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 389.878, + "computedY" : 720.957, + "placementX" : 389.878, + "placementY" : 720.957, + "placementWidth" : 57.062, + "placementHeight" : 9.765, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 57.062, + "contentHeight" : 9.765, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Masthead[1]/InvoiceHeader[1]/MetadataRow[1]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "LumaStudioInvoice[0]/Masthead[1]/InvoiceHeader[1]/MetadataRow[1]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 491.989, + "computedY" : 720.103, + "placementX" : 491.989, + "placementY" : 720.103, + "placementWidth" : 57.65, + "placementHeight" : 11.475, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 57.65, + "contentHeight" : 11.475, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Masthead[1]/InvoiceHeader[1]/MetadataRow[2]", + "entityName" : "MetadataRow", + "entityKind" : "ShapeContainerNode", + "parentPath" : "LumaStudioInvoice[0]/Masthead[1]/InvoiceHeader[1]", + "childIndex" : 2, + "depth" : 4, + "layer" : 4, + "computedX" : 389.878, + "computedY" : 699.59, + "placementX" : 389.878, + "placementY" : 699.59, + "placementWidth" : 166.466, + "placementHeight" : 17.5, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 166.466, + "contentHeight" : 17.5, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Masthead[1]/InvoiceHeader[1]/MetadataRow[2]/MetaLabel[0]", + "entityName" : "MetaLabel", + "entityKind" : "ParagraphNode", + "parentPath" : "LumaStudioInvoice[0]/Masthead[1]/InvoiceHeader[1]/MetadataRow[2]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 389.878, + "computedY" : 703.458, + "placementX" : 389.878, + "placementY" : 703.458, + "placementWidth" : 51.552, + "placementHeight" : 9.765, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 51.552, + "contentHeight" : 9.765, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Masthead[1]/InvoiceHeader[1]/MetadataRow[2]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "LumaStudioInvoice[0]/Masthead[1]/InvoiceHeader[1]/MetadataRow[2]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 491.989, + "computedY" : 702.603, + "placementX" : 491.989, + "placementY" : 702.603, + "placementWidth" : 49.641, + "placementHeight" : 11.475, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 49.641, + "contentHeight" : 11.475, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Masthead[1]/InvoiceHeader[1]/MetadataRow[3]", + "entityName" : "MetadataRow", + "entityKind" : "ShapeContainerNode", + "parentPath" : "LumaStudioInvoice[0]/Masthead[1]/InvoiceHeader[1]", + "childIndex" : 3, + "depth" : 4, + "layer" : 4, + "computedX" : 389.878, + "computedY" : 682.09, + "placementX" : 389.878, + "placementY" : 682.09, + "placementWidth" : 166.466, + "placementHeight" : 17.5, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 166.466, + "contentHeight" : 17.5, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Masthead[1]/InvoiceHeader[1]/MetadataRow[3]/MetaLabel[0]", + "entityName" : "MetaLabel", + "entityKind" : "ParagraphNode", + "parentPath" : "LumaStudioInvoice[0]/Masthead[1]/InvoiceHeader[1]/MetadataRow[3]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 389.878, + "computedY" : 685.958, + "placementX" : 389.878, + "placementY" : 685.958, + "placementWidth" : 43.963, + "placementHeight" : 9.765, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 43.963, + "contentHeight" : 9.765, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Masthead[1]/InvoiceHeader[1]/MetadataRow[3]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "LumaStudioInvoice[0]/Masthead[1]/InvoiceHeader[1]/MetadataRow[3]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 491.989, + "computedY" : 685.103, + "placementX" : 491.989, + "placementY" : 685.103, + "placementWidth" : 50.393, + "placementHeight" : 11.475, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 50.393, + "contentHeight" : 11.475, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Masthead[1]/InvoiceHeader[1]/MetadataRow[4]", + "entityName" : "MetadataRow", + "entityKind" : "ShapeContainerNode", + "parentPath" : "LumaStudioInvoice[0]/Masthead[1]/InvoiceHeader[1]", + "childIndex" : 4, + "depth" : 4, + "layer" : 4, + "computedX" : 389.878, + "computedY" : 664.591, + "placementX" : 389.878, + "placementY" : 664.591, + "placementWidth" : 166.466, + "placementHeight" : 17.5, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 166.466, + "contentHeight" : 17.5, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Masthead[1]/InvoiceHeader[1]/MetadataRow[4]/MetaLabel[0]", + "entityName" : "MetaLabel", + "entityKind" : "ParagraphNode", + "parentPath" : "LumaStudioInvoice[0]/Masthead[1]/InvoiceHeader[1]/MetadataRow[4]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 389.878, + "computedY" : 668.458, + "placementX" : 389.878, + "placementY" : 668.458, + "placementWidth" : 75.89, + "placementHeight" : 9.765, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 75.89, + "contentHeight" : 9.765, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Masthead[1]/InvoiceHeader[1]/MetadataRow[4]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "LumaStudioInvoice[0]/Masthead[1]/InvoiceHeader[1]/MetadataRow[4]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 491.989, + "computedY" : 667.603, + "placementX" : 491.989, + "placementY" : 667.603, + "placementWidth" : 29.873, + "placementHeight" : 11.475, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 29.873, + "contentHeight" : 11.475, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Masthead[1]/InvoiceHeader[1]/MetadataRow[5]", + "entityName" : "MetadataRow", + "entityKind" : "ShapeContainerNode", + "parentPath" : "LumaStudioInvoice[0]/Masthead[1]/InvoiceHeader[1]", + "childIndex" : 5, + "depth" : 4, + "layer" : 4, + "computedX" : 389.878, + "computedY" : 647.091, + "placementX" : 389.878, + "placementY" : 647.091, + "placementWidth" : 166.466, + "placementHeight" : 17.5, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 166.466, + "contentHeight" : 17.5, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Masthead[1]/InvoiceHeader[1]/MetadataRow[5]/MetaLabel[0]", + "entityName" : "MetaLabel", + "entityKind" : "ParagraphNode", + "parentPath" : "LumaStudioInvoice[0]/Masthead[1]/InvoiceHeader[1]/MetadataRow[5]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 389.878, + "computedY" : 650.958, + "placementX" : 389.878, + "placementY" : 650.958, + "placementWidth" : 46.267, + "placementHeight" : 9.765, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 46.267, + "contentHeight" : 9.765, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Masthead[1]/InvoiceHeader[1]/MetadataRow[5]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "LumaStudioInvoice[0]/Masthead[1]/InvoiceHeader[1]/MetadataRow[5]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 491.989, + "computedY" : 650.103, + "placementX" : 491.989, + "placementY" : 650.103, + "placementWidth" : 15.905, + "placementHeight" : 11.475, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 15.905, + "contentHeight" : 11.475, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/HeaderRule[2]", + "entityName" : "HeaderRule", + "entityKind" : "LineNode", + "parentPath" : "LumaStudioInvoice[0]", + "childIndex" : 2, + "depth" : 2, + "layer" : 2, + "computedX" : 160.808, + "computedY" : 633.091, + "placementX" : 160.808, + "placementY" : 633.091, + "placementWidth" : 395.537, + "placementHeight" : 1.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 395.537, + "contentHeight" : 1.0, + "margin" : { + "top" : 13.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 7.334 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Parties[3]", + "entityName" : "Parties", + "entityKind" : "RowNode", + "parentPath" : "LumaStudioInvoice[0]", + "childIndex" : 3, + "depth" : 2, + "layer" : 2, + "computedX" : 153.474, + "computedY" : 532.149, + "placementX" : 153.474, + "placementY" : 532.149, + "placementWidth" : 402.871, + "placementHeight" : 88.942, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 402.871, + "contentHeight" : 88.942, + "margin" : { + "top" : 12.0, + "right" : 0.0, + "bottom" : 12.9, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Parties[3]/BILLTO[0]", + "entityName" : "BILLTO", + "entityKind" : "SectionNode", + "parentPath" : "LumaStudioInvoice[0]/Parties[3]", + "childIndex" : 0, + "depth" : 3, + "layer" : 3, + "computedX" : 153.474, + "computedY" : 532.149, + "placementX" : 153.474, + "placementY" : 532.149, + "placementWidth" : 103.12, + "placementHeight" : 88.942, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 103.12, + "contentHeight" : 88.942, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 7.334 + } + }, { + "path" : "LumaStudioInvoice[0]/Parties[3]/BILLTO[0]/PartyHeading[0]", + "entityName" : "PartyHeading", + "entityKind" : "ParagraphNode", + "parentPath" : "LumaStudioInvoice[0]/Parties[3]/BILLTO[0]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 160.808, + "computedY" : 611.326, + "placementX" : 160.808, + "placementY" : 611.326, + "placementWidth" : 33.342, + "placementHeight" : 9.765, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 33.342, + "contentHeight" : 9.765, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Parties[3]/BILLTO[0]/PartyAddress[1]", + "entityName" : "PartyAddress", + "entityKind" : "ParagraphNode", + "parentPath" : "LumaStudioInvoice[0]/Parties[3]/BILLTO[0]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 160.808, + "computedY" : 532.149, + "placementX" : 160.808, + "placementY" : 532.149, + "placementWidth" : 95.786, + "placementHeight" : 70.235, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 95.786, + "contentHeight" : 70.235, + "margin" : { + "top" : 8.942, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Parties[3]/SHIPTO[1]", + "entityName" : "SHIPTO", + "entityKind" : "SectionNode", + "parentPath" : "LumaStudioInvoice[0]/Parties[3]", + "childIndex" : 1, + "depth" : 3, + "layer" : 3, + "computedX" : 344.757, + "computedY" : 532.149, + "placementX" : 344.757, + "placementY" : 532.149, + "placementWidth" : 133.88, + "placementHeight" : 88.942, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 133.88, + "contentHeight" : 88.942, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 34.4 + } + }, { + "path" : "LumaStudioInvoice[0]/Parties[3]/SHIPTO[1]/PartyHeading[0]", + "entityName" : "PartyHeading", + "entityKind" : "ParagraphNode", + "parentPath" : "LumaStudioInvoice[0]/Parties[3]/SHIPTO[1]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 379.157, + "computedY" : 611.326, + "placementX" : 379.157, + "placementY" : 611.326, + "placementWidth" : 35.173, + "placementHeight" : 9.765, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 35.173, + "contentHeight" : 9.765, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Parties[3]/SHIPTO[1]/PartyAddress[1]", + "entityName" : "PartyAddress", + "entityKind" : "ParagraphNode", + "parentPath" : "LumaStudioInvoice[0]/Parties[3]/SHIPTO[1]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 379.157, + "computedY" : 532.149, + "placementX" : 379.157, + "placementY" : 532.149, + "placementWidth" : 99.48, + "placementHeight" : 70.235, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 99.48, + "contentHeight" : 70.235, + "margin" : { + "top" : 8.942, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/LineItems[4]", + "entityName" : "LineItems", + "entityKind" : "TableNode", + "parentPath" : "LumaStudioInvoice[0]", + "childIndex" : 4, + "depth" : 2, + "layer" : 2, + "computedX" : 153.474, + "computedY" : 268.127, + "placementX" : 153.474, + "placementY" : 268.127, + "placementWidth" : 402.871, + "placementHeight" : 251.122, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 402.871, + "contentHeight" : 251.122, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Totals[5]", + "entityName" : "Totals", + "entityKind" : "SectionNode", + "parentPath" : "LumaStudioInvoice[0]", + "childIndex" : 5, + "depth" : 2, + "layer" : 2, + "computedX" : 306.404, + "computedY" : 197.553, + "placementX" : 306.404, + "placementY" : 197.553, + "placementWidth" : 249.941, + "placementHeight" : 66.174, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 249.941, + "contentHeight" : 66.174, + "margin" : { + "top" : 4.4, + "right" : 0.0, + "bottom" : 0.0, + "left" : 152.93 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Totals[5]/TotalsRow[0]", + "entityName" : "TotalsRow", + "entityKind" : "ShapeContainerNode", + "parentPath" : "LumaStudioInvoice[0]/Totals[5]", + "childIndex" : 0, + "depth" : 3, + "layer" : 3, + "computedX" : 306.404, + "computedY" : 245.619, + "placementX" : 306.404, + "placementY" : 245.619, + "placementWidth" : 249.941, + "placementHeight" : 18.108, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 249.941, + "contentHeight" : 18.108, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Totals[5]/TotalsRow[0]/TotalsLabel[0]", + "entityName" : "TotalsLabel", + "entityKind" : "ParagraphNode", + "parentPath" : "LumaStudioInvoice[0]/Totals[5]/TotalsRow[0]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 348.204, + "computedY" : 248.821, + "placementX" : 348.204, + "placementY" : 248.821, + "placementWidth" : 46.299, + "placementHeight" : 11.704, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 46.299, + "contentHeight" : 11.704, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Totals[5]/TotalsRow[0]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "LumaStudioInvoice[0]/Totals[5]/TotalsRow[0]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 293.404, + "computedY" : 248.821, + "placementX" : 293.404, + "placementY" : 248.821, + "placementWidth" : 249.941, + "placementHeight" : 11.704, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 249.941, + "contentHeight" : 11.704, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Totals[5]/TotalsRow[1]", + "entityName" : "TotalsRow", + "entityKind" : "ShapeContainerNode", + "parentPath" : "LumaStudioInvoice[0]/Totals[5]", + "childIndex" : 1, + "depth" : 3, + "layer" : 3, + "computedX" : 306.404, + "computedY" : 227.51, + "placementX" : 306.404, + "placementY" : 227.51, + "placementWidth" : 249.941, + "placementHeight" : 18.108, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 249.941, + "contentHeight" : 18.108, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Totals[5]/TotalsRow[1]/TotalsLabel[0]", + "entityName" : "TotalsLabel", + "entityKind" : "ParagraphNode", + "parentPath" : "LumaStudioInvoice[0]/Totals[5]/TotalsRow[1]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 348.204, + "computedY" : 230.712, + "placementX" : 348.204, + "placementY" : 230.712, + "placementWidth" : 45.982, + "placementHeight" : 11.704, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 45.982, + "contentHeight" : 11.704, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Totals[5]/TotalsRow[1]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "LumaStudioInvoice[0]/Totals[5]/TotalsRow[1]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 293.404, + "computedY" : 230.712, + "placementX" : 293.404, + "placementY" : 230.712, + "placementWidth" : 249.941, + "placementHeight" : 11.704, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 249.941, + "contentHeight" : 11.704, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Totals[5]/TotalDueBand[2]", + "entityName" : "TotalDueBand", + "entityKind" : "ShapeContainerNode", + "parentPath" : "LumaStudioInvoice[0]/Totals[5]", + "childIndex" : 2, + "depth" : 3, + "layer" : 3, + "computedX" : 306.404, + "computedY" : 197.553, + "placementX" : 306.404, + "placementY" : 197.553, + "placementWidth" : 249.941, + "placementHeight" : 27.1, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 249.941, + "contentHeight" : 27.1, + "margin" : { + "top" : 2.857, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Totals[5]/TotalDueBand[2]/TotalsLabel[0]", + "entityName" : "TotalsLabel", + "entityKind" : "ParagraphNode", + "parentPath" : "LumaStudioInvoice[0]/Totals[5]/TotalDueBand[2]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 348.204, + "computedY" : 204.85, + "placementX" : 348.204, + "placementY" : 204.85, + "placementWidth" : 52.971, + "placementHeight" : 12.507, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 52.971, + "contentHeight" : 12.507, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Totals[5]/TotalDueBand[2]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "LumaStudioInvoice[0]/Totals[5]/TotalDueBand[2]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 293.404, + "computedY" : 199.812, + "placementX" : 293.404, + "placementY" : 199.812, + "placementWidth" : 249.941, + "placementHeight" : 22.582, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 249.941, + "contentHeight" : 22.582, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/FooterRule[6]", + "entityName" : "FooterRule", + "entityKind" : "LineNode", + "parentPath" : "LumaStudioInvoice[0]", + "childIndex" : 6, + "depth" : 2, + "layer" : 2, + "computedX" : 153.474, + "computedY" : 194.553, + "placementX" : 153.474, + "placementY" : 194.553, + "placementWidth" : 402.871, + "placementHeight" : 1.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 402.871, + "contentHeight" : 1.0, + "margin" : { + "top" : 2.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Closing[7]", + "entityName" : "Closing", + "entityKind" : "RowNode", + "parentPath" : "LumaStudioInvoice[0]", + "childIndex" : 7, + "depth" : 2, + "layer" : 2, + "computedX" : 153.474, + "computedY" : 74.375, + "placementX" : 153.474, + "placementY" : 74.375, + "placementWidth" : 402.871, + "placementHeight" : 108.178, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 402.871, + "contentHeight" : 108.178, + "margin" : { + "top" : 12.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Closing[7]/Notes[0]", + "entityName" : "Notes", + "entityKind" : "SectionNode", + "parentPath" : "LumaStudioInvoice[0]/Closing[7]", + "childIndex" : 0, + "depth" : 3, + "layer" : 3, + "computedX" : 153.474, + "computedY" : 86.272, + "placementX" : 153.474, + "placementY" : 86.272, + "placementWidth" : 167.03, + "placementHeight" : 96.281, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 167.03, + "contentHeight" : 96.281, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Closing[7]/Notes[0]/SectionHead[0]", + "entityName" : "SectionHead", + "entityKind" : "ShapeContainerNode", + "parentPath" : "LumaStudioInvoice[0]/Closing[7]/Notes[0]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 153.474, + "computedY" : 157.753, + "placementX" : 153.474, + "placementY" : 157.753, + "placementWidth" : 167.03, + "placementHeight" : 24.8, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 167.03, + "contentHeight" : 24.8, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Closing[7]/Notes[0]/SectionHead[0]/Disc-notes[0]", + "entityName" : "Disc-notes", + "entityKind" : "LayerStackNode", + "parentPath" : "LumaStudioInvoice[0]/Closing[7]/Notes[0]/SectionHead[0]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 153.474, + "computedY" : 157.753, + "placementX" : 153.474, + "placementY" : 157.753, + "placementWidth" : 24.8, + "placementHeight" : 24.8, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 24.8, + "contentHeight" : 24.8, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Closing[7]/Notes[0]/SectionHead[0]/Disc-notes[0]/DiscFill[0]", + "entityName" : "DiscFill", + "entityKind" : "EllipseNode", + "parentPath" : "LumaStudioInvoice[0]/Closing[7]/Notes[0]/SectionHead[0]/Disc-notes[0]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 153.474, + "computedY" : 157.753, + "placementX" : 153.474, + "placementY" : 157.753, + "placementWidth" : 24.8, + "placementHeight" : 24.8, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 24.8, + "contentHeight" : 24.8, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Closing[7]/Notes[0]/SectionHead[0]/Disc-notes[0]/SvgIcon[1]", + "entityName" : "SvgIcon", + "entityKind" : "LayerStackNode", + "parentPath" : "LumaStudioInvoice[0]/Closing[7]/Notes[0]/SectionHead[0]/Disc-notes[0]", + "childIndex" : 1, + "depth" : 6, + "layer" : 6, + "computedX" : 159.874, + "computedY" : 164.153, + "placementX" : 159.874, + "placementY" : 164.153, + "placementWidth" : 12.0, + "placementHeight" : 12.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 12.0, + "contentHeight" : 12.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Closing[7]/Notes[0]/SectionHead[0]/Disc-notes[0]/SvgIcon[1]/SvgLayer0[0]", + "entityName" : "SvgLayer0", + "entityKind" : "Path", + "parentPath" : "LumaStudioInvoice[0]/Closing[7]/Notes[0]/SectionHead[0]/Disc-notes[0]/SvgIcon[1]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 159.874, + "computedY" : 164.153, + "placementX" : 159.874, + "placementY" : 164.153, + "placementWidth" : 12.0, + "placementHeight" : 12.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 12.0, + "contentHeight" : 12.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Closing[7]/Notes[0]/SectionHead[0]/SectionHeading[1]", + "entityName" : "SectionHeading", + "entityKind" : "ParagraphNode", + "parentPath" : "LumaStudioInvoice[0]/Closing[7]/Notes[0]/SectionHead[0]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 188.974, + "computedY" : 165.271, + "placementX" : 188.974, + "placementY" : 165.271, + "placementWidth" : 28.177, + "placementHeight" : 9.765, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 28.177, + "contentHeight" : 9.765, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Closing[7]/Notes[0]/NotesBody[1]", + "entityName" : "NotesBody", + "entityKind" : "SectionNode", + "parentPath" : "LumaStudioInvoice[0]/Closing[7]/Notes[0]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 153.474, + "computedY" : 86.272, + "placementX" : 153.474, + "placementY" : 86.272, + "placementWidth" : 156.503, + "placementHeight" : 69.245, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 156.503, + "contentHeight" : 69.245, + "margin" : { + "top" : 2.236, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 35.5 + } + }, { + "path" : "LumaStudioInvoice[0]/Closing[7]/Notes[0]/NotesBody[1]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "LumaStudioInvoice[0]/Closing[7]/Notes[0]/NotesBody[1]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 188.974, + "computedY" : 125.552, + "placementX" : 188.974, + "placementY" : 125.552, + "placementWidth" : 103.794, + "placementHeight" : 29.965, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 103.794, + "contentHeight" : 29.965, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Closing[7]/Notes[0]/NotesBody[1]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "LumaStudioInvoice[0]/Closing[7]/Notes[0]/NotesBody[1]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 188.974, + "computedY" : 86.272, + "placementX" : 188.974, + "placementY" : 86.272, + "placementWidth" : 121.003, + "placementHeight" : 29.965, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 121.003, + "contentHeight" : 29.965, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Closing[7]/PaymentDetails[1]", + "entityName" : "PaymentDetails", + "entityKind" : "SectionNode", + "parentPath" : "LumaStudioInvoice[0]/Closing[7]", + "childIndex" : 1, + "depth" : 3, + "layer" : 3, + "computedX" : 320.504, + "computedY" : 74.375, + "placementX" : 320.504, + "placementY" : 74.375, + "placementWidth" : 235.84, + "placementHeight" : 108.178, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 235.84, + "contentHeight" : 108.178, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 24.3 + } + }, { + "path" : "LumaStudioInvoice[0]/Closing[7]/PaymentDetails[1]/SectionHead[0]", + "entityName" : "SectionHead", + "entityKind" : "ShapeContainerNode", + "parentPath" : "LumaStudioInvoice[0]/Closing[7]/PaymentDetails[1]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 344.804, + "computedY" : 157.753, + "placementX" : 344.804, + "placementY" : 157.753, + "placementWidth" : 211.54, + "placementHeight" : 24.8, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 211.54, + "contentHeight" : 24.8, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Closing[7]/PaymentDetails[1]/SectionHead[0]/Disc-bank[0]", + "entityName" : "Disc-bank", + "entityKind" : "LayerStackNode", + "parentPath" : "LumaStudioInvoice[0]/Closing[7]/PaymentDetails[1]/SectionHead[0]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 344.804, + "computedY" : 157.753, + "placementX" : 344.804, + "placementY" : 157.753, + "placementWidth" : 24.8, + "placementHeight" : 24.8, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 24.8, + "contentHeight" : 24.8, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Closing[7]/PaymentDetails[1]/SectionHead[0]/Disc-bank[0]/DiscFill[0]", + "entityName" : "DiscFill", + "entityKind" : "EllipseNode", + "parentPath" : "LumaStudioInvoice[0]/Closing[7]/PaymentDetails[1]/SectionHead[0]/Disc-bank[0]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 344.804, + "computedY" : 157.753, + "placementX" : 344.804, + "placementY" : 157.753, + "placementWidth" : 24.8, + "placementHeight" : 24.8, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 24.8, + "contentHeight" : 24.8, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Closing[7]/PaymentDetails[1]/SectionHead[0]/Disc-bank[0]/SvgIcon[1]", + "entityName" : "SvgIcon", + "entityKind" : "LayerStackNode", + "parentPath" : "LumaStudioInvoice[0]/Closing[7]/PaymentDetails[1]/SectionHead[0]/Disc-bank[0]", + "childIndex" : 1, + "depth" : 6, + "layer" : 6, + "computedX" : 351.204, + "computedY" : 164.153, + "placementX" : 351.204, + "placementY" : 164.153, + "placementWidth" : 12.0, + "placementHeight" : 12.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 12.0, + "contentHeight" : 12.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Closing[7]/PaymentDetails[1]/SectionHead[0]/Disc-bank[0]/SvgIcon[1]/SvgLayer0[0]", + "entityName" : "SvgLayer0", + "entityKind" : "Path", + "parentPath" : "LumaStudioInvoice[0]/Closing[7]/PaymentDetails[1]/SectionHead[0]/Disc-bank[0]/SvgIcon[1]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 351.204, + "computedY" : 164.153, + "placementX" : 351.204, + "placementY" : 164.153, + "placementWidth" : 12.0, + "placementHeight" : 12.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 12.0, + "contentHeight" : 12.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Closing[7]/PaymentDetails[1]/SectionHead[0]/SectionHeading[1]", + "entityName" : "SectionHeading", + "entityKind" : "ParagraphNode", + "parentPath" : "LumaStudioInvoice[0]/Closing[7]/PaymentDetails[1]/SectionHead[0]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 380.304, + "computedY" : 165.271, + "placementX" : 380.304, + "placementY" : 165.271, + "placementWidth" : 82.727, + "placementHeight" : 9.765, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 82.727, + "contentHeight" : 9.765, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Closing[7]/PaymentDetails[1]/PaymentBody[1]", + "entityName" : "PaymentBody", + "entityKind" : "SectionNode", + "parentPath" : "LumaStudioInvoice[0]/Closing[7]/PaymentDetails[1]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 344.804, + "computedY" : 74.375, + "placementX" : 344.804, + "placementY" : 74.375, + "placementWidth" : 211.54, + "placementHeight" : 81.143, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 211.54, + "contentHeight" : 81.143, + "margin" : { + "top" : 2.236, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 35.5 + } + }, { + "path" : "LumaStudioInvoice[0]/Closing[7]/PaymentDetails[1]/PaymentBody[1]/PaymentInstruction[0]", + "entityName" : "PaymentInstruction", + "entityKind" : "ParagraphNode", + "parentPath" : "LumaStudioInvoice[0]/Closing[7]/PaymentDetails[1]/PaymentBody[1]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 380.304, + "computedY" : 147.544, + "placementX" : 380.304, + "placementY" : 147.544, + "placementWidth" : 121.183, + "placementHeight" : 7.974, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 121.183, + "contentHeight" : 7.974, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Closing[7]/PaymentDetails[1]/PaymentBody[1]/AccountHolder[1]", + "entityName" : "AccountHolder", + "entityKind" : "ParagraphNode", + "parentPath" : "LumaStudioInvoice[0]/Closing[7]/PaymentDetails[1]/PaymentBody[1]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 380.304, + "computedY" : 132.19, + "placementX" : 380.304, + "placementY" : 132.19, + "placementWidth" : 83.026, + "placementHeight" : 9.765, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 83.026, + "contentHeight" : 9.765, + "margin" : { + "top" : 5.589, + "right" : 0.0, + "bottom" : 6.831, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Closing[7]/PaymentDetails[1]/PaymentBody[1]/PaymentField[2]", + "entityName" : "PaymentField", + "entityKind" : "ShapeContainerNode", + "parentPath" : "LumaStudioInvoice[0]/Closing[7]/PaymentDetails[1]/PaymentBody[1]", + "childIndex" : 2, + "depth" : 5, + "layer" : 5, + "computedX" : 380.304, + "computedY" : 115.162, + "placementX" : 380.304, + "placementY" : 115.162, + "placementWidth" : 176.04, + "placementHeight" : 10.197, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 176.04, + "contentHeight" : 10.197, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Closing[7]/PaymentDetails[1]/PaymentBody[1]/PaymentField[2]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "LumaStudioInvoice[0]/Closing[7]/PaymentDetails[1]/PaymentBody[1]/PaymentField[2]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 380.304, + "computedY" : 115.866, + "placementX" : 380.304, + "placementY" : 115.866, + "placementWidth" : 17.087, + "placementHeight" : 8.79, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 17.087, + "contentHeight" : 8.79, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Closing[7]/PaymentDetails[1]/PaymentBody[1]/PaymentField[2]/BankPipe[1]", + "entityName" : "BankPipe", + "entityKind" : "LineNode", + "parentPath" : "LumaStudioInvoice[0]/Closing[7]/PaymentDetails[1]/PaymentBody[1]/PaymentField[2]", + "childIndex" : 1, + "depth" : 6, + "layer" : 6, + "computedX" : 433.904, + "computedY" : 116.895, + "placementX" : 433.904, + "placementY" : 116.895, + "placementWidth" : 1.0, + "placementHeight" : 6.73, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 1.0, + "contentHeight" : 6.73, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Closing[7]/PaymentDetails[1]/PaymentBody[1]/PaymentField[2]/ParagraphNode[2]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "LumaStudioInvoice[0]/Closing[7]/PaymentDetails[1]/PaymentBody[1]/PaymentField[2]", + "childIndex" : 2, + "depth" : 6, + "layer" : 6, + "computedX" : 444.104, + "computedY" : 115.866, + "placementX" : 444.104, + "placementY" : 115.866, + "placementWidth" : 38.198, + "placementHeight" : 8.79, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 38.198, + "contentHeight" : 8.79, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Closing[7]/PaymentDetails[1]/PaymentBody[1]/PaymentField[3]", + "entityName" : "PaymentField", + "entityKind" : "ShapeContainerNode", + "parentPath" : "LumaStudioInvoice[0]/Closing[7]/PaymentDetails[1]/PaymentBody[1]", + "childIndex" : 3, + "depth" : 5, + "layer" : 5, + "computedX" : 380.304, + "computedY" : 104.965, + "placementX" : 380.304, + "placementY" : 104.965, + "placementWidth" : 176.04, + "placementHeight" : 10.197, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 176.04, + "contentHeight" : 10.197, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Closing[7]/PaymentDetails[1]/PaymentBody[1]/PaymentField[3]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "LumaStudioInvoice[0]/Closing[7]/PaymentDetails[1]/PaymentBody[1]/PaymentField[3]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 380.304, + "computedY" : 105.669, + "placementX" : 380.304, + "placementY" : 105.669, + "placementWidth" : 34.245, + "placementHeight" : 8.79, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 34.245, + "contentHeight" : 8.79, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Closing[7]/PaymentDetails[1]/PaymentBody[1]/PaymentField[3]/BankPipe[1]", + "entityName" : "BankPipe", + "entityKind" : "LineNode", + "parentPath" : "LumaStudioInvoice[0]/Closing[7]/PaymentDetails[1]/PaymentBody[1]/PaymentField[3]", + "childIndex" : 1, + "depth" : 6, + "layer" : 6, + "computedX" : 433.904, + "computedY" : 106.699, + "placementX" : 433.904, + "placementY" : 106.699, + "placementWidth" : 1.0, + "placementHeight" : 6.73, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 1.0, + "contentHeight" : 6.73, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Closing[7]/PaymentDetails[1]/PaymentBody[1]/PaymentField[3]/ParagraphNode[2]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "LumaStudioInvoice[0]/Closing[7]/PaymentDetails[1]/PaymentBody[1]/PaymentField[3]", + "childIndex" : 2, + "depth" : 6, + "layer" : 6, + "computedX" : 444.104, + "computedY" : 105.669, + "placementX" : 444.104, + "placementY" : 105.669, + "placementWidth" : 26.31, + "placementHeight" : 8.79, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 26.31, + "contentHeight" : 8.79, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Closing[7]/PaymentDetails[1]/PaymentBody[1]/PaymentField[4]", + "entityName" : "PaymentField", + "entityKind" : "ShapeContainerNode", + "parentPath" : "LumaStudioInvoice[0]/Closing[7]/PaymentDetails[1]/PaymentBody[1]", + "childIndex" : 4, + "depth" : 5, + "layer" : 5, + "computedX" : 380.304, + "computedY" : 94.768, + "placementX" : 380.304, + "placementY" : 94.768, + "placementWidth" : 176.04, + "placementHeight" : 10.197, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 176.04, + "contentHeight" : 10.197, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Closing[7]/PaymentDetails[1]/PaymentBody[1]/PaymentField[4]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "LumaStudioInvoice[0]/Closing[7]/PaymentDetails[1]/PaymentBody[1]/PaymentField[4]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 380.304, + "computedY" : 95.472, + "placementX" : 380.304, + "placementY" : 95.472, + "placementWidth" : 43.022, + "placementHeight" : 8.79, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 43.022, + "contentHeight" : 8.79, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Closing[7]/PaymentDetails[1]/PaymentBody[1]/PaymentField[4]/BankPipe[1]", + "entityName" : "BankPipe", + "entityKind" : "LineNode", + "parentPath" : "LumaStudioInvoice[0]/Closing[7]/PaymentDetails[1]/PaymentBody[1]/PaymentField[4]", + "childIndex" : 1, + "depth" : 6, + "layer" : 6, + "computedX" : 433.904, + "computedY" : 96.502, + "placementX" : 433.904, + "placementY" : 96.502, + "placementWidth" : 1.0, + "placementHeight" : 6.73, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 1.0, + "contentHeight" : 6.73, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Closing[7]/PaymentDetails[1]/PaymentBody[1]/PaymentField[4]/ParagraphNode[2]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "LumaStudioInvoice[0]/Closing[7]/PaymentDetails[1]/PaymentBody[1]/PaymentField[4]", + "childIndex" : 2, + "depth" : 6, + "layer" : 6, + "computedX" : 444.104, + "computedY" : 95.472, + "placementX" : 444.104, + "placementY" : 95.472, + "placementWidth" : 29.205, + "placementHeight" : 8.79, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 29.205, + "contentHeight" : 8.79, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Closing[7]/PaymentDetails[1]/PaymentBody[1]/PaymentField[5]", + "entityName" : "PaymentField", + "entityKind" : "ShapeContainerNode", + "parentPath" : "LumaStudioInvoice[0]/Closing[7]/PaymentDetails[1]/PaymentBody[1]", + "childIndex" : 5, + "depth" : 5, + "layer" : 5, + "computedX" : 380.304, + "computedY" : 84.571, + "placementX" : 380.304, + "placementY" : 84.571, + "placementWidth" : 176.04, + "placementHeight" : 10.197, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 176.04, + "contentHeight" : 10.197, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Closing[7]/PaymentDetails[1]/PaymentBody[1]/PaymentField[5]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "LumaStudioInvoice[0]/Closing[7]/PaymentDetails[1]/PaymentBody[1]/PaymentField[5]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 380.304, + "computedY" : 85.275, + "placementX" : 380.304, + "placementY" : 85.275, + "placementWidth" : 15.07, + "placementHeight" : 8.79, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 15.07, + "contentHeight" : 8.79, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Closing[7]/PaymentDetails[1]/PaymentBody[1]/PaymentField[5]/BankPipe[1]", + "entityName" : "BankPipe", + "entityKind" : "LineNode", + "parentPath" : "LumaStudioInvoice[0]/Closing[7]/PaymentDetails[1]/PaymentBody[1]/PaymentField[5]", + "childIndex" : 1, + "depth" : 6, + "layer" : 6, + "computedX" : 433.904, + "computedY" : 86.305, + "placementX" : 433.904, + "placementY" : 86.305, + "placementWidth" : 1.0, + "placementHeight" : 6.73, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 1.0, + "contentHeight" : 6.73, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Closing[7]/PaymentDetails[1]/PaymentBody[1]/PaymentField[5]/ParagraphNode[2]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "LumaStudioInvoice[0]/Closing[7]/PaymentDetails[1]/PaymentBody[1]/PaymentField[5]", + "childIndex" : 2, + "depth" : 6, + "layer" : 6, + "computedX" : 444.104, + "computedY" : 85.275, + "placementX" : 444.104, + "placementY" : 85.275, + "placementWidth" : 89.789, + "placementHeight" : 8.79, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 89.789, + "contentHeight" : 8.79, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Closing[7]/PaymentDetails[1]/PaymentBody[1]/PaymentField[6]", + "entityName" : "PaymentField", + "entityKind" : "ShapeContainerNode", + "parentPath" : "LumaStudioInvoice[0]/Closing[7]/PaymentDetails[1]/PaymentBody[1]", + "childIndex" : 6, + "depth" : 5, + "layer" : 5, + "computedX" : 380.304, + "computedY" : 74.375, + "placementX" : 380.304, + "placementY" : 74.375, + "placementWidth" : 176.04, + "placementHeight" : 10.197, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 176.04, + "contentHeight" : 10.197, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Closing[7]/PaymentDetails[1]/PaymentBody[1]/PaymentField[6]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "LumaStudioInvoice[0]/Closing[7]/PaymentDetails[1]/PaymentBody[1]/PaymentField[6]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 380.304, + "computedY" : 75.078, + "placementX" : 380.304, + "placementY" : 75.078, + "placementWidth" : 9.771, + "placementHeight" : 8.79, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 9.771, + "contentHeight" : 8.79, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Closing[7]/PaymentDetails[1]/PaymentBody[1]/PaymentField[6]/BankPipe[1]", + "entityName" : "BankPipe", + "entityKind" : "LineNode", + "parentPath" : "LumaStudioInvoice[0]/Closing[7]/PaymentDetails[1]/PaymentBody[1]/PaymentField[6]", + "childIndex" : 1, + "depth" : 6, + "layer" : 6, + "computedX" : 433.904, + "computedY" : 76.108, + "placementX" : 433.904, + "placementY" : 76.108, + "placementWidth" : 1.0, + "placementHeight" : 6.73, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 1.0, + "contentHeight" : 6.73, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Closing[7]/PaymentDetails[1]/PaymentBody[1]/PaymentField[6]/ParagraphNode[2]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "LumaStudioInvoice[0]/Closing[7]/PaymentDetails[1]/PaymentBody[1]/PaymentField[6]", + "childIndex" : 2, + "depth" : 6, + "layer" : 6, + "computedX" : 444.104, + "computedY" : 75.078, + "placementX" : 444.104, + "placementY" : 75.078, + "placementWidth" : 29.918, + "placementHeight" : 8.79, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 29.918, + "contentHeight" : 8.79, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/ClosingBanner[8]", + "entityName" : "ClosingBanner", + "entityKind" : "SectionNode", + "parentPath" : "LumaStudioInvoice[0]", + "childIndex" : 8, + "depth" : 2, + "layer" : 2, + "computedX" : 153.474, + "computedY" : 65.475, + "placementX" : 153.474, + "placementY" : 65.475, + "placementWidth" : 402.871, + "placementHeight" : 0.3, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 402.871, + "contentHeight" : 0.3, + "margin" : { + "top" : 8.6, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/ClosingBanner[8]/BannerContent[0]", + "entityName" : "BannerContent", + "entityKind" : "ShapeContainerNode", + "parentPath" : "LumaStudioInvoice[0]/ClosingBanner[8]", + "childIndex" : 0, + "depth" : 3, + "layer" : 3, + "computedX" : 0.0, + "computedY" : 3.096, + "placementX" : 0.0, + "placementY" : 3.096, + "placementWidth" : 595.276, + "placementHeight" : 62.679, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 595.276, + "contentHeight" : 62.679, + "margin" : { + "top" : 0.0, + "right" : -38.931, + "bottom" : -62.379, + "left" : -153.474 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/ClosingBanner[8]/BannerContent[0]/BannerBand[0]", + "entityName" : "BannerBand", + "entityKind" : "ShapeNode", + "parentPath" : "LumaStudioInvoice[0]/ClosingBanner[8]/BannerContent[0]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 0.0, + "computedY" : 3.096, + "placementX" : 0.0, + "placementY" : 3.096, + "placementWidth" : 595.276, + "placementHeight" : 62.679, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 595.276, + "contentHeight" : 62.679, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/ClosingBanner[8]/BannerContent[0]/Disc-heart[1]", + "entityName" : "Disc-heart", + "entityKind" : "LayerStackNode", + "parentPath" : "LumaStudioInvoice[0]/ClosingBanner[8]/BannerContent[0]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 32.728, + "computedY" : 17.485, + "placementX" : 32.728, + "placementY" : 17.485, + "placementWidth" : 33.9, + "placementHeight" : 33.9, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 33.9, + "contentHeight" : 33.9, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/ClosingBanner[8]/BannerContent[0]/Disc-heart[1]/DiscFill[0]", + "entityName" : "DiscFill", + "entityKind" : "EllipseNode", + "parentPath" : "LumaStudioInvoice[0]/ClosingBanner[8]/BannerContent[0]/Disc-heart[1]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 32.728, + "computedY" : 17.485, + "placementX" : 32.728, + "placementY" : 17.485, + "placementWidth" : 33.9, + "placementHeight" : 33.9, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 33.9, + "contentHeight" : 33.9, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/ClosingBanner[8]/BannerContent[0]/Disc-heart[1]/SvgIcon[1]", + "entityName" : "SvgIcon", + "entityKind" : "LayerStackNode", + "parentPath" : "LumaStudioInvoice[0]/ClosingBanner[8]/BannerContent[0]/Disc-heart[1]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 41.678, + "computedY" : 26.435, + "placementX" : 41.678, + "placementY" : 26.435, + "placementWidth" : 16.0, + "placementHeight" : 16.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 16.0, + "contentHeight" : 16.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/ClosingBanner[8]/BannerContent[0]/Disc-heart[1]/SvgIcon[1]/SvgLayer0[0]", + "entityName" : "SvgLayer0", + "entityKind" : "Path", + "parentPath" : "LumaStudioInvoice[0]/ClosingBanner[8]/BannerContent[0]/Disc-heart[1]/SvgIcon[1]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 41.678, + "computedY" : 26.435, + "placementX" : 41.678, + "placementY" : 26.435, + "placementWidth" : 16.0, + "placementHeight" : 16.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 16.0, + "contentHeight" : 16.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/ClosingBanner[8]/BannerContent[0]/BannerLines[2]", + "entityName" : "BannerLines", + "entityKind" : "SectionNode", + "parentPath" : "LumaStudioInvoice[0]/ClosingBanner[8]/BannerContent[0]", + "childIndex" : 2, + "depth" : 4, + "layer" : 4, + "computedX" : 80.628, + "computedY" : 21.357, + "placementX" : 80.628, + "placementY" : 21.357, + "placementWidth" : 179.909, + "placementHeight" : 26.156, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 179.909, + "contentHeight" : 26.156, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/ClosingBanner[8]/BannerContent[0]/BannerLines[2]/SignOff[0]", + "entityName" : "SignOff", + "entityKind" : "ParagraphNode", + "parentPath" : "LumaStudioInvoice[0]/ClosingBanner[8]/BannerContent[0]/BannerLines[2]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 80.628, + "computedY" : 35.809, + "placementX" : 80.628, + "placementY" : 35.809, + "placementWidth" : 179.909, + "placementHeight" : 11.704, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 179.909, + "contentHeight" : 11.704, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/ClosingBanner[8]/BannerContent[0]/BannerLines[2]/DueNotice[1]", + "entityName" : "DueNotice", + "entityKind" : "ParagraphNode", + "parentPath" : "LumaStudioInvoice[0]/ClosingBanner[8]/BannerContent[0]/BannerLines[2]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 80.628, + "computedY" : 21.357, + "placementX" : 80.628, + "placementY" : 21.357, + "placementWidth" : 135.165, + "placementHeight" : 11.347, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 135.165, + "contentHeight" : 11.347, + "margin" : { + "top" : 3.105, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + } ] +} diff --git a/qa/src/test/resources/layout-snapshots/canonical-templates/invoice/luma_studio_invoice_overflow_layout.json b/qa/src/test/resources/layout-snapshots/canonical-templates/invoice/luma_studio_invoice_overflow_layout.json new file mode 100644 index 000000000..7592ce06c --- /dev/null +++ b/qa/src/test/resources/layout-snapshots/canonical-templates/invoice/luma_studio_invoice_overflow_layout.json @@ -0,0 +1,3677 @@ +{ + "formatVersion" : "2.0", + "canvas" : { + "pageWidth" : 595.276, + "pageHeight" : 841.89, + "innerWidth" : 402.871, + "innerHeight" : 732.911, + "margin" : { + "top" : 46.3, + "right" : 38.931, + "bottom" : 62.679, + "left" : 153.474 + } + }, + "totalPages" : 3, + "nodes" : [ { + "path" : "LumaStudioInvoice[0]", + "entityName" : "LumaStudioInvoice", + "entityKind" : "ContainerNode", + "parentPath" : null, + "childIndex" : 0, + "depth" : 1, + "layer" : 1, + "computedX" : 153.474, + "computedY" : -695.368, + "placementX" : 153.474, + "placementY" : -695.368, + "placementWidth" : 402.871, + "placementHeight" : 1490.957, + "startPage" : 0, + "endPage" : 2, + "contentWidth" : 402.871, + "contentHeight" : 1490.957, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Sidebar[0]", + "entityName" : "Sidebar", + "entityKind" : "ShapeContainerNode", + "parentPath" : "LumaStudioInvoice[0]", + "childIndex" : 0, + "depth" : 2, + "layer" : 2, + "computedX" : 0.0, + "computedY" : 841.49, + "placementX" : 0.0, + "placementY" : 841.49, + "placementWidth" : 132.032, + "placementHeight" : 0.4, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 132.032, + "contentHeight" : 0.4, + "margin" : { + "top" : -46.3, + "right" : 0.0, + "bottom" : 45.9, + "left" : -153.474 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Sidebar[0]/SidebarOrnament[0]", + "entityName" : "SidebarOrnament", + "entityKind" : "ShapeContainerNode", + "parentPath" : "LumaStudioInvoice[0]/Sidebar[0]", + "childIndex" : 0, + "depth" : 3, + "layer" : 3, + "computedX" : 0.0, + "computedY" : 0.0, + "placementX" : 0.0, + "placementY" : 0.0, + "placementWidth" : 132.032, + "placementHeight" : 634.667, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 132.032, + "contentHeight" : 634.667, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Sidebar[0]/SidebarOrnament[0]/OrnamentDisc[0]", + "entityName" : "OrnamentDisc", + "entityKind" : "EllipseNode", + "parentPath" : "LumaStudioInvoice[0]/Sidebar[0]/SidebarOrnament[0]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 17.489, + "computedY" : 527.868, + "placementX" : 17.489, + "placementY" : 527.868, + "placementWidth" : 205.382, + "placementHeight" : 205.382, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 205.382, + "contentHeight" : 205.382, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Sidebar[0]/SidebarOrnament[0]/OrnamentArch[1]", + "entityName" : "OrnamentArch", + "entityKind" : "ShapeNode", + "parentPath" : "LumaStudioInvoice[0]/Sidebar[0]/SidebarOrnament[0]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 15.799, + "computedY" : 0.0, + "placementX" : 15.799, + "placementY" : 0.0, + "placementWidth" : 116.234, + "placementHeight" : 236.588, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 116.234, + "contentHeight" : 236.588, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Sidebar[0]/SidebarOrnament[0]/SvgIcon[2]", + "entityName" : "SvgIcon", + "entityKind" : "LayerStackNode", + "parentPath" : "LumaStudioInvoice[0]/Sidebar[0]/SidebarOrnament[0]", + "childIndex" : 2, + "depth" : 4, + "layer" : 4, + "computedX" : 12.977, + "computedY" : 282.647, + "placementX" : 12.977, + "placementY" : 282.647, + "placementWidth" : 95.244, + "placementHeight" : 95.244, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 95.244, + "contentHeight" : 95.244, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Sidebar[0]/SidebarOrnament[0]/SvgIcon[2]/SvgLayer0[0]", + "entityName" : "SvgLayer0", + "entityKind" : "Path", + "parentPath" : "LumaStudioInvoice[0]/Sidebar[0]/SidebarOrnament[0]/SvgIcon[2]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 12.977, + "computedY" : 282.647, + "placementX" : 12.977, + "placementY" : 282.647, + "placementWidth" : 95.244, + "placementHeight" : 95.244, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 95.244, + "contentHeight" : 95.244, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Sidebar[0]/BrandBlock[1]", + "entityName" : "BrandBlock", + "entityKind" : "ShapeNode", + "parentPath" : "LumaStudioInvoice[0]/Sidebar[0]", + "childIndex" : 1, + "depth" : 3, + "layer" : 3, + "computedX" : 0.0, + "computedY" : 634.667, + "placementX" : 0.0, + "placementY" : 634.667, + "placementWidth" : 132.032, + "placementHeight" : 207.223, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 132.032, + "contentHeight" : 207.223, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Sidebar[0]/BrandLockup[2]", + "entityName" : "BrandLockup", + "entityKind" : "ShapeContainerNode", + "parentPath" : "LumaStudioInvoice[0]/Sidebar[0]", + "childIndex" : 2, + "depth" : 3, + "layer" : 3, + "computedX" : 0.0, + "computedY" : 634.667, + "placementX" : 0.0, + "placementY" : 634.667, + "placementWidth" : 132.032, + "placementHeight" : 207.223, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 132.032, + "contentHeight" : 207.223, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Sidebar[0]/BrandLockup[2]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "LumaStudioInvoice[0]/Sidebar[0]/BrandLockup[2]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 33.288, + "computedY" : 715.218, + "placementX" : 33.288, + "placementY" : 715.218, + "placementWidth" : 36.655, + "placementHeight" : 88.273, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 36.655, + "contentHeight" : 88.273, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Sidebar[0]/BrandLockup[2]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "LumaStudioInvoice[0]/Sidebar[0]/BrandLockup[2]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 33.288, + "computedY" : 705.462, + "placementX" : 33.288, + "placementY" : 705.462, + "placementWidth" : 68.585, + "placementHeight" : 47.212, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 68.585, + "contentHeight" : 47.212, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Sidebar[0]/BrandLockup[2]/LockupRule[2]", + "entityName" : "LockupRule", + "entityKind" : "LineNode", + "parentPath" : "LumaStudioInvoice[0]/Sidebar[0]/BrandLockup[2]", + "childIndex" : 2, + "depth" : 4, + "layer" : 4, + "computedX" : 33.288, + "computedY" : 704.671, + "placementX" : 33.288, + "placementY" : 704.671, + "placementWidth" : 20.311, + "placementHeight" : 1.7, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 20.311, + "contentHeight" : 1.7, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Sidebar[0]/BrandLockup[2]/Wordmark[3]", + "entityName" : "Wordmark", + "entityKind" : "ParagraphNode", + "parentPath" : "LumaStudioInvoice[0]/Sidebar[0]/BrandLockup[2]", + "childIndex" : 3, + "depth" : 4, + "layer" : 4, + "computedX" : 33.288, + "computedY" : 675.395, + "placementX" : 33.288, + "placementY" : 675.395, + "placementWidth" : 82.984, + "placementHeight" : 9.524, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 82.984, + "contentHeight" : 9.524, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Sidebar[0]/BrandLockup[2]/ParagraphNode[4]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "LumaStudioInvoice[0]/Sidebar[0]/BrandLockup[2]", + "childIndex" : 4, + "depth" : 4, + "layer" : 4, + "computedX" : 33.288, + "computedY" : 661.626, + "placementX" : 33.288, + "placementY" : 661.626, + "placementWidth" : 77.712, + "placementHeight" : 8.047, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 77.712, + "contentHeight" : 8.047, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Masthead[1]", + "entityName" : "Masthead", + "entityKind" : "RowNode", + "parentPath" : "LumaStudioInvoice[0]", + "childIndex" : 1, + "depth" : 2, + "layer" : 2, + "computedX" : 153.474, + "computedY" : 647.091, + "placementX" : 153.474, + "placementY" : 647.091, + "placementWidth" : 402.871, + "placementHeight" : 148.499, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 402.871, + "contentHeight" : 148.499, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Masthead[1]/SupplierHeader[0]", + "entityName" : "SupplierHeader", + "entityKind" : "SectionNode", + "parentPath" : "LumaStudioInvoice[0]/Masthead[1]", + "childIndex" : 0, + "depth" : 3, + "layer" : 3, + "computedX" : 153.474, + "computedY" : 655.01, + "placementX" : 153.474, + "placementY" : 655.01, + "placementWidth" : 236.404, + "placementHeight" : 140.58, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 236.404, + "contentHeight" : 140.58, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 7.334 + } + }, { + "path" : "LumaStudioInvoice[0]/Masthead[1]/SupplierHeader[0]/SupplierName[0]", + "entityName" : "SupplierName", + "entityKind" : "ParagraphNode", + "parentPath" : "LumaStudioInvoice[0]/Masthead[1]/SupplierHeader[0]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 160.808, + "computedY" : 782.164, + "placementX" : 160.808, + "placementY" : 782.164, + "placementWidth" : 94.638, + "placementHeight" : 13.425, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 94.638, + "contentHeight" : 13.425, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Masthead[1]/SupplierHeader[0]/SupplierAddress[1]", + "entityName" : "SupplierAddress", + "entityKind" : "ParagraphNode", + "parentPath" : "LumaStudioInvoice[0]/Masthead[1]/SupplierHeader[0]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 160.808, + "computedY" : 727.228, + "placementX" : 160.808, + "placementY" : 727.228, + "placementWidth" : 85.39, + "placementHeight" : 48.726, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 85.39, + "contentHeight" : 48.726, + "margin" : { + "top" : 6.21, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Masthead[1]/SupplierHeader[0]/ContactRows[2]", + "entityName" : "ContactRows", + "entityKind" : "SectionNode", + "parentPath" : "LumaStudioInvoice[0]/Masthead[1]/SupplierHeader[0]", + "childIndex" : 2, + "depth" : 4, + "layer" : 4, + "computedX" : 160.808, + "computedY" : 674.232, + "placementX" : 160.808, + "placementY" : 674.232, + "placementWidth" : 229.071, + "placementHeight" : 43.184, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 229.071, + "contentHeight" : 43.184, + "margin" : { + "top" : 9.812, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Masthead[1]/SupplierHeader[0]/ContactRows[2]/ContactRow[0]", + "entityName" : "ContactRow", + "entityKind" : "ShapeContainerNode", + "parentPath" : "LumaStudioInvoice[0]/Masthead[1]/SupplierHeader[0]/ContactRows[2]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 160.808, + "computedY" : 703.022, + "placementX" : 160.808, + "placementY" : 703.022, + "placementWidth" : 229.071, + "placementHeight" : 14.395, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 229.071, + "contentHeight" : 14.395, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Masthead[1]/SupplierHeader[0]/ContactRows[2]/ContactRow[0]/SvgIcon[0]", + "entityName" : "SvgIcon", + "entityKind" : "LayerStackNode", + "parentPath" : "LumaStudioInvoice[0]/Masthead[1]/SupplierHeader[0]/ContactRows[2]/ContactRow[0]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 160.808, + "computedY" : 705.919, + "placementX" : 160.808, + "placementY" : 705.919, + "placementWidth" : 8.6, + "placementHeight" : 8.6, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 8.6, + "contentHeight" : 8.6, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Masthead[1]/SupplierHeader[0]/ContactRows[2]/ContactRow[0]/SvgIcon[0]/SvgLayer0[0]", + "entityName" : "SvgLayer0", + "entityKind" : "Path", + "parentPath" : "LumaStudioInvoice[0]/Masthead[1]/SupplierHeader[0]/ContactRows[2]/ContactRow[0]/SvgIcon[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 160.808, + "computedY" : 705.919, + "placementX" : 160.808, + "placementY" : 705.919, + "placementWidth" : 8.6, + "placementHeight" : 8.6, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 8.6, + "contentHeight" : 8.6, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Masthead[1]/SupplierHeader[0]/ContactRows[2]/ContactRow[0]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "LumaStudioInvoice[0]/Masthead[1]/SupplierHeader[0]/ContactRows[2]/ContactRow[0]", + "childIndex" : 1, + "depth" : 6, + "layer" : 6, + "computedX" : 181.808, + "computedY" : 704.482, + "placementX" : 181.808, + "placementY" : 704.482, + "placementWidth" : 78.706, + "placementHeight" : 11.475, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 78.706, + "contentHeight" : 11.475, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Masthead[1]/SupplierHeader[0]/ContactRows[2]/ContactRow[1]", + "entityName" : "ContactRow", + "entityKind" : "ShapeContainerNode", + "parentPath" : "LumaStudioInvoice[0]/Masthead[1]/SupplierHeader[0]/ContactRows[2]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 160.808, + "computedY" : 688.627, + "placementX" : 160.808, + "placementY" : 688.627, + "placementWidth" : 229.071, + "placementHeight" : 14.395, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 229.071, + "contentHeight" : 14.395, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Masthead[1]/SupplierHeader[0]/ContactRows[2]/ContactRow[1]/SvgIcon[0]", + "entityName" : "SvgIcon", + "entityKind" : "LayerStackNode", + "parentPath" : "LumaStudioInvoice[0]/Masthead[1]/SupplierHeader[0]/ContactRows[2]/ContactRow[1]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 160.808, + "computedY" : 691.524, + "placementX" : 160.808, + "placementY" : 691.524, + "placementWidth" : 8.6, + "placementHeight" : 8.6, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 8.6, + "contentHeight" : 8.6, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Masthead[1]/SupplierHeader[0]/ContactRows[2]/ContactRow[1]/SvgIcon[0]/SvgLayer0[0]", + "entityName" : "SvgLayer0", + "entityKind" : "Path", + "parentPath" : "LumaStudioInvoice[0]/Masthead[1]/SupplierHeader[0]/ContactRows[2]/ContactRow[1]/SvgIcon[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 160.808, + "computedY" : 691.524, + "placementX" : 160.808, + "placementY" : 691.524, + "placementWidth" : 8.6, + "placementHeight" : 8.6, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 8.6, + "contentHeight" : 8.6, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Masthead[1]/SupplierHeader[0]/ContactRows[2]/ContactRow[1]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "LumaStudioInvoice[0]/Masthead[1]/SupplierHeader[0]/ContactRows[2]/ContactRow[1]", + "childIndex" : 1, + "depth" : 6, + "layer" : 6, + "computedX" : 181.808, + "computedY" : 690.087, + "placementX" : 181.808, + "placementY" : 690.087, + "placementWidth" : 95.852, + "placementHeight" : 11.475, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 95.852, + "contentHeight" : 11.475, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Masthead[1]/SupplierHeader[0]/ContactRows[2]/ContactRow[2]", + "entityName" : "ContactRow", + "entityKind" : "ShapeContainerNode", + "parentPath" : "LumaStudioInvoice[0]/Masthead[1]/SupplierHeader[0]/ContactRows[2]", + "childIndex" : 2, + "depth" : 5, + "layer" : 5, + "computedX" : 160.808, + "computedY" : 674.232, + "placementX" : 160.808, + "placementY" : 674.232, + "placementWidth" : 229.071, + "placementHeight" : 14.395, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 229.071, + "contentHeight" : 14.395, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Masthead[1]/SupplierHeader[0]/ContactRows[2]/ContactRow[2]/SvgIcon[0]", + "entityName" : "SvgIcon", + "entityKind" : "LayerStackNode", + "parentPath" : "LumaStudioInvoice[0]/Masthead[1]/SupplierHeader[0]/ContactRows[2]/ContactRow[2]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 160.808, + "computedY" : 677.129, + "placementX" : 160.808, + "placementY" : 677.129, + "placementWidth" : 8.6, + "placementHeight" : 8.6, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 8.6, + "contentHeight" : 8.6, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Masthead[1]/SupplierHeader[0]/ContactRows[2]/ContactRow[2]/SvgIcon[0]/SvgLayer0[0]", + "entityName" : "SvgLayer0", + "entityKind" : "Path", + "parentPath" : "LumaStudioInvoice[0]/Masthead[1]/SupplierHeader[0]/ContactRows[2]/ContactRow[2]/SvgIcon[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 160.808, + "computedY" : 677.129, + "placementX" : 160.808, + "placementY" : 677.129, + "placementWidth" : 8.6, + "placementHeight" : 8.6, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 8.6, + "contentHeight" : 8.6, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Masthead[1]/SupplierHeader[0]/ContactRows[2]/ContactRow[2]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "LumaStudioInvoice[0]/Masthead[1]/SupplierHeader[0]/ContactRows[2]/ContactRow[2]", + "childIndex" : 1, + "depth" : 6, + "layer" : 6, + "computedX" : 181.808, + "computedY" : 675.692, + "placementX" : 181.808, + "placementY" : 675.692, + "placementWidth" : 91.105, + "placementHeight" : 11.475, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 91.105, + "contentHeight" : 11.475, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Masthead[1]/SupplierHeader[0]/Registration[3]", + "entityName" : "Registration", + "entityKind" : "ParagraphNode", + "parentPath" : "LumaStudioInvoice[0]/Masthead[1]/SupplierHeader[0]", + "childIndex" : 3, + "depth" : 4, + "layer" : 4, + "computedX" : 160.808, + "computedY" : 655.01, + "placementX" : 160.808, + "placementY" : 655.01, + "placementWidth" : 148.091, + "placementHeight" : 8.79, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 148.091, + "contentHeight" : 8.79, + "margin" : { + "top" : 10.433, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Masthead[1]/InvoiceHeader[1]", + "entityName" : "InvoiceHeader", + "entityKind" : "SectionNode", + "parentPath" : "LumaStudioInvoice[0]/Masthead[1]", + "childIndex" : 1, + "depth" : 3, + "layer" : 3, + "computedX" : 389.878, + "computedY" : 647.091, + "placementX" : 389.878, + "placementY" : 647.091, + "placementWidth" : 166.466, + "placementHeight" : 148.499, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 166.466, + "contentHeight" : 148.499, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Masthead[1]/InvoiceHeader[1]/InvoiceTitleBlock[0]", + "entityName" : "InvoiceTitleBlock", + "entityKind" : "ShapeContainerNode", + "parentPath" : "LumaStudioInvoice[0]/Masthead[1]/InvoiceHeader[1]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 389.878, + "computedY" : 749.89, + "placementX" : 389.878, + "placementY" : 749.89, + "placementWidth" : 166.466, + "placementHeight" : 45.7, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 166.466, + "contentHeight" : 45.7, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 15.3, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Masthead[1]/InvoiceHeader[1]/InvoiceTitleBlock[0]/InvoiceTitle[0]", + "entityName" : "InvoiceTitle", + "entityKind" : "ParagraphNode", + "parentPath" : "LumaStudioInvoice[0]/Masthead[1]/InvoiceHeader[1]/InvoiceTitleBlock[0]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 389.878, + "computedY" : 736.636, + "placementX" : 389.878, + "placementY" : 736.636, + "placementWidth" : 164.751, + "placementHeight" : 55.553, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 164.751, + "contentHeight" : 55.553, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Masthead[1]/InvoiceHeader[1]/InvoiceTitleBlock[0]/TitleRule[1]", + "entityName" : "TitleRule", + "entityKind" : "LineNode", + "parentPath" : "LumaStudioInvoice[0]/Masthead[1]/InvoiceHeader[1]/InvoiceTitleBlock[0]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 389.878, + "computedY" : 749.89, + "placementX" : 389.878, + "placementY" : 749.89, + "placementWidth" : 58.7, + "placementHeight" : 1.7, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 58.7, + "contentHeight" : 1.7, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Masthead[1]/InvoiceHeader[1]/MetadataRow[1]", + "entityName" : "MetadataRow", + "entityKind" : "ShapeContainerNode", + "parentPath" : "LumaStudioInvoice[0]/Masthead[1]/InvoiceHeader[1]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 389.878, + "computedY" : 717.09, + "placementX" : 389.878, + "placementY" : 717.09, + "placementWidth" : 166.466, + "placementHeight" : 17.5, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 166.466, + "contentHeight" : 17.5, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Masthead[1]/InvoiceHeader[1]/MetadataRow[1]/MetaLabel[0]", + "entityName" : "MetaLabel", + "entityKind" : "ParagraphNode", + "parentPath" : "LumaStudioInvoice[0]/Masthead[1]/InvoiceHeader[1]/MetadataRow[1]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 389.878, + "computedY" : 720.957, + "placementX" : 389.878, + "placementY" : 720.957, + "placementWidth" : 57.062, + "placementHeight" : 9.765, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 57.062, + "contentHeight" : 9.765, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Masthead[1]/InvoiceHeader[1]/MetadataRow[1]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "LumaStudioInvoice[0]/Masthead[1]/InvoiceHeader[1]/MetadataRow[1]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 491.989, + "computedY" : 720.103, + "placementX" : 491.989, + "placementY" : 720.103, + "placementWidth" : 57.65, + "placementHeight" : 11.475, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 57.65, + "contentHeight" : 11.475, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Masthead[1]/InvoiceHeader[1]/MetadataRow[2]", + "entityName" : "MetadataRow", + "entityKind" : "ShapeContainerNode", + "parentPath" : "LumaStudioInvoice[0]/Masthead[1]/InvoiceHeader[1]", + "childIndex" : 2, + "depth" : 4, + "layer" : 4, + "computedX" : 389.878, + "computedY" : 699.59, + "placementX" : 389.878, + "placementY" : 699.59, + "placementWidth" : 166.466, + "placementHeight" : 17.5, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 166.466, + "contentHeight" : 17.5, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Masthead[1]/InvoiceHeader[1]/MetadataRow[2]/MetaLabel[0]", + "entityName" : "MetaLabel", + "entityKind" : "ParagraphNode", + "parentPath" : "LumaStudioInvoice[0]/Masthead[1]/InvoiceHeader[1]/MetadataRow[2]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 389.878, + "computedY" : 703.458, + "placementX" : 389.878, + "placementY" : 703.458, + "placementWidth" : 51.552, + "placementHeight" : 9.765, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 51.552, + "contentHeight" : 9.765, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Masthead[1]/InvoiceHeader[1]/MetadataRow[2]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "LumaStudioInvoice[0]/Masthead[1]/InvoiceHeader[1]/MetadataRow[2]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 491.989, + "computedY" : 702.603, + "placementX" : 491.989, + "placementY" : 702.603, + "placementWidth" : 49.641, + "placementHeight" : 11.475, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 49.641, + "contentHeight" : 11.475, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Masthead[1]/InvoiceHeader[1]/MetadataRow[3]", + "entityName" : "MetadataRow", + "entityKind" : "ShapeContainerNode", + "parentPath" : "LumaStudioInvoice[0]/Masthead[1]/InvoiceHeader[1]", + "childIndex" : 3, + "depth" : 4, + "layer" : 4, + "computedX" : 389.878, + "computedY" : 682.09, + "placementX" : 389.878, + "placementY" : 682.09, + "placementWidth" : 166.466, + "placementHeight" : 17.5, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 166.466, + "contentHeight" : 17.5, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Masthead[1]/InvoiceHeader[1]/MetadataRow[3]/MetaLabel[0]", + "entityName" : "MetaLabel", + "entityKind" : "ParagraphNode", + "parentPath" : "LumaStudioInvoice[0]/Masthead[1]/InvoiceHeader[1]/MetadataRow[3]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 389.878, + "computedY" : 685.958, + "placementX" : 389.878, + "placementY" : 685.958, + "placementWidth" : 43.963, + "placementHeight" : 9.765, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 43.963, + "contentHeight" : 9.765, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Masthead[1]/InvoiceHeader[1]/MetadataRow[3]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "LumaStudioInvoice[0]/Masthead[1]/InvoiceHeader[1]/MetadataRow[3]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 491.989, + "computedY" : 685.103, + "placementX" : 491.989, + "placementY" : 685.103, + "placementWidth" : 50.393, + "placementHeight" : 11.475, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 50.393, + "contentHeight" : 11.475, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Masthead[1]/InvoiceHeader[1]/MetadataRow[4]", + "entityName" : "MetadataRow", + "entityKind" : "ShapeContainerNode", + "parentPath" : "LumaStudioInvoice[0]/Masthead[1]/InvoiceHeader[1]", + "childIndex" : 4, + "depth" : 4, + "layer" : 4, + "computedX" : 389.878, + "computedY" : 664.591, + "placementX" : 389.878, + "placementY" : 664.591, + "placementWidth" : 166.466, + "placementHeight" : 17.5, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 166.466, + "contentHeight" : 17.5, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Masthead[1]/InvoiceHeader[1]/MetadataRow[4]/MetaLabel[0]", + "entityName" : "MetaLabel", + "entityKind" : "ParagraphNode", + "parentPath" : "LumaStudioInvoice[0]/Masthead[1]/InvoiceHeader[1]/MetadataRow[4]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 389.878, + "computedY" : 668.458, + "placementX" : 389.878, + "placementY" : 668.458, + "placementWidth" : 75.89, + "placementHeight" : 9.765, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 75.89, + "contentHeight" : 9.765, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Masthead[1]/InvoiceHeader[1]/MetadataRow[4]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "LumaStudioInvoice[0]/Masthead[1]/InvoiceHeader[1]/MetadataRow[4]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 491.989, + "computedY" : 667.603, + "placementX" : 491.989, + "placementY" : 667.603, + "placementWidth" : 29.873, + "placementHeight" : 11.475, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 29.873, + "contentHeight" : 11.475, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Masthead[1]/InvoiceHeader[1]/MetadataRow[5]", + "entityName" : "MetadataRow", + "entityKind" : "ShapeContainerNode", + "parentPath" : "LumaStudioInvoice[0]/Masthead[1]/InvoiceHeader[1]", + "childIndex" : 5, + "depth" : 4, + "layer" : 4, + "computedX" : 389.878, + "computedY" : 647.091, + "placementX" : 389.878, + "placementY" : 647.091, + "placementWidth" : 166.466, + "placementHeight" : 17.5, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 166.466, + "contentHeight" : 17.5, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Masthead[1]/InvoiceHeader[1]/MetadataRow[5]/MetaLabel[0]", + "entityName" : "MetaLabel", + "entityKind" : "ParagraphNode", + "parentPath" : "LumaStudioInvoice[0]/Masthead[1]/InvoiceHeader[1]/MetadataRow[5]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 389.878, + "computedY" : 650.958, + "placementX" : 389.878, + "placementY" : 650.958, + "placementWidth" : 46.267, + "placementHeight" : 9.765, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 46.267, + "contentHeight" : 9.765, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Masthead[1]/InvoiceHeader[1]/MetadataRow[5]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "LumaStudioInvoice[0]/Masthead[1]/InvoiceHeader[1]/MetadataRow[5]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 491.989, + "computedY" : 650.103, + "placementX" : 491.989, + "placementY" : 650.103, + "placementWidth" : 15.905, + "placementHeight" : 11.475, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 15.905, + "contentHeight" : 11.475, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/HeaderRule[2]", + "entityName" : "HeaderRule", + "entityKind" : "LineNode", + "parentPath" : "LumaStudioInvoice[0]", + "childIndex" : 2, + "depth" : 2, + "layer" : 2, + "computedX" : 160.808, + "computedY" : 633.091, + "placementX" : 160.808, + "placementY" : 633.091, + "placementWidth" : 395.537, + "placementHeight" : 1.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 395.537, + "contentHeight" : 1.0, + "margin" : { + "top" : 13.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 7.334 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Parties[3]", + "entityName" : "Parties", + "entityKind" : "RowNode", + "parentPath" : "LumaStudioInvoice[0]", + "childIndex" : 3, + "depth" : 2, + "layer" : 2, + "computedX" : 153.474, + "computedY" : 532.149, + "placementX" : 153.474, + "placementY" : 532.149, + "placementWidth" : 402.871, + "placementHeight" : 88.942, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 402.871, + "contentHeight" : 88.942, + "margin" : { + "top" : 12.0, + "right" : 0.0, + "bottom" : 12.9, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Parties[3]/BILLTO[0]", + "entityName" : "BILLTO", + "entityKind" : "SectionNode", + "parentPath" : "LumaStudioInvoice[0]/Parties[3]", + "childIndex" : 0, + "depth" : 3, + "layer" : 3, + "computedX" : 153.474, + "computedY" : 532.149, + "placementX" : 153.474, + "placementY" : 532.149, + "placementWidth" : 103.12, + "placementHeight" : 88.942, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 103.12, + "contentHeight" : 88.942, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 7.334 + } + }, { + "path" : "LumaStudioInvoice[0]/Parties[3]/BILLTO[0]/PartyHeading[0]", + "entityName" : "PartyHeading", + "entityKind" : "ParagraphNode", + "parentPath" : "LumaStudioInvoice[0]/Parties[3]/BILLTO[0]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 160.808, + "computedY" : 611.326, + "placementX" : 160.808, + "placementY" : 611.326, + "placementWidth" : 33.342, + "placementHeight" : 9.765, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 33.342, + "contentHeight" : 9.765, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Parties[3]/BILLTO[0]/PartyAddress[1]", + "entityName" : "PartyAddress", + "entityKind" : "ParagraphNode", + "parentPath" : "LumaStudioInvoice[0]/Parties[3]/BILLTO[0]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 160.808, + "computedY" : 532.149, + "placementX" : 160.808, + "placementY" : 532.149, + "placementWidth" : 95.786, + "placementHeight" : 70.235, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 95.786, + "contentHeight" : 70.235, + "margin" : { + "top" : 8.942, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Parties[3]/SHIPTO[1]", + "entityName" : "SHIPTO", + "entityKind" : "SectionNode", + "parentPath" : "LumaStudioInvoice[0]/Parties[3]", + "childIndex" : 1, + "depth" : 3, + "layer" : 3, + "computedX" : 344.757, + "computedY" : 532.149, + "placementX" : 344.757, + "placementY" : 532.149, + "placementWidth" : 133.88, + "placementHeight" : 88.942, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 133.88, + "contentHeight" : 88.942, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 34.4 + } + }, { + "path" : "LumaStudioInvoice[0]/Parties[3]/SHIPTO[1]/PartyHeading[0]", + "entityName" : "PartyHeading", + "entityKind" : "ParagraphNode", + "parentPath" : "LumaStudioInvoice[0]/Parties[3]/SHIPTO[1]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 379.157, + "computedY" : 611.326, + "placementX" : 379.157, + "placementY" : 611.326, + "placementWidth" : 35.173, + "placementHeight" : 9.765, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 35.173, + "contentHeight" : 9.765, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Parties[3]/SHIPTO[1]/PartyAddress[1]", + "entityName" : "PartyAddress", + "entityKind" : "ParagraphNode", + "parentPath" : "LumaStudioInvoice[0]/Parties[3]/SHIPTO[1]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 379.157, + "computedY" : 532.149, + "placementX" : 379.157, + "placementY" : 532.149, + "placementWidth" : 99.48, + "placementHeight" : 70.235, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 99.48, + "contentHeight" : 70.235, + "margin" : { + "top" : 8.942, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/LineItems[4]", + "entityName" : "LineItems", + "entityKind" : "TableNode", + "parentPath" : "LumaStudioInvoice[0]", + "childIndex" : 4, + "depth" : 2, + "layer" : 2, + "computedX" : 153.474, + "computedY" : 99.964, + "placementX" : 153.474, + "placementY" : 99.964, + "placementWidth" : 402.871, + "placementHeight" : 1011.964, + "startPage" : 0, + "endPage" : 1, + "contentWidth" : 402.871, + "contentHeight" : 1011.964, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Totals[5]", + "entityName" : "Totals", + "entityKind" : "SectionNode", + "parentPath" : "LumaStudioInvoice[0]", + "childIndex" : 5, + "depth" : 2, + "layer" : 2, + "computedX" : 306.404, + "computedY" : 108.172, + "placementX" : 306.404, + "placementY" : 108.172, + "placementWidth" : 249.941, + "placementHeight" : 66.174, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 249.941, + "contentHeight" : 66.174, + "margin" : { + "top" : 4.4, + "right" : 0.0, + "bottom" : 0.0, + "left" : 152.93 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Totals[5]/TotalsRow[0]", + "entityName" : "TotalsRow", + "entityKind" : "ShapeContainerNode", + "parentPath" : "LumaStudioInvoice[0]/Totals[5]", + "childIndex" : 0, + "depth" : 3, + "layer" : 3, + "computedX" : 306.404, + "computedY" : 156.237, + "placementX" : 306.404, + "placementY" : 156.237, + "placementWidth" : 249.941, + "placementHeight" : 18.108, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 249.941, + "contentHeight" : 18.108, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Totals[5]/TotalsRow[0]/TotalsLabel[0]", + "entityName" : "TotalsLabel", + "entityKind" : "ParagraphNode", + "parentPath" : "LumaStudioInvoice[0]/Totals[5]/TotalsRow[0]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 348.204, + "computedY" : 159.439, + "placementX" : 348.204, + "placementY" : 159.439, + "placementWidth" : 46.299, + "placementHeight" : 11.704, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 46.299, + "contentHeight" : 11.704, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Totals[5]/TotalsRow[0]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "LumaStudioInvoice[0]/Totals[5]/TotalsRow[0]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 293.404, + "computedY" : 159.439, + "placementX" : 293.404, + "placementY" : 159.439, + "placementWidth" : 249.941, + "placementHeight" : 11.704, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 249.941, + "contentHeight" : 11.704, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Totals[5]/TotalsRow[1]", + "entityName" : "TotalsRow", + "entityKind" : "ShapeContainerNode", + "parentPath" : "LumaStudioInvoice[0]/Totals[5]", + "childIndex" : 1, + "depth" : 3, + "layer" : 3, + "computedX" : 306.404, + "computedY" : 138.129, + "placementX" : 306.404, + "placementY" : 138.129, + "placementWidth" : 249.941, + "placementHeight" : 18.108, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 249.941, + "contentHeight" : 18.108, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Totals[5]/TotalsRow[1]/TotalsLabel[0]", + "entityName" : "TotalsLabel", + "entityKind" : "ParagraphNode", + "parentPath" : "LumaStudioInvoice[0]/Totals[5]/TotalsRow[1]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 348.204, + "computedY" : 141.331, + "placementX" : 348.204, + "placementY" : 141.331, + "placementWidth" : 45.982, + "placementHeight" : 11.704, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 45.982, + "contentHeight" : 11.704, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Totals[5]/TotalsRow[1]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "LumaStudioInvoice[0]/Totals[5]/TotalsRow[1]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 293.404, + "computedY" : 141.331, + "placementX" : 293.404, + "placementY" : 141.331, + "placementWidth" : 249.941, + "placementHeight" : 11.704, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 249.941, + "contentHeight" : 11.704, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Totals[5]/TotalDueBand[2]", + "entityName" : "TotalDueBand", + "entityKind" : "ShapeContainerNode", + "parentPath" : "LumaStudioInvoice[0]/Totals[5]", + "childIndex" : 2, + "depth" : 3, + "layer" : 3, + "computedX" : 306.404, + "computedY" : 108.172, + "placementX" : 306.404, + "placementY" : 108.172, + "placementWidth" : 249.941, + "placementHeight" : 27.1, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 249.941, + "contentHeight" : 27.1, + "margin" : { + "top" : 2.857, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Totals[5]/TotalDueBand[2]/TotalsLabel[0]", + "entityName" : "TotalsLabel", + "entityKind" : "ParagraphNode", + "parentPath" : "LumaStudioInvoice[0]/Totals[5]/TotalDueBand[2]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 348.204, + "computedY" : 115.468, + "placementX" : 348.204, + "placementY" : 115.468, + "placementWidth" : 52.971, + "placementHeight" : 12.507, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 52.971, + "contentHeight" : 12.507, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Totals[5]/TotalDueBand[2]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "LumaStudioInvoice[0]/Totals[5]/TotalDueBand[2]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 293.404, + "computedY" : 110.431, + "placementX" : 293.404, + "placementY" : 110.431, + "placementWidth" : 249.941, + "placementHeight" : 22.582, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 249.941, + "contentHeight" : 22.582, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/FooterRule[6]", + "entityName" : "FooterRule", + "entityKind" : "LineNode", + "parentPath" : "LumaStudioInvoice[0]", + "childIndex" : 6, + "depth" : 2, + "layer" : 2, + "computedX" : 153.474, + "computedY" : 105.172, + "placementX" : 153.474, + "placementY" : 105.172, + "placementWidth" : 402.871, + "placementHeight" : 1.0, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 402.871, + "contentHeight" : 1.0, + "margin" : { + "top" : 2.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Closing[7]", + "entityName" : "Closing", + "entityKind" : "RowNode", + "parentPath" : "LumaStudioInvoice[0]", + "childIndex" : 7, + "depth" : 2, + "layer" : 2, + "computedX" : 153.474, + "computedY" : 675.411, + "placementX" : 153.474, + "placementY" : 675.411, + "placementWidth" : 402.871, + "placementHeight" : 108.178, + "startPage" : 2, + "endPage" : 2, + "contentWidth" : 402.871, + "contentHeight" : 108.178, + "margin" : { + "top" : 12.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Closing[7]/Notes[0]", + "entityName" : "Notes", + "entityKind" : "SectionNode", + "parentPath" : "LumaStudioInvoice[0]/Closing[7]", + "childIndex" : 0, + "depth" : 3, + "layer" : 3, + "computedX" : 153.474, + "computedY" : 687.309, + "placementX" : 153.474, + "placementY" : 687.309, + "placementWidth" : 167.03, + "placementHeight" : 96.281, + "startPage" : 2, + "endPage" : 2, + "contentWidth" : 167.03, + "contentHeight" : 96.281, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Closing[7]/Notes[0]/SectionHead[0]", + "entityName" : "SectionHead", + "entityKind" : "ShapeContainerNode", + "parentPath" : "LumaStudioInvoice[0]/Closing[7]/Notes[0]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 153.474, + "computedY" : 758.79, + "placementX" : 153.474, + "placementY" : 758.79, + "placementWidth" : 167.03, + "placementHeight" : 24.8, + "startPage" : 2, + "endPage" : 2, + "contentWidth" : 167.03, + "contentHeight" : 24.8, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Closing[7]/Notes[0]/SectionHead[0]/Disc-notes[0]", + "entityName" : "Disc-notes", + "entityKind" : "LayerStackNode", + "parentPath" : "LumaStudioInvoice[0]/Closing[7]/Notes[0]/SectionHead[0]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 153.474, + "computedY" : 758.79, + "placementX" : 153.474, + "placementY" : 758.79, + "placementWidth" : 24.8, + "placementHeight" : 24.8, + "startPage" : 2, + "endPage" : 2, + "contentWidth" : 24.8, + "contentHeight" : 24.8, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Closing[7]/Notes[0]/SectionHead[0]/Disc-notes[0]/DiscFill[0]", + "entityName" : "DiscFill", + "entityKind" : "EllipseNode", + "parentPath" : "LumaStudioInvoice[0]/Closing[7]/Notes[0]/SectionHead[0]/Disc-notes[0]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 153.474, + "computedY" : 758.79, + "placementX" : 153.474, + "placementY" : 758.79, + "placementWidth" : 24.8, + "placementHeight" : 24.8, + "startPage" : 2, + "endPage" : 2, + "contentWidth" : 24.8, + "contentHeight" : 24.8, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Closing[7]/Notes[0]/SectionHead[0]/Disc-notes[0]/SvgIcon[1]", + "entityName" : "SvgIcon", + "entityKind" : "LayerStackNode", + "parentPath" : "LumaStudioInvoice[0]/Closing[7]/Notes[0]/SectionHead[0]/Disc-notes[0]", + "childIndex" : 1, + "depth" : 6, + "layer" : 6, + "computedX" : 159.874, + "computedY" : 765.19, + "placementX" : 159.874, + "placementY" : 765.19, + "placementWidth" : 12.0, + "placementHeight" : 12.0, + "startPage" : 2, + "endPage" : 2, + "contentWidth" : 12.0, + "contentHeight" : 12.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Closing[7]/Notes[0]/SectionHead[0]/Disc-notes[0]/SvgIcon[1]/SvgLayer0[0]", + "entityName" : "SvgLayer0", + "entityKind" : "Path", + "parentPath" : "LumaStudioInvoice[0]/Closing[7]/Notes[0]/SectionHead[0]/Disc-notes[0]/SvgIcon[1]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 159.874, + "computedY" : 765.19, + "placementX" : 159.874, + "placementY" : 765.19, + "placementWidth" : 12.0, + "placementHeight" : 12.0, + "startPage" : 2, + "endPage" : 2, + "contentWidth" : 12.0, + "contentHeight" : 12.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Closing[7]/Notes[0]/SectionHead[0]/SectionHeading[1]", + "entityName" : "SectionHeading", + "entityKind" : "ParagraphNode", + "parentPath" : "LumaStudioInvoice[0]/Closing[7]/Notes[0]/SectionHead[0]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 188.974, + "computedY" : 766.307, + "placementX" : 188.974, + "placementY" : 766.307, + "placementWidth" : 28.177, + "placementHeight" : 9.765, + "startPage" : 2, + "endPage" : 2, + "contentWidth" : 28.177, + "contentHeight" : 9.765, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Closing[7]/Notes[0]/NotesBody[1]", + "entityName" : "NotesBody", + "entityKind" : "SectionNode", + "parentPath" : "LumaStudioInvoice[0]/Closing[7]/Notes[0]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 153.474, + "computedY" : 687.309, + "placementX" : 153.474, + "placementY" : 687.309, + "placementWidth" : 156.503, + "placementHeight" : 69.245, + "startPage" : 2, + "endPage" : 2, + "contentWidth" : 156.503, + "contentHeight" : 69.245, + "margin" : { + "top" : 2.236, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 35.5 + } + }, { + "path" : "LumaStudioInvoice[0]/Closing[7]/Notes[0]/NotesBody[1]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "LumaStudioInvoice[0]/Closing[7]/Notes[0]/NotesBody[1]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 188.974, + "computedY" : 726.589, + "placementX" : 188.974, + "placementY" : 726.589, + "placementWidth" : 103.794, + "placementHeight" : 29.965, + "startPage" : 2, + "endPage" : 2, + "contentWidth" : 103.794, + "contentHeight" : 29.965, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Closing[7]/Notes[0]/NotesBody[1]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "LumaStudioInvoice[0]/Closing[7]/Notes[0]/NotesBody[1]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 188.974, + "computedY" : 687.309, + "placementX" : 188.974, + "placementY" : 687.309, + "placementWidth" : 121.003, + "placementHeight" : 29.965, + "startPage" : 2, + "endPage" : 2, + "contentWidth" : 121.003, + "contentHeight" : 29.965, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Closing[7]/PaymentDetails[1]", + "entityName" : "PaymentDetails", + "entityKind" : "SectionNode", + "parentPath" : "LumaStudioInvoice[0]/Closing[7]", + "childIndex" : 1, + "depth" : 3, + "layer" : 3, + "computedX" : 320.504, + "computedY" : 675.411, + "placementX" : 320.504, + "placementY" : 675.411, + "placementWidth" : 235.84, + "placementHeight" : 108.178, + "startPage" : 2, + "endPage" : 2, + "contentWidth" : 235.84, + "contentHeight" : 108.178, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 24.3 + } + }, { + "path" : "LumaStudioInvoice[0]/Closing[7]/PaymentDetails[1]/SectionHead[0]", + "entityName" : "SectionHead", + "entityKind" : "ShapeContainerNode", + "parentPath" : "LumaStudioInvoice[0]/Closing[7]/PaymentDetails[1]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 344.804, + "computedY" : 758.79, + "placementX" : 344.804, + "placementY" : 758.79, + "placementWidth" : 211.54, + "placementHeight" : 24.8, + "startPage" : 2, + "endPage" : 2, + "contentWidth" : 211.54, + "contentHeight" : 24.8, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Closing[7]/PaymentDetails[1]/SectionHead[0]/Disc-bank[0]", + "entityName" : "Disc-bank", + "entityKind" : "LayerStackNode", + "parentPath" : "LumaStudioInvoice[0]/Closing[7]/PaymentDetails[1]/SectionHead[0]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 344.804, + "computedY" : 758.79, + "placementX" : 344.804, + "placementY" : 758.79, + "placementWidth" : 24.8, + "placementHeight" : 24.8, + "startPage" : 2, + "endPage" : 2, + "contentWidth" : 24.8, + "contentHeight" : 24.8, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Closing[7]/PaymentDetails[1]/SectionHead[0]/Disc-bank[0]/DiscFill[0]", + "entityName" : "DiscFill", + "entityKind" : "EllipseNode", + "parentPath" : "LumaStudioInvoice[0]/Closing[7]/PaymentDetails[1]/SectionHead[0]/Disc-bank[0]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 344.804, + "computedY" : 758.79, + "placementX" : 344.804, + "placementY" : 758.79, + "placementWidth" : 24.8, + "placementHeight" : 24.8, + "startPage" : 2, + "endPage" : 2, + "contentWidth" : 24.8, + "contentHeight" : 24.8, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Closing[7]/PaymentDetails[1]/SectionHead[0]/Disc-bank[0]/SvgIcon[1]", + "entityName" : "SvgIcon", + "entityKind" : "LayerStackNode", + "parentPath" : "LumaStudioInvoice[0]/Closing[7]/PaymentDetails[1]/SectionHead[0]/Disc-bank[0]", + "childIndex" : 1, + "depth" : 6, + "layer" : 6, + "computedX" : 351.204, + "computedY" : 765.19, + "placementX" : 351.204, + "placementY" : 765.19, + "placementWidth" : 12.0, + "placementHeight" : 12.0, + "startPage" : 2, + "endPage" : 2, + "contentWidth" : 12.0, + "contentHeight" : 12.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Closing[7]/PaymentDetails[1]/SectionHead[0]/Disc-bank[0]/SvgIcon[1]/SvgLayer0[0]", + "entityName" : "SvgLayer0", + "entityKind" : "Path", + "parentPath" : "LumaStudioInvoice[0]/Closing[7]/PaymentDetails[1]/SectionHead[0]/Disc-bank[0]/SvgIcon[1]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 351.204, + "computedY" : 765.19, + "placementX" : 351.204, + "placementY" : 765.19, + "placementWidth" : 12.0, + "placementHeight" : 12.0, + "startPage" : 2, + "endPage" : 2, + "contentWidth" : 12.0, + "contentHeight" : 12.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Closing[7]/PaymentDetails[1]/SectionHead[0]/SectionHeading[1]", + "entityName" : "SectionHeading", + "entityKind" : "ParagraphNode", + "parentPath" : "LumaStudioInvoice[0]/Closing[7]/PaymentDetails[1]/SectionHead[0]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 380.304, + "computedY" : 766.307, + "placementX" : 380.304, + "placementY" : 766.307, + "placementWidth" : 82.727, + "placementHeight" : 9.765, + "startPage" : 2, + "endPage" : 2, + "contentWidth" : 82.727, + "contentHeight" : 9.765, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Closing[7]/PaymentDetails[1]/PaymentBody[1]", + "entityName" : "PaymentBody", + "entityKind" : "SectionNode", + "parentPath" : "LumaStudioInvoice[0]/Closing[7]/PaymentDetails[1]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 344.804, + "computedY" : 675.411, + "placementX" : 344.804, + "placementY" : 675.411, + "placementWidth" : 211.54, + "placementHeight" : 81.143, + "startPage" : 2, + "endPage" : 2, + "contentWidth" : 211.54, + "contentHeight" : 81.143, + "margin" : { + "top" : 2.236, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 35.5 + } + }, { + "path" : "LumaStudioInvoice[0]/Closing[7]/PaymentDetails[1]/PaymentBody[1]/PaymentInstruction[0]", + "entityName" : "PaymentInstruction", + "entityKind" : "ParagraphNode", + "parentPath" : "LumaStudioInvoice[0]/Closing[7]/PaymentDetails[1]/PaymentBody[1]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 380.304, + "computedY" : 748.58, + "placementX" : 380.304, + "placementY" : 748.58, + "placementWidth" : 121.183, + "placementHeight" : 7.974, + "startPage" : 2, + "endPage" : 2, + "contentWidth" : 121.183, + "contentHeight" : 7.974, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Closing[7]/PaymentDetails[1]/PaymentBody[1]/AccountHolder[1]", + "entityName" : "AccountHolder", + "entityKind" : "ParagraphNode", + "parentPath" : "LumaStudioInvoice[0]/Closing[7]/PaymentDetails[1]/PaymentBody[1]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 380.304, + "computedY" : 733.226, + "placementX" : 380.304, + "placementY" : 733.226, + "placementWidth" : 83.026, + "placementHeight" : 9.765, + "startPage" : 2, + "endPage" : 2, + "contentWidth" : 83.026, + "contentHeight" : 9.765, + "margin" : { + "top" : 5.589, + "right" : 0.0, + "bottom" : 6.831, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Closing[7]/PaymentDetails[1]/PaymentBody[1]/PaymentField[2]", + "entityName" : "PaymentField", + "entityKind" : "ShapeContainerNode", + "parentPath" : "LumaStudioInvoice[0]/Closing[7]/PaymentDetails[1]/PaymentBody[1]", + "childIndex" : 2, + "depth" : 5, + "layer" : 5, + "computedX" : 380.304, + "computedY" : 716.199, + "placementX" : 380.304, + "placementY" : 716.199, + "placementWidth" : 176.04, + "placementHeight" : 10.197, + "startPage" : 2, + "endPage" : 2, + "contentWidth" : 176.04, + "contentHeight" : 10.197, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Closing[7]/PaymentDetails[1]/PaymentBody[1]/PaymentField[2]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "LumaStudioInvoice[0]/Closing[7]/PaymentDetails[1]/PaymentBody[1]/PaymentField[2]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 380.304, + "computedY" : 716.902, + "placementX" : 380.304, + "placementY" : 716.902, + "placementWidth" : 17.087, + "placementHeight" : 8.79, + "startPage" : 2, + "endPage" : 2, + "contentWidth" : 17.087, + "contentHeight" : 8.79, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Closing[7]/PaymentDetails[1]/PaymentBody[1]/PaymentField[2]/BankPipe[1]", + "entityName" : "BankPipe", + "entityKind" : "LineNode", + "parentPath" : "LumaStudioInvoice[0]/Closing[7]/PaymentDetails[1]/PaymentBody[1]/PaymentField[2]", + "childIndex" : 1, + "depth" : 6, + "layer" : 6, + "computedX" : 433.904, + "computedY" : 717.932, + "placementX" : 433.904, + "placementY" : 717.932, + "placementWidth" : 1.0, + "placementHeight" : 6.73, + "startPage" : 2, + "endPage" : 2, + "contentWidth" : 1.0, + "contentHeight" : 6.73, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Closing[7]/PaymentDetails[1]/PaymentBody[1]/PaymentField[2]/ParagraphNode[2]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "LumaStudioInvoice[0]/Closing[7]/PaymentDetails[1]/PaymentBody[1]/PaymentField[2]", + "childIndex" : 2, + "depth" : 6, + "layer" : 6, + "computedX" : 444.104, + "computedY" : 716.902, + "placementX" : 444.104, + "placementY" : 716.902, + "placementWidth" : 38.198, + "placementHeight" : 8.79, + "startPage" : 2, + "endPage" : 2, + "contentWidth" : 38.198, + "contentHeight" : 8.79, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Closing[7]/PaymentDetails[1]/PaymentBody[1]/PaymentField[3]", + "entityName" : "PaymentField", + "entityKind" : "ShapeContainerNode", + "parentPath" : "LumaStudioInvoice[0]/Closing[7]/PaymentDetails[1]/PaymentBody[1]", + "childIndex" : 3, + "depth" : 5, + "layer" : 5, + "computedX" : 380.304, + "computedY" : 706.002, + "placementX" : 380.304, + "placementY" : 706.002, + "placementWidth" : 176.04, + "placementHeight" : 10.197, + "startPage" : 2, + "endPage" : 2, + "contentWidth" : 176.04, + "contentHeight" : 10.197, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Closing[7]/PaymentDetails[1]/PaymentBody[1]/PaymentField[3]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "LumaStudioInvoice[0]/Closing[7]/PaymentDetails[1]/PaymentBody[1]/PaymentField[3]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 380.304, + "computedY" : 706.705, + "placementX" : 380.304, + "placementY" : 706.705, + "placementWidth" : 34.245, + "placementHeight" : 8.79, + "startPage" : 2, + "endPage" : 2, + "contentWidth" : 34.245, + "contentHeight" : 8.79, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Closing[7]/PaymentDetails[1]/PaymentBody[1]/PaymentField[3]/BankPipe[1]", + "entityName" : "BankPipe", + "entityKind" : "LineNode", + "parentPath" : "LumaStudioInvoice[0]/Closing[7]/PaymentDetails[1]/PaymentBody[1]/PaymentField[3]", + "childIndex" : 1, + "depth" : 6, + "layer" : 6, + "computedX" : 433.904, + "computedY" : 707.735, + "placementX" : 433.904, + "placementY" : 707.735, + "placementWidth" : 1.0, + "placementHeight" : 6.73, + "startPage" : 2, + "endPage" : 2, + "contentWidth" : 1.0, + "contentHeight" : 6.73, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Closing[7]/PaymentDetails[1]/PaymentBody[1]/PaymentField[3]/ParagraphNode[2]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "LumaStudioInvoice[0]/Closing[7]/PaymentDetails[1]/PaymentBody[1]/PaymentField[3]", + "childIndex" : 2, + "depth" : 6, + "layer" : 6, + "computedX" : 444.104, + "computedY" : 706.705, + "placementX" : 444.104, + "placementY" : 706.705, + "placementWidth" : 26.31, + "placementHeight" : 8.79, + "startPage" : 2, + "endPage" : 2, + "contentWidth" : 26.31, + "contentHeight" : 8.79, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Closing[7]/PaymentDetails[1]/PaymentBody[1]/PaymentField[4]", + "entityName" : "PaymentField", + "entityKind" : "ShapeContainerNode", + "parentPath" : "LumaStudioInvoice[0]/Closing[7]/PaymentDetails[1]/PaymentBody[1]", + "childIndex" : 4, + "depth" : 5, + "layer" : 5, + "computedX" : 380.304, + "computedY" : 695.805, + "placementX" : 380.304, + "placementY" : 695.805, + "placementWidth" : 176.04, + "placementHeight" : 10.197, + "startPage" : 2, + "endPage" : 2, + "contentWidth" : 176.04, + "contentHeight" : 10.197, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Closing[7]/PaymentDetails[1]/PaymentBody[1]/PaymentField[4]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "LumaStudioInvoice[0]/Closing[7]/PaymentDetails[1]/PaymentBody[1]/PaymentField[4]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 380.304, + "computedY" : 696.509, + "placementX" : 380.304, + "placementY" : 696.509, + "placementWidth" : 43.022, + "placementHeight" : 8.79, + "startPage" : 2, + "endPage" : 2, + "contentWidth" : 43.022, + "contentHeight" : 8.79, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Closing[7]/PaymentDetails[1]/PaymentBody[1]/PaymentField[4]/BankPipe[1]", + "entityName" : "BankPipe", + "entityKind" : "LineNode", + "parentPath" : "LumaStudioInvoice[0]/Closing[7]/PaymentDetails[1]/PaymentBody[1]/PaymentField[4]", + "childIndex" : 1, + "depth" : 6, + "layer" : 6, + "computedX" : 433.904, + "computedY" : 697.538, + "placementX" : 433.904, + "placementY" : 697.538, + "placementWidth" : 1.0, + "placementHeight" : 6.73, + "startPage" : 2, + "endPage" : 2, + "contentWidth" : 1.0, + "contentHeight" : 6.73, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Closing[7]/PaymentDetails[1]/PaymentBody[1]/PaymentField[4]/ParagraphNode[2]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "LumaStudioInvoice[0]/Closing[7]/PaymentDetails[1]/PaymentBody[1]/PaymentField[4]", + "childIndex" : 2, + "depth" : 6, + "layer" : 6, + "computedX" : 444.104, + "computedY" : 696.509, + "placementX" : 444.104, + "placementY" : 696.509, + "placementWidth" : 29.205, + "placementHeight" : 8.79, + "startPage" : 2, + "endPage" : 2, + "contentWidth" : 29.205, + "contentHeight" : 8.79, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Closing[7]/PaymentDetails[1]/PaymentBody[1]/PaymentField[5]", + "entityName" : "PaymentField", + "entityKind" : "ShapeContainerNode", + "parentPath" : "LumaStudioInvoice[0]/Closing[7]/PaymentDetails[1]/PaymentBody[1]", + "childIndex" : 5, + "depth" : 5, + "layer" : 5, + "computedX" : 380.304, + "computedY" : 685.608, + "placementX" : 380.304, + "placementY" : 685.608, + "placementWidth" : 176.04, + "placementHeight" : 10.197, + "startPage" : 2, + "endPage" : 2, + "contentWidth" : 176.04, + "contentHeight" : 10.197, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Closing[7]/PaymentDetails[1]/PaymentBody[1]/PaymentField[5]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "LumaStudioInvoice[0]/Closing[7]/PaymentDetails[1]/PaymentBody[1]/PaymentField[5]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 380.304, + "computedY" : 686.312, + "placementX" : 380.304, + "placementY" : 686.312, + "placementWidth" : 15.07, + "placementHeight" : 8.79, + "startPage" : 2, + "endPage" : 2, + "contentWidth" : 15.07, + "contentHeight" : 8.79, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Closing[7]/PaymentDetails[1]/PaymentBody[1]/PaymentField[5]/BankPipe[1]", + "entityName" : "BankPipe", + "entityKind" : "LineNode", + "parentPath" : "LumaStudioInvoice[0]/Closing[7]/PaymentDetails[1]/PaymentBody[1]/PaymentField[5]", + "childIndex" : 1, + "depth" : 6, + "layer" : 6, + "computedX" : 433.904, + "computedY" : 687.342, + "placementX" : 433.904, + "placementY" : 687.342, + "placementWidth" : 1.0, + "placementHeight" : 6.73, + "startPage" : 2, + "endPage" : 2, + "contentWidth" : 1.0, + "contentHeight" : 6.73, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Closing[7]/PaymentDetails[1]/PaymentBody[1]/PaymentField[5]/ParagraphNode[2]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "LumaStudioInvoice[0]/Closing[7]/PaymentDetails[1]/PaymentBody[1]/PaymentField[5]", + "childIndex" : 2, + "depth" : 6, + "layer" : 6, + "computedX" : 444.104, + "computedY" : 686.312, + "placementX" : 444.104, + "placementY" : 686.312, + "placementWidth" : 89.789, + "placementHeight" : 8.79, + "startPage" : 2, + "endPage" : 2, + "contentWidth" : 89.789, + "contentHeight" : 8.79, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Closing[7]/PaymentDetails[1]/PaymentBody[1]/PaymentField[6]", + "entityName" : "PaymentField", + "entityKind" : "ShapeContainerNode", + "parentPath" : "LumaStudioInvoice[0]/Closing[7]/PaymentDetails[1]/PaymentBody[1]", + "childIndex" : 6, + "depth" : 5, + "layer" : 5, + "computedX" : 380.304, + "computedY" : 675.411, + "placementX" : 380.304, + "placementY" : 675.411, + "placementWidth" : 176.04, + "placementHeight" : 10.197, + "startPage" : 2, + "endPage" : 2, + "contentWidth" : 176.04, + "contentHeight" : 10.197, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Closing[7]/PaymentDetails[1]/PaymentBody[1]/PaymentField[6]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "LumaStudioInvoice[0]/Closing[7]/PaymentDetails[1]/PaymentBody[1]/PaymentField[6]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 380.304, + "computedY" : 676.115, + "placementX" : 380.304, + "placementY" : 676.115, + "placementWidth" : 9.771, + "placementHeight" : 8.79, + "startPage" : 2, + "endPage" : 2, + "contentWidth" : 9.771, + "contentHeight" : 8.79, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Closing[7]/PaymentDetails[1]/PaymentBody[1]/PaymentField[6]/BankPipe[1]", + "entityName" : "BankPipe", + "entityKind" : "LineNode", + "parentPath" : "LumaStudioInvoice[0]/Closing[7]/PaymentDetails[1]/PaymentBody[1]/PaymentField[6]", + "childIndex" : 1, + "depth" : 6, + "layer" : 6, + "computedX" : 433.904, + "computedY" : 677.145, + "placementX" : 433.904, + "placementY" : 677.145, + "placementWidth" : 1.0, + "placementHeight" : 6.73, + "startPage" : 2, + "endPage" : 2, + "contentWidth" : 1.0, + "contentHeight" : 6.73, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/Closing[7]/PaymentDetails[1]/PaymentBody[1]/PaymentField[6]/ParagraphNode[2]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "LumaStudioInvoice[0]/Closing[7]/PaymentDetails[1]/PaymentBody[1]/PaymentField[6]", + "childIndex" : 2, + "depth" : 6, + "layer" : 6, + "computedX" : 444.104, + "computedY" : 676.115, + "placementX" : 444.104, + "placementY" : 676.115, + "placementWidth" : 29.918, + "placementHeight" : 8.79, + "startPage" : 2, + "endPage" : 2, + "contentWidth" : 29.918, + "contentHeight" : 8.79, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/ClosingBanner[8]", + "entityName" : "ClosingBanner", + "entityKind" : "SectionNode", + "parentPath" : "LumaStudioInvoice[0]", + "childIndex" : 8, + "depth" : 2, + "layer" : 2, + "computedX" : 153.474, + "computedY" : 666.511, + "placementX" : 153.474, + "placementY" : 666.511, + "placementWidth" : 402.871, + "placementHeight" : 0.3, + "startPage" : 2, + "endPage" : 2, + "contentWidth" : 402.871, + "contentHeight" : 0.3, + "margin" : { + "top" : 8.6, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/ClosingBanner[8]/BannerContent[0]", + "entityName" : "BannerContent", + "entityKind" : "ShapeContainerNode", + "parentPath" : "LumaStudioInvoice[0]/ClosingBanner[8]", + "childIndex" : 0, + "depth" : 3, + "layer" : 3, + "computedX" : 0.0, + "computedY" : 604.133, + "placementX" : 0.0, + "placementY" : 604.133, + "placementWidth" : 595.276, + "placementHeight" : 62.679, + "startPage" : 2, + "endPage" : 2, + "contentWidth" : 595.276, + "contentHeight" : 62.679, + "margin" : { + "top" : 0.0, + "right" : -38.931, + "bottom" : -62.379, + "left" : -153.474 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/ClosingBanner[8]/BannerContent[0]/BannerBand[0]", + "entityName" : "BannerBand", + "entityKind" : "ShapeNode", + "parentPath" : "LumaStudioInvoice[0]/ClosingBanner[8]/BannerContent[0]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 0.0, + "computedY" : 604.133, + "placementX" : 0.0, + "placementY" : 604.133, + "placementWidth" : 595.276, + "placementHeight" : 62.679, + "startPage" : 2, + "endPage" : 2, + "contentWidth" : 595.276, + "contentHeight" : 62.679, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/ClosingBanner[8]/BannerContent[0]/Disc-heart[1]", + "entityName" : "Disc-heart", + "entityKind" : "LayerStackNode", + "parentPath" : "LumaStudioInvoice[0]/ClosingBanner[8]/BannerContent[0]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 32.728, + "computedY" : 618.522, + "placementX" : 32.728, + "placementY" : 618.522, + "placementWidth" : 33.9, + "placementHeight" : 33.9, + "startPage" : 2, + "endPage" : 2, + "contentWidth" : 33.9, + "contentHeight" : 33.9, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/ClosingBanner[8]/BannerContent[0]/Disc-heart[1]/DiscFill[0]", + "entityName" : "DiscFill", + "entityKind" : "EllipseNode", + "parentPath" : "LumaStudioInvoice[0]/ClosingBanner[8]/BannerContent[0]/Disc-heart[1]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 32.728, + "computedY" : 618.522, + "placementX" : 32.728, + "placementY" : 618.522, + "placementWidth" : 33.9, + "placementHeight" : 33.9, + "startPage" : 2, + "endPage" : 2, + "contentWidth" : 33.9, + "contentHeight" : 33.9, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/ClosingBanner[8]/BannerContent[0]/Disc-heart[1]/SvgIcon[1]", + "entityName" : "SvgIcon", + "entityKind" : "LayerStackNode", + "parentPath" : "LumaStudioInvoice[0]/ClosingBanner[8]/BannerContent[0]/Disc-heart[1]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 41.678, + "computedY" : 627.472, + "placementX" : 41.678, + "placementY" : 627.472, + "placementWidth" : 16.0, + "placementHeight" : 16.0, + "startPage" : 2, + "endPage" : 2, + "contentWidth" : 16.0, + "contentHeight" : 16.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/ClosingBanner[8]/BannerContent[0]/Disc-heart[1]/SvgIcon[1]/SvgLayer0[0]", + "entityName" : "SvgLayer0", + "entityKind" : "Path", + "parentPath" : "LumaStudioInvoice[0]/ClosingBanner[8]/BannerContent[0]/Disc-heart[1]/SvgIcon[1]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 41.678, + "computedY" : 627.472, + "placementX" : 41.678, + "placementY" : 627.472, + "placementWidth" : 16.0, + "placementHeight" : 16.0, + "startPage" : 2, + "endPage" : 2, + "contentWidth" : 16.0, + "contentHeight" : 16.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/ClosingBanner[8]/BannerContent[0]/BannerLines[2]", + "entityName" : "BannerLines", + "entityKind" : "SectionNode", + "parentPath" : "LumaStudioInvoice[0]/ClosingBanner[8]/BannerContent[0]", + "childIndex" : 2, + "depth" : 4, + "layer" : 4, + "computedX" : 80.628, + "computedY" : 622.394, + "placementX" : 80.628, + "placementY" : 622.394, + "placementWidth" : 179.909, + "placementHeight" : 26.156, + "startPage" : 2, + "endPage" : 2, + "contentWidth" : 179.909, + "contentHeight" : 26.156, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/ClosingBanner[8]/BannerContent[0]/BannerLines[2]/SignOff[0]", + "entityName" : "SignOff", + "entityKind" : "ParagraphNode", + "parentPath" : "LumaStudioInvoice[0]/ClosingBanner[8]/BannerContent[0]/BannerLines[2]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 80.628, + "computedY" : 636.846, + "placementX" : 80.628, + "placementY" : 636.846, + "placementWidth" : 179.909, + "placementHeight" : 11.704, + "startPage" : 2, + "endPage" : 2, + "contentWidth" : 179.909, + "contentHeight" : 11.704, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "LumaStudioInvoice[0]/ClosingBanner[8]/BannerContent[0]/BannerLines[2]/DueNotice[1]", + "entityName" : "DueNotice", + "entityKind" : "ParagraphNode", + "parentPath" : "LumaStudioInvoice[0]/ClosingBanner[8]/BannerContent[0]/BannerLines[2]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 80.628, + "computedY" : 622.394, + "placementX" : 80.628, + "placementY" : 622.394, + "placementWidth" : 135.165, + "placementHeight" : 11.347, + "startPage" : 2, + "endPage" : 2, + "contentWidth" : 135.165, + "contentHeight" : 11.347, + "margin" : { + "top" : 3.105, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + } ] +} diff --git a/qa/src/test/resources/layout-snapshots/canonical-templates/invoice/merchant_invoice_layout.json b/qa/src/test/resources/layout-snapshots/canonical-templates/invoice/merchant_invoice_layout.json new file mode 100644 index 000000000..9b4d922d5 --- /dev/null +++ b/qa/src/test/resources/layout-snapshots/canonical-templates/invoice/merchant_invoice_layout.json @@ -0,0 +1,5807 @@ +{ + "formatVersion" : "2.0", + "canvas" : { + "pageWidth" : 595.276, + "pageHeight" : 841.89, + "innerWidth" : 554.047, + "innerHeight" : 815.369, + "margin" : { + "top" : 23.135, + "right" : 21.462, + "bottom" : 3.386, + "left" : 19.767 + } + }, + "totalPages" : 1, + "nodes" : [ { + "path" : "MerchantInvoice[0]", + "entityName" : "MerchantInvoice", + "entityKind" : "ContainerNode", + "parentPath" : null, + "childIndex" : 0, + "depth" : 1, + "layer" : 1, + "computedX" : 19.767, + "computedY" : 3.919, + "placementX" : 19.767, + "placementY" : 3.919, + "placementWidth" : 554.047, + "placementHeight" : 814.836, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 554.047, + "contentHeight" : 814.836, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/Masthead[0]", + "entityName" : "Masthead", + "entityKind" : "RowNode", + "parentPath" : "MerchantInvoice[0]", + "childIndex" : 0, + "depth" : 2, + "layer" : 2, + "computedX" : 19.767, + "computedY" : 778.507, + "placementX" : 19.767, + "placementY" : 778.507, + "placementWidth" : 554.047, + "placementHeight" : 40.248, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 554.047, + "contentHeight" : 40.248, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/Masthead[0]/BrandLockup[0]", + "entityName" : "BrandLockup", + "entityKind" : "SectionNode", + "parentPath" : "MerchantInvoice[0]/Masthead[0]", + "childIndex" : 0, + "depth" : 3, + "layer" : 3, + "computedX" : 19.767, + "computedY" : 808.199, + "placementX" : 19.767, + "placementY" : 808.199, + "placementWidth" : 48.662, + "placementHeight" : 10.556, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 48.662, + "contentHeight" : 10.556, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/Masthead[0]/BrandLockup[0]/BrandWordmark[0]", + "entityName" : "BrandWordmark", + "entityKind" : "ParagraphNode", + "parentPath" : "MerchantInvoice[0]/Masthead[0]/BrandLockup[0]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 20.897, + "computedY" : 808.199, + "placementX" : 20.897, + "placementY" : 808.199, + "placementWidth" : 47.532, + "placementHeight" : 10.556, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 47.532, + "contentHeight" : 10.556, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 1.13 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/Masthead[0]/InvoiceTitle[1]", + "entityName" : "InvoiceTitle", + "entityKind" : "ParagraphNode", + "parentPath" : "MerchantInvoice[0]/Masthead[0]", + "childIndex" : 1, + "depth" : 3, + "layer" : 3, + "computedX" : 145.148, + "computedY" : 778.507, + "placementX" : 145.148, + "placementY" : 778.507, + "placementWidth" : 428.666, + "placementHeight" : 36.665, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 428.666, + "contentHeight" : 36.665, + "margin" : { + "top" : 3.583, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/TitleRule[1]", + "entityName" : "TitleRule", + "entityKind" : "LineNode", + "parentPath" : "MerchantInvoice[0]", + "childIndex" : 1, + "depth" : 2, + "layer" : 2, + "computedX" : 542.187, + "computedY" : 771.582, + "placementX" : 542.187, + "placementY" : 771.582, + "placementWidth" : 31.628, + "placementHeight" : 1.467, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 31.628, + "contentHeight" : 1.467, + "margin" : { + "top" : 5.458, + "right" : 0.0, + "bottom" : 0.0, + "left" : 522.419 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/IdentityRow[2]", + "entityName" : "IdentityRow", + "entityKind" : "RowNode", + "parentPath" : "MerchantInvoice[0]", + "childIndex" : 2, + "depth" : 2, + "layer" : 2, + "computedX" : 19.767, + "computedY" : 620.421, + "placementX" : 19.767, + "placementY" : 620.421, + "placementWidth" : 554.047, + "placementHeight" : 139.747, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 554.047, + "contentHeight" : 139.747, + "margin" : { + "top" : 11.414, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/IdentityRow[2]/SupplierColumn[0]", + "entityName" : "SupplierColumn", + "entityKind" : "SectionNode", + "parentPath" : "MerchantInvoice[0]/IdentityRow[2]", + "childIndex" : 0, + "depth" : 3, + "layer" : 3, + "computedX" : 19.767, + "computedY" : 620.421, + "placementX" : 19.767, + "placementY" : 620.421, + "placementWidth" : 137.688, + "placementHeight" : 139.747, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 137.688, + "contentHeight" : 139.747, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/IdentityRow[2]/SupplierColumn[0]/SupplierName[0]", + "entityName" : "SupplierName", + "entityKind" : "ParagraphNode", + "parentPath" : "MerchantInvoice[0]/IdentityRow[2]/SupplierColumn[0]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 19.767, + "computedY" : 744.238, + "placementX" : 19.767, + "placementY" : 744.238, + "placementWidth" : 125.919, + "placementHeight" : 10.556, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 125.919, + "contentHeight" : 10.556, + "margin" : { + "top" : 5.375, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/IdentityRow[2]/SupplierColumn[0]/SupplierAddress[1]", + "entityName" : "SupplierAddress", + "entityKind" : "SectionNode", + "parentPath" : "MerchantInvoice[0]/IdentityRow[2]/SupplierColumn[0]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 19.767, + "computedY" : 699.983, + "placementX" : 19.767, + "placementY" : 699.983, + "placementWidth" : 104.653, + "placementHeight" : 37.075, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 104.653, + "contentHeight" : 37.075, + "margin" : { + "top" : 7.18, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/IdentityRow[2]/SupplierColumn[0]/SupplierAddress[1]/SupplierAddress_0[0]", + "entityName" : "SupplierAddress_0", + "entityKind" : "ParagraphNode", + "parentPath" : "MerchantInvoice[0]/IdentityRow[2]/SupplierColumn[0]/SupplierAddress[1]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 19.767, + "computedY" : 728.197, + "placementX" : 19.767, + "placementY" : 728.197, + "placementWidth" : 104.653, + "placementHeight" : 8.861, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 104.653, + "contentHeight" : 8.861, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/IdentityRow[2]/SupplierColumn[0]/SupplierAddress[1]/SupplierAddress_1[1]", + "entityName" : "SupplierAddress_1", + "entityKind" : "ParagraphNode", + "parentPath" : "MerchantInvoice[0]/IdentityRow[2]/SupplierColumn[0]/SupplierAddress[1]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 19.767, + "computedY" : 714.09, + "placementX" : 19.767, + "placementY" : 714.09, + "placementWidth" : 81.737, + "placementHeight" : 8.861, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 81.737, + "contentHeight" : 8.861, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/IdentityRow[2]/SupplierColumn[0]/SupplierAddress[1]/SupplierAddress_2[2]", + "entityName" : "SupplierAddress_2", + "entityKind" : "ParagraphNode", + "parentPath" : "MerchantInvoice[0]/IdentityRow[2]/SupplierColumn[0]/SupplierAddress[1]", + "childIndex" : 2, + "depth" : 5, + "layer" : 5, + "computedX" : 19.767, + "computedY" : 699.983, + "placementX" : 19.767, + "placementY" : 699.983, + "placementWidth" : 30.474, + "placementHeight" : 8.861, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 30.474, + "contentHeight" : 8.861, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/IdentityRow[2]/SupplierColumn[0]/SupplierContact[2]", + "entityName" : "SupplierContact", + "entityKind" : "SectionNode", + "parentPath" : "MerchantInvoice[0]/IdentityRow[2]/SupplierColumn[0]", + "childIndex" : 2, + "depth" : 4, + "layer" : 4, + "computedX" : 19.767, + "computedY" : 641.102, + "placementX" : 19.767, + "placementY" : 641.102, + "placementWidth" : 119.683, + "placementHeight" : 42.325, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 119.683, + "contentHeight" : 42.325, + "margin" : { + "top" : 16.556, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/IdentityRow[2]/SupplierColumn[0]/SupplierContact[2]/SupplierContactWebsite[0]", + "entityName" : "SupplierContactWebsite", + "entityKind" : "ParagraphNode", + "parentPath" : "MerchantInvoice[0]/IdentityRow[2]/SupplierColumn[0]/SupplierContact[2]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 19.767, + "computedY" : 673.266, + "placementX" : 19.767, + "placementY" : 673.266, + "placementWidth" : 109.543, + "placementHeight" : 10.161, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 109.543, + "contentHeight" : 10.161, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/IdentityRow[2]/SupplierColumn[0]/SupplierContact[2]/SupplierContactEmail[1]", + "entityName" : "SupplierContactEmail", + "entityKind" : "ParagraphNode", + "parentPath" : "MerchantInvoice[0]/IdentityRow[2]/SupplierColumn[0]/SupplierContact[2]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 19.767, + "computedY" : 657.184, + "placementX" : 19.767, + "placementY" : 657.184, + "placementWidth" : 119.683, + "placementHeight" : 10.161, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 119.683, + "contentHeight" : 10.161, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/IdentityRow[2]/SupplierColumn[0]/SupplierContact[2]/SupplierContactPhone[2]", + "entityName" : "SupplierContactPhone", + "entityKind" : "ParagraphNode", + "parentPath" : "MerchantInvoice[0]/IdentityRow[2]/SupplierColumn[0]/SupplierContact[2]", + "childIndex" : 2, + "depth" : 5, + "layer" : 5, + "computedX" : 19.767, + "computedY" : 641.102, + "placementX" : 19.767, + "placementY" : 641.102, + "placementWidth" : 87.149, + "placementHeight" : 10.161, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 87.149, + "contentHeight" : 10.161, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/IdentityRow[2]/SupplierColumn[0]/SupplierTaxId[3]", + "entityName" : "SupplierTaxId", + "entityKind" : "ParagraphNode", + "parentPath" : "MerchantInvoice[0]/IdentityRow[2]/SupplierColumn[0]", + "childIndex" : 3, + "depth" : 4, + "layer" : 4, + "computedX" : 19.767, + "computedY" : 620.421, + "placementX" : 19.767, + "placementY" : 620.421, + "placementWidth" : 137.688, + "placementHeight" : 8.861, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 137.688, + "contentHeight" : 8.861, + "margin" : { + "top" : 11.82, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/IdentityRow[2]/IdentityColumnDivider[1]", + "entityName" : "IdentityColumnDivider", + "entityKind" : "SectionNode", + "parentPath" : "MerchantInvoice[0]/IdentityRow[2]", + "childIndex" : 1, + "depth" : 3, + "layer" : 3, + "computedX" : 297.073, + "computedY" : 620.793, + "placementX" : 297.073, + "placementY" : 620.793, + "placementWidth" : 1.13, + "placementHeight" : 139.375, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 1.13, + "contentHeight" : 139.375, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/IdentityRow[2]/IdentityColumnDivider[1]/IdentityColumnDivider[0]", + "entityName" : "IdentityColumnDivider", + "entityKind" : "LineNode", + "parentPath" : "MerchantInvoice[0]/IdentityRow[2]/IdentityColumnDivider[1]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 297.073, + "computedY" : 620.793, + "placementX" : 297.073, + "placementY" : 620.793, + "placementWidth" : 1.13, + "placementHeight" : 139.375, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 1.13, + "contentHeight" : 139.375, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/IdentityRow[2]/MetaColumn[2]", + "entityName" : "MetaColumn", + "entityKind" : "SectionNode", + "parentPath" : "MerchantInvoice[0]/IdentityRow[2]", + "childIndex" : 2, + "depth" : 3, + "layer" : 3, + "computedX" : 298.203, + "computedY" : 630.014, + "placementX" : 298.203, + "placementY" : 630.014, + "placementWidth" : 275.611, + "placementHeight" : 123.972, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 275.611, + "contentHeight" : 123.972, + "margin" : { + "top" : 6.182, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 36.711 + } + }, { + "path" : "MerchantInvoice[0]/IdentityRow[2]/MetaColumn[2]/MetaRow_0Layer[0]", + "entityName" : "MetaRow_0Layer", + "entityKind" : "LayerStackNode", + "parentPath" : "MerchantInvoice[0]/IdentityRow[2]/MetaColumn[2]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 334.913, + "computedY" : 745.125, + "placementX" : 334.913, + "placementY" : 745.125, + "placementWidth" : 238.901, + "placementHeight" : 8.861, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 238.901, + "contentHeight" : 8.861, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/IdentityRow[2]/MetaColumn[2]/MetaRow_0Layer[0]/MetaRow_0Holder[0]", + "entityName" : "MetaRow_0Holder", + "entityKind" : "SectionNode", + "parentPath" : "MerchantInvoice[0]/IdentityRow[2]/MetaColumn[2]/MetaRow_0Layer[0]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 334.913, + "computedY" : 745.125, + "placementX" : 334.913, + "placementY" : 745.125, + "placementWidth" : 238.901, + "placementHeight" : 8.861, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 238.901, + "contentHeight" : 8.861, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/IdentityRow[2]/MetaColumn[2]/MetaRow_0Layer[0]/MetaRow_0Holder[0]/MetaRow_0[0]", + "entityName" : "MetaRow_0", + "entityKind" : "RowNode", + "parentPath" : "MerchantInvoice[0]/IdentityRow[2]/MetaColumn[2]/MetaRow_0Layer[0]/MetaRow_0Holder[0]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 334.913, + "computedY" : 745.125, + "placementX" : 334.913, + "placementY" : 745.125, + "placementWidth" : 238.901, + "placementHeight" : 8.861, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 238.901, + "contentHeight" : 8.861, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/IdentityRow[2]/MetaColumn[2]/MetaRow_0Layer[0]/MetaRow_0Holder[0]/MetaRow_0[0]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "MerchantInvoice[0]/IdentityRow[2]/MetaColumn[2]/MetaRow_0Layer[0]/MetaRow_0Holder[0]/MetaRow_0[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 334.913, + "computedY" : 745.125, + "placementX" : 334.913, + "placementY" : 745.125, + "placementWidth" : 65.734, + "placementHeight" : 8.861, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 65.734, + "contentHeight" : 8.861, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/IdentityRow[2]/MetaColumn[2]/MetaRow_0Layer[0]/MetaRow_0Holder[0]/MetaRow_0[0]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "MerchantInvoice[0]/IdentityRow[2]/MetaColumn[2]/MetaRow_0Layer[0]/MetaRow_0Holder[0]/MetaRow_0[0]", + "childIndex" : 1, + "depth" : 7, + "layer" : 7, + "computedX" : 436.573, + "computedY" : 745.125, + "placementX" : 436.573, + "placementY" : 745.125, + "placementWidth" : 73.177, + "placementHeight" : 8.861, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 73.177, + "contentHeight" : 8.861, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/IdentityRow[2]/MetaColumn[2]/MetaRow_1Layer[1]", + "entityName" : "MetaRow_1Layer", + "entityKind" : "LayerStackNode", + "parentPath" : "MerchantInvoice[0]/IdentityRow[2]/MetaColumn[2]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 334.913, + "computedY" : 722.102, + "placementX" : 334.913, + "placementY" : 722.102, + "placementWidth" : 238.901, + "placementHeight" : 8.861, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 238.901, + "contentHeight" : 8.861, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/IdentityRow[2]/MetaColumn[2]/MetaRow_1Layer[1]/MetaRow_1Holder[0]", + "entityName" : "MetaRow_1Holder", + "entityKind" : "SectionNode", + "parentPath" : "MerchantInvoice[0]/IdentityRow[2]/MetaColumn[2]/MetaRow_1Layer[1]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 334.913, + "computedY" : 722.102, + "placementX" : 334.913, + "placementY" : 722.102, + "placementWidth" : 238.901, + "placementHeight" : 8.861, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 238.901, + "contentHeight" : 8.861, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/IdentityRow[2]/MetaColumn[2]/MetaRow_1Layer[1]/MetaRow_1Holder[0]/MetaRow_1[0]", + "entityName" : "MetaRow_1", + "entityKind" : "RowNode", + "parentPath" : "MerchantInvoice[0]/IdentityRow[2]/MetaColumn[2]/MetaRow_1Layer[1]/MetaRow_1Holder[0]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 334.913, + "computedY" : 722.102, + "placementX" : 334.913, + "placementY" : 722.102, + "placementWidth" : 238.901, + "placementHeight" : 8.861, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 238.901, + "contentHeight" : 8.861, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/IdentityRow[2]/MetaColumn[2]/MetaRow_1Layer[1]/MetaRow_1Holder[0]/MetaRow_1[0]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "MerchantInvoice[0]/IdentityRow[2]/MetaColumn[2]/MetaRow_1Layer[1]/MetaRow_1Holder[0]/MetaRow_1[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 334.913, + "computedY" : 722.102, + "placementX" : 334.913, + "placementY" : 722.102, + "placementWidth" : 52.725, + "placementHeight" : 8.861, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 52.725, + "contentHeight" : 8.861, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/IdentityRow[2]/MetaColumn[2]/MetaRow_1Layer[1]/MetaRow_1Holder[0]/MetaRow_1[0]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "MerchantInvoice[0]/IdentityRow[2]/MetaColumn[2]/MetaRow_1Layer[1]/MetaRow_1Holder[0]/MetaRow_1[0]", + "childIndex" : 1, + "depth" : 7, + "layer" : 7, + "computedX" : 436.573, + "computedY" : 722.102, + "placementX" : 436.573, + "placementY" : 722.102, + "placementWidth" : 50.838, + "placementHeight" : 8.861, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 50.838, + "contentHeight" : 8.861, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/IdentityRow[2]/MetaColumn[2]/MetaRow_2Layer[2]", + "entityName" : "MetaRow_2Layer", + "entityKind" : "LayerStackNode", + "parentPath" : "MerchantInvoice[0]/IdentityRow[2]/MetaColumn[2]", + "childIndex" : 2, + "depth" : 4, + "layer" : 4, + "computedX" : 334.913, + "computedY" : 699.08, + "placementX" : 334.913, + "placementY" : 699.08, + "placementWidth" : 238.901, + "placementHeight" : 8.861, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 238.901, + "contentHeight" : 8.861, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/IdentityRow[2]/MetaColumn[2]/MetaRow_2Layer[2]/MetaRow_2Holder[0]", + "entityName" : "MetaRow_2Holder", + "entityKind" : "SectionNode", + "parentPath" : "MerchantInvoice[0]/IdentityRow[2]/MetaColumn[2]/MetaRow_2Layer[2]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 334.913, + "computedY" : 699.08, + "placementX" : 334.913, + "placementY" : 699.08, + "placementWidth" : 238.901, + "placementHeight" : 8.861, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 238.901, + "contentHeight" : 8.861, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/IdentityRow[2]/MetaColumn[2]/MetaRow_2Layer[2]/MetaRow_2Holder[0]/MetaRow_2[0]", + "entityName" : "MetaRow_2", + "entityKind" : "RowNode", + "parentPath" : "MerchantInvoice[0]/IdentityRow[2]/MetaColumn[2]/MetaRow_2Layer[2]/MetaRow_2Holder[0]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 334.913, + "computedY" : 699.08, + "placementX" : 334.913, + "placementY" : 699.08, + "placementWidth" : 238.901, + "placementHeight" : 8.861, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 238.901, + "contentHeight" : 8.861, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/IdentityRow[2]/MetaColumn[2]/MetaRow_2Layer[2]/MetaRow_2Holder[0]/MetaRow_2[0]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "MerchantInvoice[0]/IdentityRow[2]/MetaColumn[2]/MetaRow_2Layer[2]/MetaRow_2Holder[0]/MetaRow_2[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 334.913, + "computedY" : 699.08, + "placementX" : 334.913, + "placementY" : 699.08, + "placementWidth" : 40.124, + "placementHeight" : 8.861, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 40.124, + "contentHeight" : 8.861, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/IdentityRow[2]/MetaColumn[2]/MetaRow_2Layer[2]/MetaRow_2Holder[0]/MetaRow_2[0]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "MerchantInvoice[0]/IdentityRow[2]/MetaColumn[2]/MetaRow_2Layer[2]/MetaRow_2Holder[0]/MetaRow_2[0]", + "childIndex" : 1, + "depth" : 7, + "layer" : 7, + "computedX" : 436.573, + "computedY" : 699.08, + "placementX" : 436.573, + "placementY" : 699.08, + "placementWidth" : 53.08, + "placementHeight" : 8.861, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 53.08, + "contentHeight" : 8.861, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/IdentityRow[2]/MetaColumn[2]/MetaRow_3Layer[3]", + "entityName" : "MetaRow_3Layer", + "entityKind" : "LayerStackNode", + "parentPath" : "MerchantInvoice[0]/IdentityRow[2]/MetaColumn[2]", + "childIndex" : 3, + "depth" : 4, + "layer" : 4, + "computedX" : 334.913, + "computedY" : 676.058, + "placementX" : 334.913, + "placementY" : 676.058, + "placementWidth" : 238.901, + "placementHeight" : 8.861, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 238.901, + "contentHeight" : 8.861, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/IdentityRow[2]/MetaColumn[2]/MetaRow_3Layer[3]/MetaRow_3Holder[0]", + "entityName" : "MetaRow_3Holder", + "entityKind" : "SectionNode", + "parentPath" : "MerchantInvoice[0]/IdentityRow[2]/MetaColumn[2]/MetaRow_3Layer[3]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 334.913, + "computedY" : 676.058, + "placementX" : 334.913, + "placementY" : 676.058, + "placementWidth" : 238.901, + "placementHeight" : 8.861, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 238.901, + "contentHeight" : 8.861, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/IdentityRow[2]/MetaColumn[2]/MetaRow_3Layer[3]/MetaRow_3Holder[0]/MetaRow_3[0]", + "entityName" : "MetaRow_3", + "entityKind" : "RowNode", + "parentPath" : "MerchantInvoice[0]/IdentityRow[2]/MetaColumn[2]/MetaRow_3Layer[3]/MetaRow_3Holder[0]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 334.913, + "computedY" : 676.058, + "placementX" : 334.913, + "placementY" : 676.058, + "placementWidth" : 238.901, + "placementHeight" : 8.861, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 238.901, + "contentHeight" : 8.861, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/IdentityRow[2]/MetaColumn[2]/MetaRow_3Layer[3]/MetaRow_3Holder[0]/MetaRow_3[0]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "MerchantInvoice[0]/IdentityRow[2]/MetaColumn[2]/MetaRow_3Layer[3]/MetaRow_3Holder[0]/MetaRow_3[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 334.913, + "computedY" : 676.058, + "placementX" : 334.913, + "placementY" : 676.058, + "placementWidth" : 56.739, + "placementHeight" : 8.861, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 56.739, + "contentHeight" : 8.861, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/IdentityRow[2]/MetaColumn[2]/MetaRow_3Layer[3]/MetaRow_3Holder[0]/MetaRow_3[0]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "MerchantInvoice[0]/IdentityRow[2]/MetaColumn[2]/MetaRow_3Layer[3]/MetaRow_3Holder[0]/MetaRow_3[0]", + "childIndex" : 1, + "depth" : 7, + "layer" : 7, + "computedX" : 436.573, + "computedY" : 676.058, + "placementX" : 436.573, + "placementY" : 676.058, + "placementWidth" : 110.528, + "placementHeight" : 8.861, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 110.528, + "contentHeight" : 8.861, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/IdentityRow[2]/MetaColumn[2]/MetaRow_4Layer[4]", + "entityName" : "MetaRow_4Layer", + "entityKind" : "LayerStackNode", + "parentPath" : "MerchantInvoice[0]/IdentityRow[2]/MetaColumn[2]", + "childIndex" : 4, + "depth" : 4, + "layer" : 4, + "computedX" : 334.913, + "computedY" : 653.036, + "placementX" : 334.913, + "placementY" : 653.036, + "placementWidth" : 238.901, + "placementHeight" : 8.861, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 238.901, + "contentHeight" : 8.861, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/IdentityRow[2]/MetaColumn[2]/MetaRow_4Layer[4]/MetaRow_4Holder[0]", + "entityName" : "MetaRow_4Holder", + "entityKind" : "SectionNode", + "parentPath" : "MerchantInvoice[0]/IdentityRow[2]/MetaColumn[2]/MetaRow_4Layer[4]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 334.913, + "computedY" : 653.036, + "placementX" : 334.913, + "placementY" : 653.036, + "placementWidth" : 238.901, + "placementHeight" : 8.861, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 238.901, + "contentHeight" : 8.861, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/IdentityRow[2]/MetaColumn[2]/MetaRow_4Layer[4]/MetaRow_4Holder[0]/MetaRow_4[0]", + "entityName" : "MetaRow_4", + "entityKind" : "RowNode", + "parentPath" : "MerchantInvoice[0]/IdentityRow[2]/MetaColumn[2]/MetaRow_4Layer[4]/MetaRow_4Holder[0]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 334.913, + "computedY" : 653.036, + "placementX" : 334.913, + "placementY" : 653.036, + "placementWidth" : 238.901, + "placementHeight" : 8.861, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 238.901, + "contentHeight" : 8.861, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/IdentityRow[2]/MetaColumn[2]/MetaRow_4Layer[4]/MetaRow_4Holder[0]/MetaRow_4[0]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "MerchantInvoice[0]/IdentityRow[2]/MetaColumn[2]/MetaRow_4Layer[4]/MetaRow_4Holder[0]/MetaRow_4[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 334.913, + "computedY" : 653.036, + "placementX" : 334.913, + "placementY" : 653.036, + "placementWidth" : 65.202, + "placementHeight" : 8.861, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 65.202, + "contentHeight" : 8.861, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/IdentityRow[2]/MetaColumn[2]/MetaRow_4Layer[4]/MetaRow_4Holder[0]/MetaRow_4[0]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "MerchantInvoice[0]/IdentityRow[2]/MetaColumn[2]/MetaRow_4Layer[4]/MetaRow_4Holder[0]/MetaRow_4[0]", + "childIndex" : 1, + "depth" : 7, + "layer" : 7, + "computedX" : 436.573, + "computedY" : 653.036, + "placementX" : 436.573, + "placementY" : 653.036, + "placementWidth" : 48.268, + "placementHeight" : 8.861, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 48.268, + "contentHeight" : 8.861, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/IdentityRow[2]/MetaColumn[2]/MetaRow_5Layer[5]", + "entityName" : "MetaRow_5Layer", + "entityKind" : "LayerStackNode", + "parentPath" : "MerchantInvoice[0]/IdentityRow[2]/MetaColumn[2]", + "childIndex" : 5, + "depth" : 4, + "layer" : 4, + "computedX" : 334.913, + "computedY" : 630.014, + "placementX" : 334.913, + "placementY" : 630.014, + "placementWidth" : 238.901, + "placementHeight" : 8.861, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 238.901, + "contentHeight" : 8.861, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/IdentityRow[2]/MetaColumn[2]/MetaRow_5Layer[5]/MetaRow_5Holder[0]", + "entityName" : "MetaRow_5Holder", + "entityKind" : "SectionNode", + "parentPath" : "MerchantInvoice[0]/IdentityRow[2]/MetaColumn[2]/MetaRow_5Layer[5]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 334.913, + "computedY" : 630.014, + "placementX" : 334.913, + "placementY" : 630.014, + "placementWidth" : 238.901, + "placementHeight" : 8.861, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 238.901, + "contentHeight" : 8.861, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/IdentityRow[2]/MetaColumn[2]/MetaRow_5Layer[5]/MetaRow_5Holder[0]/MetaRow_5[0]", + "entityName" : "MetaRow_5", + "entityKind" : "RowNode", + "parentPath" : "MerchantInvoice[0]/IdentityRow[2]/MetaColumn[2]/MetaRow_5Layer[5]/MetaRow_5Holder[0]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 334.913, + "computedY" : 630.014, + "placementX" : 334.913, + "placementY" : 630.014, + "placementWidth" : 238.901, + "placementHeight" : 8.861, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 238.901, + "contentHeight" : 8.861, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/IdentityRow[2]/MetaColumn[2]/MetaRow_5Layer[5]/MetaRow_5Holder[0]/MetaRow_5[0]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "MerchantInvoice[0]/IdentityRow[2]/MetaColumn[2]/MetaRow_5Layer[5]/MetaRow_5Holder[0]/MetaRow_5[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 334.913, + "computedY" : 630.014, + "placementX" : 334.913, + "placementY" : 630.014, + "placementWidth" : 38.875, + "placementHeight" : 8.861, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 38.875, + "contentHeight" : 8.861, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/IdentityRow[2]/MetaColumn[2]/MetaRow_5Layer[5]/MetaRow_5Holder[0]/MetaRow_5[0]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "MerchantInvoice[0]/IdentityRow[2]/MetaColumn[2]/MetaRow_5Layer[5]/MetaRow_5Holder[0]/MetaRow_5[0]", + "childIndex" : 1, + "depth" : 7, + "layer" : 7, + "computedX" : 436.573, + "computedY" : 630.014, + "placementX" : 436.573, + "placementY" : 630.014, + "placementWidth" : 17.785, + "placementHeight" : 8.861, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 17.785, + "contentHeight" : 8.861, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/IdentityRule[3]", + "entityName" : "IdentityRule", + "entityKind" : "LineNode", + "parentPath" : "MerchantInvoice[0]", + "childIndex" : 3, + "depth" : 2, + "layer" : 2, + "computedX" : 19.767, + "computedY" : 601.737, + "placementX" : 19.767, + "placementY" : 601.737, + "placementWidth" : 554.047, + "placementHeight" : 1.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 554.047, + "contentHeight" : 1.0, + "margin" : { + "top" : 17.684, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/PartiesRow[4]", + "entityName" : "PartiesRow", + "entityKind" : "RowNode", + "parentPath" : "MerchantInvoice[0]", + "childIndex" : 4, + "depth" : 2, + "layer" : 2, + "computedX" : 19.767, + "computedY" : 480.29, + "placementX" : 19.767, + "placementY" : 480.29, + "placementWidth" : 554.047, + "placementHeight" : 106.647, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 554.047, + "contentHeight" : 106.647, + "margin" : { + "top" : 14.8, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/PartiesRow[4]/BillToColumn[0]", + "entityName" : "BillToColumn", + "entityKind" : "SectionNode", + "parentPath" : "MerchantInvoice[0]/PartiesRow[4]", + "childIndex" : 0, + "depth" : 3, + "layer" : 3, + "computedX" : 19.767, + "computedY" : 482.728, + "placementX" : 19.767, + "placementY" : 482.728, + "placementWidth" : 277.306, + "placementHeight" : 104.209, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 277.306, + "contentHeight" : 104.209, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.565 + } + }, { + "path" : "MerchantInvoice[0]/PartiesRow[4]/BillToColumn[0]/BillToLabelLayer[0]", + "entityName" : "BillToLabelLayer", + "entityKind" : "LayerStackNode", + "parentPath" : "MerchantInvoice[0]/PartiesRow[4]/BillToColumn[0]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 20.332, + "computedY" : 562.098, + "placementX" : 20.332, + "placementY" : 562.098, + "placementWidth" : 276.741, + "placementHeight" : 24.839, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 276.741, + "contentHeight" : 24.839, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/PartiesRow[4]/BillToColumn[0]/BillToLabelLayer[0]/BillToLabelHolder[0]", + "entityName" : "BillToLabelHolder", + "entityKind" : "SectionNode", + "parentPath" : "MerchantInvoice[0]/PartiesRow[4]/BillToColumn[0]/BillToLabelLayer[0]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 20.332, + "computedY" : 562.098, + "placementX" : 20.332, + "placementY" : 562.098, + "placementWidth" : 276.741, + "placementHeight" : 24.839, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 276.741, + "contentHeight" : 24.839, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/PartiesRow[4]/BillToColumn[0]/BillToLabelLayer[0]/BillToLabelHolder[0]/BillToLabel[0]", + "entityName" : "BillToLabel", + "entityKind" : "RowNode", + "parentPath" : "MerchantInvoice[0]/PartiesRow[4]/BillToColumn[0]/BillToLabelLayer[0]/BillToLabelHolder[0]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 20.332, + "computedY" : 562.098, + "placementX" : 20.332, + "placementY" : 562.098, + "placementWidth" : 276.741, + "placementHeight" : 24.839, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 276.741, + "contentHeight" : 24.839, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/PartiesRow[4]/BillToColumn[0]/BillToLabelLayer[0]/BillToLabelHolder[0]/BillToLabel[0]/BillToDisc[0]", + "entityName" : "BillToDisc", + "entityKind" : "ShapeContainerNode", + "parentPath" : "MerchantInvoice[0]/PartiesRow[4]/BillToColumn[0]/BillToLabelLayer[0]/BillToLabelHolder[0]/BillToLabel[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 20.332, + "computedY" : 562.098, + "placementX" : 20.332, + "placementY" : 562.098, + "placementWidth" : 24.839, + "placementHeight" : 24.839, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 24.839, + "contentHeight" : 24.839, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/PartiesRow[4]/BillToColumn[0]/BillToLabelLayer[0]/BillToLabelHolder[0]/BillToLabel[0]/BillToDisc[0]/SvgIcon[0]", + "entityName" : "SvgIcon", + "entityKind" : "LayerStackNode", + "parentPath" : "MerchantInvoice[0]/PartiesRow[4]/BillToColumn[0]/BillToLabelLayer[0]/BillToLabelHolder[0]/BillToLabel[0]/BillToDisc[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 26.259, + "computedY" : 568.026, + "placementX" : 26.259, + "placementY" : 568.026, + "placementWidth" : 12.984, + "placementHeight" : 12.984, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 12.984, + "contentHeight" : 12.984, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/PartiesRow[4]/BillToColumn[0]/BillToLabelLayer[0]/BillToLabelHolder[0]/BillToLabel[0]/BillToDisc[0]/SvgIcon[0]/SvgLayer0[0]", + "entityName" : "SvgLayer0", + "entityKind" : "Path", + "parentPath" : "MerchantInvoice[0]/PartiesRow[4]/BillToColumn[0]/BillToLabelLayer[0]/BillToLabelHolder[0]/BillToLabel[0]/BillToDisc[0]/SvgIcon[0]", + "childIndex" : 0, + "depth" : 9, + "layer" : 9, + "computedX" : 26.259, + "computedY" : 568.026, + "placementX" : 26.259, + "placementY" : 568.026, + "placementWidth" : 12.984, + "placementHeight" : 12.984, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 12.984, + "contentHeight" : 12.984, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/PartiesRow[4]/BillToColumn[0]/BillToLabelLayer[0]/BillToLabelHolder[0]/BillToLabel[0]/BillToLabelText[1]", + "entityName" : "BillToLabelText", + "entityKind" : "ParagraphNode", + "parentPath" : "MerchantInvoice[0]/PartiesRow[4]/BillToColumn[0]/BillToLabelLayer[0]/BillToLabelHolder[0]/BillToLabel[0]", + "childIndex" : 1, + "depth" : 7, + "layer" : 7, + "computedX" : 55.348, + "computedY" : 570.05, + "placementX" : 55.348, + "placementY" : 570.05, + "placementWidth" : 33.327, + "placementHeight" : 8.935, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 33.327, + "contentHeight" : 8.935, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/PartiesRow[4]/BillToColumn[0]/BillToBody[1]", + "entityName" : "BillToBody", + "entityKind" : "SectionNode", + "parentPath" : "MerchantInvoice[0]/PartiesRow[4]/BillToColumn[0]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 20.332, + "computedY" : 482.728, + "placementX" : 20.332, + "placementY" : 482.728, + "placementWidth" : 142.531, + "placementHeight" : 79.37, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 142.531, + "contentHeight" : 79.37, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 35.016 + } + }, { + "path" : "MerchantInvoice[0]/PartiesRow[4]/BillToColumn[0]/BillToBody[1]/BillToName[0]", + "entityName" : "BillToName", + "entityKind" : "ParagraphNode", + "parentPath" : "MerchantInvoice[0]/PartiesRow[4]/BillToColumn[0]/BillToBody[1]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 55.348, + "computedY" : 550.833, + "placementX" : 55.348, + "placementY" : 550.833, + "placementWidth" : 83.62, + "placementHeight" : 9.821, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 83.62, + "contentHeight" : 9.821, + "margin" : { + "top" : 1.443, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/PartiesRow[4]/BillToColumn[0]/BillToBody[1]/BillToAddress[1]", + "entityName" : "BillToAddress", + "entityKind" : "SectionNode", + "parentPath" : "MerchantInvoice[0]/PartiesRow[4]/BillToColumn[0]/BillToBody[1]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 55.348, + "computedY" : 508.685, + "placementX" : 55.348, + "placementY" : 508.685, + "placementWidth" : 86.363, + "placementHeight" : 35.946, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 86.363, + "contentHeight" : 35.946, + "margin" : { + "top" : 6.203, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/PartiesRow[4]/BillToColumn[0]/BillToBody[1]/BillToAddress[1]/BillToAddress_0[0]", + "entityName" : "BillToAddress_0", + "entityKind" : "ParagraphNode", + "parentPath" : "MerchantInvoice[0]/PartiesRow[4]/BillToColumn[0]/BillToBody[1]/BillToAddress[1]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 55.348, + "computedY" : 535.769, + "placementX" : 55.348, + "placementY" : 535.769, + "placementWidth" : 56.917, + "placementHeight" : 8.861, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 56.917, + "contentHeight" : 8.861, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/PartiesRow[4]/BillToColumn[0]/BillToBody[1]/BillToAddress[1]/BillToAddress_1[1]", + "entityName" : "BillToAddress_1", + "entityKind" : "ParagraphNode", + "parentPath" : "MerchantInvoice[0]/PartiesRow[4]/BillToColumn[0]/BillToBody[1]/BillToAddress[1]", + "childIndex" : 1, + "depth" : 6, + "layer" : 6, + "computedX" : 55.348, + "computedY" : 522.227, + "placementX" : 55.348, + "placementY" : 522.227, + "placementWidth" : 86.363, + "placementHeight" : 8.861, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 86.363, + "contentHeight" : 8.861, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/PartiesRow[4]/BillToColumn[0]/BillToBody[1]/BillToAddress[1]/BillToAddress_2[2]", + "entityName" : "BillToAddress_2", + "entityKind" : "ParagraphNode", + "parentPath" : "MerchantInvoice[0]/PartiesRow[4]/BillToColumn[0]/BillToBody[1]/BillToAddress[1]", + "childIndex" : 2, + "depth" : 6, + "layer" : 6, + "computedX" : 55.348, + "computedY" : 508.685, + "placementX" : 55.348, + "placementY" : 508.685, + "placementWidth" : 64.555, + "placementHeight" : 8.861, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 64.555, + "contentHeight" : 8.861, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/PartiesRow[4]/BillToColumn[0]/BillToBody[1]/BillToRegistration[2]", + "entityName" : "BillToRegistration", + "entityKind" : "ParagraphNode", + "parentPath" : "MerchantInvoice[0]/PartiesRow[4]/BillToColumn[0]/BillToBody[1]", + "childIndex" : 2, + "depth" : 5, + "layer" : 5, + "computedX" : 55.348, + "computedY" : 482.728, + "placementX" : 55.348, + "placementY" : 482.728, + "placementWidth" : 107.515, + "placementHeight" : 8.861, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 107.515, + "contentHeight" : 8.861, + "margin" : { + "top" : 17.095, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/PartiesRow[4]/PartyColumnDivider[1]", + "entityName" : "PartyColumnDivider", + "entityKind" : "SectionNode", + "parentPath" : "MerchantInvoice[0]/PartiesRow[4]", + "childIndex" : 1, + "depth" : 3, + "layer" : 3, + "computedX" : 297.073, + "computedY" : 480.29, + "placementX" : 297.073, + "placementY" : 480.29, + "placementWidth" : 1.13, + "placementHeight" : 106.647, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 1.13, + "contentHeight" : 106.647, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/PartiesRow[4]/PartyColumnDivider[1]/PartyColumnDivider[0]", + "entityName" : "PartyColumnDivider", + "entityKind" : "LineNode", + "parentPath" : "MerchantInvoice[0]/PartiesRow[4]/PartyColumnDivider[1]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 297.073, + "computedY" : 480.29, + "placementX" : 297.073, + "placementY" : 480.29, + "placementWidth" : 1.13, + "placementHeight" : 106.083, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 1.13, + "contentHeight" : 106.083, + "margin" : { + "top" : 0.564, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/PartiesRow[4]/ShipToColumn[2]", + "entityName" : "ShipToColumn", + "entityKind" : "SectionNode", + "parentPath" : "MerchantInvoice[0]/PartiesRow[4]", + "childIndex" : 2, + "depth" : 3, + "layer" : 3, + "computedX" : 298.203, + "computedY" : 495.142, + "placementX" : 298.203, + "placementY" : 495.142, + "placementWidth" : 275.611, + "placementHeight" : 91.795, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 275.611, + "contentHeight" : 91.795, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 18.073 + } + }, { + "path" : "MerchantInvoice[0]/PartiesRow[4]/ShipToColumn[2]/ShipToLabelLayer[0]", + "entityName" : "ShipToLabelLayer", + "entityKind" : "LayerStackNode", + "parentPath" : "MerchantInvoice[0]/PartiesRow[4]/ShipToColumn[2]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 316.275, + "computedY" : 562.098, + "placementX" : 316.275, + "placementY" : 562.098, + "placementWidth" : 257.539, + "placementHeight" : 24.839, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 257.539, + "contentHeight" : 24.839, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/PartiesRow[4]/ShipToColumn[2]/ShipToLabelLayer[0]/ShipToLabelHolder[0]", + "entityName" : "ShipToLabelHolder", + "entityKind" : "SectionNode", + "parentPath" : "MerchantInvoice[0]/PartiesRow[4]/ShipToColumn[2]/ShipToLabelLayer[0]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 316.275, + "computedY" : 562.098, + "placementX" : 316.275, + "placementY" : 562.098, + "placementWidth" : 257.539, + "placementHeight" : 24.839, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 257.539, + "contentHeight" : 24.839, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/PartiesRow[4]/ShipToColumn[2]/ShipToLabelLayer[0]/ShipToLabelHolder[0]/ShipToLabel[0]", + "entityName" : "ShipToLabel", + "entityKind" : "RowNode", + "parentPath" : "MerchantInvoice[0]/PartiesRow[4]/ShipToColumn[2]/ShipToLabelLayer[0]/ShipToLabelHolder[0]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 316.275, + "computedY" : 562.098, + "placementX" : 316.275, + "placementY" : 562.098, + "placementWidth" : 257.539, + "placementHeight" : 24.839, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 257.539, + "contentHeight" : 24.839, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/PartiesRow[4]/ShipToColumn[2]/ShipToLabelLayer[0]/ShipToLabelHolder[0]/ShipToLabel[0]/ShipToDisc[0]", + "entityName" : "ShipToDisc", + "entityKind" : "ShapeContainerNode", + "parentPath" : "MerchantInvoice[0]/PartiesRow[4]/ShipToColumn[2]/ShipToLabelLayer[0]/ShipToLabelHolder[0]/ShipToLabel[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 316.275, + "computedY" : 562.098, + "placementX" : 316.275, + "placementY" : 562.098, + "placementWidth" : 24.839, + "placementHeight" : 24.839, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 24.839, + "contentHeight" : 24.839, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/PartiesRow[4]/ShipToColumn[2]/ShipToLabelLayer[0]/ShipToLabelHolder[0]/ShipToLabel[0]/ShipToDisc[0]/SvgIcon[0]", + "entityName" : "SvgIcon", + "entityKind" : "LayerStackNode", + "parentPath" : "MerchantInvoice[0]/PartiesRow[4]/ShipToColumn[2]/ShipToLabelLayer[0]/ShipToLabelHolder[0]/ShipToLabel[0]/ShipToDisc[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 322.203, + "computedY" : 568.026, + "placementX" : 322.203, + "placementY" : 568.026, + "placementWidth" : 12.984, + "placementHeight" : 12.984, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 12.984, + "contentHeight" : 12.984, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/PartiesRow[4]/ShipToColumn[2]/ShipToLabelLayer[0]/ShipToLabelHolder[0]/ShipToLabel[0]/ShipToDisc[0]/SvgIcon[0]/SvgLayer0[0]", + "entityName" : "SvgLayer0", + "entityKind" : "Path", + "parentPath" : "MerchantInvoice[0]/PartiesRow[4]/ShipToColumn[2]/ShipToLabelLayer[0]/ShipToLabelHolder[0]/ShipToLabel[0]/ShipToDisc[0]/SvgIcon[0]", + "childIndex" : 0, + "depth" : 9, + "layer" : 9, + "computedX" : 322.203, + "computedY" : 568.026, + "placementX" : 322.203, + "placementY" : 568.026, + "placementWidth" : 12.984, + "placementHeight" : 12.984, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 12.984, + "contentHeight" : 12.984, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/PartiesRow[4]/ShipToColumn[2]/ShipToLabelLayer[0]/ShipToLabelHolder[0]/ShipToLabel[0]/ShipToLabelText[1]", + "entityName" : "ShipToLabelText", + "entityKind" : "ParagraphNode", + "parentPath" : "MerchantInvoice[0]/PartiesRow[4]/ShipToColumn[2]/ShipToLabelLayer[0]/ShipToLabelHolder[0]/ShipToLabel[0]", + "childIndex" : 1, + "depth" : 7, + "layer" : 7, + "computedX" : 351.292, + "computedY" : 570.05, + "placementX" : 351.292, + "placementY" : 570.05, + "placementWidth" : 35.239, + "placementHeight" : 8.935, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 35.239, + "contentHeight" : 8.935, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/PartiesRow[4]/ShipToColumn[2]/ShipToBody[1]", + "entityName" : "ShipToBody", + "entityKind" : "SectionNode", + "parentPath" : "MerchantInvoice[0]/PartiesRow[4]/ShipToColumn[2]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 316.275, + "computedY" : 495.142, + "placementX" : 316.275, + "placementY" : 495.142, + "placementWidth" : 125.677, + "placementHeight" : 66.956, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 125.677, + "contentHeight" : 66.956, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 35.016 + } + }, { + "path" : "MerchantInvoice[0]/PartiesRow[4]/ShipToColumn[2]/ShipToBody[1]/ShipToName[0]", + "entityName" : "ShipToName", + "entityKind" : "ParagraphNode", + "parentPath" : "MerchantInvoice[0]/PartiesRow[4]/ShipToColumn[2]/ShipToBody[1]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 351.292, + "computedY" : 550.833, + "placementX" : 351.292, + "placementY" : 550.833, + "placementWidth" : 83.62, + "placementHeight" : 9.821, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 83.62, + "contentHeight" : 9.821, + "margin" : { + "top" : 1.443, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/PartiesRow[4]/ShipToColumn[2]/ShipToBody[1]/ShipToAddress[1]", + "entityName" : "ShipToAddress", + "entityKind" : "SectionNode", + "parentPath" : "MerchantInvoice[0]/PartiesRow[4]/ShipToColumn[2]/ShipToBody[1]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 351.292, + "computedY" : 495.142, + "placementX" : 351.292, + "placementY" : 495.142, + "placementWidth" : 90.661, + "placementHeight" : 49.489, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 90.661, + "contentHeight" : 49.489, + "margin" : { + "top" : 6.203, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/PartiesRow[4]/ShipToColumn[2]/ShipToBody[1]/ShipToAddress[1]/ShipToAddress_0[0]", + "entityName" : "ShipToAddress_0", + "entityKind" : "ParagraphNode", + "parentPath" : "MerchantInvoice[0]/PartiesRow[4]/ShipToColumn[2]/ShipToBody[1]/ShipToAddress[1]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 351.292, + "computedY" : 535.769, + "placementX" : 351.292, + "placementY" : 535.769, + "placementWidth" : 51.75, + "placementHeight" : 8.861, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 51.75, + "contentHeight" : 8.861, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/PartiesRow[4]/ShipToColumn[2]/ShipToBody[1]/ShipToAddress[1]/ShipToAddress_1[1]", + "entityName" : "ShipToAddress_1", + "entityKind" : "ParagraphNode", + "parentPath" : "MerchantInvoice[0]/PartiesRow[4]/ShipToColumn[2]/ShipToBody[1]/ShipToAddress[1]", + "childIndex" : 1, + "depth" : 6, + "layer" : 6, + "computedX" : 351.292, + "computedY" : 522.227, + "placementX" : 351.292, + "placementY" : 522.227, + "placementWidth" : 90.661, + "placementHeight" : 8.861, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 90.661, + "contentHeight" : 8.861, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/PartiesRow[4]/ShipToColumn[2]/ShipToBody[1]/ShipToAddress[1]/ShipToAddress_2[2]", + "entityName" : "ShipToAddress_2", + "entityKind" : "ParagraphNode", + "parentPath" : "MerchantInvoice[0]/PartiesRow[4]/ShipToColumn[2]/ShipToBody[1]/ShipToAddress[1]", + "childIndex" : 2, + "depth" : 6, + "layer" : 6, + "computedX" : 351.292, + "computedY" : 508.685, + "placementX" : 351.292, + "placementY" : 508.685, + "placementWidth" : 86.363, + "placementHeight" : 8.861, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 86.363, + "contentHeight" : 8.861, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/PartiesRow[4]/ShipToColumn[2]/ShipToBody[1]/ShipToAddress[1]/ShipToAddress_3[3]", + "entityName" : "ShipToAddress_3", + "entityKind" : "ParagraphNode", + "parentPath" : "MerchantInvoice[0]/PartiesRow[4]/ShipToColumn[2]/ShipToBody[1]/ShipToAddress[1]", + "childIndex" : 3, + "depth" : 6, + "layer" : 6, + "computedX" : 351.292, + "computedY" : 495.142, + "placementX" : 351.292, + "placementY" : 495.142, + "placementWidth" : 64.555, + "placementHeight" : 8.861, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 64.555, + "contentHeight" : 8.861, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/LineItems[5]", + "entityName" : "LineItems", + "entityKind" : "TableNode", + "parentPath" : "MerchantInvoice[0]", + "childIndex" : 5, + "depth" : 2, + "layer" : 2, + "computedX" : 19.767, + "computedY" : 291.824, + "placementX" : 19.767, + "placementY" : 291.824, + "placementWidth" : 554.047, + "placementHeight" : 173.231, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 554.047, + "contentHeight" : 173.231, + "margin" : { + "top" : 15.235, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/Settlement[6]", + "entityName" : "Settlement", + "entityKind" : "RowNode", + "parentPath" : "MerchantInvoice[0]", + "childIndex" : 6, + "depth" : 2, + "layer" : 2, + "computedX" : 19.767, + "computedY" : 94.894, + "placementX" : 19.767, + "placementY" : 94.894, + "placementWidth" : 554.047, + "placementHeight" : 184.516, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 554.047, + "contentHeight" : 184.516, + "margin" : { + "top" : 12.414, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/Settlement[6]/PaymentPanel[0]", + "entityName" : "PaymentPanel", + "entityKind" : "SectionNode", + "parentPath" : "MerchantInvoice[0]/Settlement[6]", + "childIndex" : 0, + "depth" : 3, + "layer" : 3, + "computedX" : 19.767, + "computedY" : 94.894, + "placementX" : 19.767, + "placementY" : 94.894, + "placementWidth" : 225.911, + "placementHeight" : 184.516, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 225.911, + "contentHeight" : 184.516, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/Settlement[6]/PaymentPanel[0]/PaymentHeadingLayer[0]", + "entityName" : "PaymentHeadingLayer", + "entityKind" : "LayerStackNode", + "parentPath" : "MerchantInvoice[0]/Settlement[6]/PaymentPanel[0]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 19.767, + "computedY" : 257.397, + "placementX" : 19.767, + "placementY" : 257.397, + "placementWidth" : 225.911, + "placementHeight" : 22.013, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 225.911, + "contentHeight" : 22.013, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/Settlement[6]/PaymentPanel[0]/PaymentHeadingLayer[0]/PaymentHeadingHolder[0]", + "entityName" : "PaymentHeadingHolder", + "entityKind" : "SectionNode", + "parentPath" : "MerchantInvoice[0]/Settlement[6]/PaymentPanel[0]/PaymentHeadingLayer[0]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 19.767, + "computedY" : 257.397, + "placementX" : 19.767, + "placementY" : 257.397, + "placementWidth" : 225.911, + "placementHeight" : 22.013, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 225.911, + "contentHeight" : 22.013, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/Settlement[6]/PaymentPanel[0]/PaymentHeadingLayer[0]/PaymentHeadingHolder[0]/PaymentHeading[0]", + "entityName" : "PaymentHeading", + "entityKind" : "RowNode", + "parentPath" : "MerchantInvoice[0]/Settlement[6]/PaymentPanel[0]/PaymentHeadingLayer[0]/PaymentHeadingHolder[0]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 19.767, + "computedY" : 257.397, + "placementX" : 19.767, + "placementY" : 257.397, + "placementWidth" : 225.911, + "placementHeight" : 22.013, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 225.911, + "contentHeight" : 22.013, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 7.336, + "right" : 0.0, + "bottom" : 0.0, + "left" : 11.296 + } + }, { + "path" : "MerchantInvoice[0]/Settlement[6]/PaymentPanel[0]/PaymentHeadingLayer[0]/PaymentHeadingHolder[0]/PaymentHeading[0]/SvgIcon[0]", + "entityName" : "SvgIcon", + "entityKind" : "LayerStackNode", + "parentPath" : "MerchantInvoice[0]/Settlement[6]/PaymentPanel[0]/PaymentHeadingLayer[0]/PaymentHeadingHolder[0]/PaymentHeading[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 31.063, + "computedY" : 257.397, + "placementX" : 31.063, + "placementY" : 257.397, + "placementWidth" : 14.678, + "placementHeight" : 14.678, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 14.678, + "contentHeight" : 14.678, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/Settlement[6]/PaymentPanel[0]/PaymentHeadingLayer[0]/PaymentHeadingHolder[0]/PaymentHeading[0]/SvgIcon[0]/SvgLayer0[0]", + "entityName" : "SvgLayer0", + "entityKind" : "Path", + "parentPath" : "MerchantInvoice[0]/Settlement[6]/PaymentPanel[0]/PaymentHeadingLayer[0]/PaymentHeadingHolder[0]/PaymentHeading[0]/SvgIcon[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 31.063, + "computedY" : 257.397, + "placementX" : 31.063, + "placementY" : 257.397, + "placementWidth" : 14.678, + "placementHeight" : 14.678, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 14.678, + "contentHeight" : 14.678, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/Settlement[6]/PaymentPanel[0]/PaymentHeadingLayer[0]/PaymentHeadingHolder[0]/PaymentHeading[0]/PaymentHeadingText[1]", + "entityName" : "PaymentHeadingText", + "entityKind" : "ParagraphNode", + "parentPath" : "MerchantInvoice[0]/Settlement[6]/PaymentPanel[0]/PaymentHeadingLayer[0]/PaymentHeadingHolder[0]/PaymentHeading[0]", + "childIndex" : 1, + "depth" : 7, + "layer" : 7, + "computedX" : 57.043, + "computedY" : 260.424, + "placementX" : 57.043, + "placementY" : 260.424, + "placementWidth" : 80.196, + "placementHeight" : 8.624, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 80.196, + "contentHeight" : 8.624, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/Settlement[6]/PaymentPanel[0]/PaymentRows[1]", + "entityName" : "PaymentRows", + "entityKind" : "SectionNode", + "parentPath" : "MerchantInvoice[0]/Settlement[6]/PaymentPanel[0]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 19.767, + "computedY" : 141.377, + "placementX" : 19.767, + "placementY" : 141.377, + "placementWidth" : 225.911, + "placementHeight" : 109.199, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 225.911, + "contentHeight" : 109.199, + "margin" : { + "top" : 6.821, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 11.296, + "bottom" : 0.0, + "left" : 10.731 + } + }, { + "path" : "MerchantInvoice[0]/Settlement[6]/PaymentPanel[0]/PaymentRows[1]/PaymentRow_0Layer[0]", + "entityName" : "PaymentRow_0Layer", + "entityKind" : "LayerStackNode", + "parentPath" : "MerchantInvoice[0]/Settlement[6]/PaymentPanel[0]/PaymentRows[1]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 30.498, + "computedY" : 242.946, + "placementX" : 30.498, + "placementY" : 242.946, + "placementWidth" : 203.885, + "placementHeight" : 7.63, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 203.885, + "contentHeight" : 7.63, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/Settlement[6]/PaymentPanel[0]/PaymentRows[1]/PaymentRow_0Layer[0]/PaymentRow_0Holder[0]", + "entityName" : "PaymentRow_0Holder", + "entityKind" : "SectionNode", + "parentPath" : "MerchantInvoice[0]/Settlement[6]/PaymentPanel[0]/PaymentRows[1]/PaymentRow_0Layer[0]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 30.498, + "computedY" : 242.946, + "placementX" : 30.498, + "placementY" : 242.946, + "placementWidth" : 203.885, + "placementHeight" : 7.63, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 203.885, + "contentHeight" : 7.63, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/Settlement[6]/PaymentPanel[0]/PaymentRows[1]/PaymentRow_0Layer[0]/PaymentRow_0Holder[0]/PaymentRow_0[0]", + "entityName" : "PaymentRow_0", + "entityKind" : "RowNode", + "parentPath" : "MerchantInvoice[0]/Settlement[6]/PaymentPanel[0]/PaymentRows[1]/PaymentRow_0Layer[0]/PaymentRow_0Holder[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 30.498, + "computedY" : 242.946, + "placementX" : 30.498, + "placementY" : 242.946, + "placementWidth" : 203.885, + "placementHeight" : 7.63, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 203.885, + "contentHeight" : 7.63, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/Settlement[6]/PaymentPanel[0]/PaymentRows[1]/PaymentRow_0Layer[0]/PaymentRow_0Holder[0]/PaymentRow_0[0]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "MerchantInvoice[0]/Settlement[6]/PaymentPanel[0]/PaymentRows[1]/PaymentRow_0Layer[0]/PaymentRow_0Holder[0]/PaymentRow_0[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 30.498, + "computedY" : 242.946, + "placementX" : 30.498, + "placementY" : 242.946, + "placementWidth" : 42.157, + "placementHeight" : 7.63, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 42.157, + "contentHeight" : 7.63, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/Settlement[6]/PaymentPanel[0]/PaymentRows[1]/PaymentRow_0Layer[0]/PaymentRow_0Holder[0]/PaymentRow_0[0]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "MerchantInvoice[0]/Settlement[6]/PaymentPanel[0]/PaymentRows[1]/PaymentRow_0Layer[0]/PaymentRow_0Holder[0]/PaymentRow_0[0]", + "childIndex" : 1, + "depth" : 8, + "layer" : 8, + "computedX" : 124.816, + "computedY" : 242.946, + "placementX" : 124.816, + "placementY" : 242.946, + "placementWidth" : 84.275, + "placementHeight" : 7.63, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 84.275, + "contentHeight" : 7.63, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/Settlement[6]/PaymentPanel[0]/PaymentRows[1]/PaymentRow_1Layer[1]", + "entityName" : "PaymentRow_1Layer", + "entityKind" : "LayerStackNode", + "parentPath" : "MerchantInvoice[0]/Settlement[6]/PaymentPanel[0]/PaymentRows[1]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 30.498, + "computedY" : 230.25, + "placementX" : 30.498, + "placementY" : 230.25, + "placementWidth" : 203.885, + "placementHeight" : 7.63, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 203.885, + "contentHeight" : 7.63, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/Settlement[6]/PaymentPanel[0]/PaymentRows[1]/PaymentRow_1Layer[1]/PaymentRow_1Holder[0]", + "entityName" : "PaymentRow_1Holder", + "entityKind" : "SectionNode", + "parentPath" : "MerchantInvoice[0]/Settlement[6]/PaymentPanel[0]/PaymentRows[1]/PaymentRow_1Layer[1]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 30.498, + "computedY" : 230.25, + "placementX" : 30.498, + "placementY" : 230.25, + "placementWidth" : 203.885, + "placementHeight" : 7.63, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 203.885, + "contentHeight" : 7.63, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/Settlement[6]/PaymentPanel[0]/PaymentRows[1]/PaymentRow_1Layer[1]/PaymentRow_1Holder[0]/PaymentRow_1[0]", + "entityName" : "PaymentRow_1", + "entityKind" : "RowNode", + "parentPath" : "MerchantInvoice[0]/Settlement[6]/PaymentPanel[0]/PaymentRows[1]/PaymentRow_1Layer[1]/PaymentRow_1Holder[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 30.498, + "computedY" : 230.25, + "placementX" : 30.498, + "placementY" : 230.25, + "placementWidth" : 203.885, + "placementHeight" : 7.63, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 203.885, + "contentHeight" : 7.63, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/Settlement[6]/PaymentPanel[0]/PaymentRows[1]/PaymentRow_1Layer[1]/PaymentRow_1Holder[0]/PaymentRow_1[0]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "MerchantInvoice[0]/Settlement[6]/PaymentPanel[0]/PaymentRows[1]/PaymentRow_1Layer[1]/PaymentRow_1Holder[0]/PaymentRow_1[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 30.498, + "computedY" : 230.25, + "placementX" : 30.498, + "placementY" : 230.25, + "placementWidth" : 52.778, + "placementHeight" : 7.63, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 52.778, + "contentHeight" : 7.63, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/Settlement[6]/PaymentPanel[0]/PaymentRows[1]/PaymentRow_1Layer[1]/PaymentRow_1Holder[0]/PaymentRow_1[0]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "MerchantInvoice[0]/Settlement[6]/PaymentPanel[0]/PaymentRows[1]/PaymentRow_1Layer[1]/PaymentRow_1Holder[0]/PaymentRow_1[0]", + "childIndex" : 1, + "depth" : 8, + "layer" : 8, + "computedX" : 124.816, + "computedY" : 230.25, + "placementX" : 124.816, + "placementY" : 230.25, + "placementWidth" : 85.847, + "placementHeight" : 7.63, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 85.847, + "contentHeight" : 7.63, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/Settlement[6]/PaymentPanel[0]/PaymentRows[1]/PaymentRow_2Layer[2]", + "entityName" : "PaymentRow_2Layer", + "entityKind" : "LayerStackNode", + "parentPath" : "MerchantInvoice[0]/Settlement[6]/PaymentPanel[0]/PaymentRows[1]", + "childIndex" : 2, + "depth" : 5, + "layer" : 5, + "computedX" : 30.498, + "computedY" : 217.554, + "placementX" : 30.498, + "placementY" : 217.554, + "placementWidth" : 203.885, + "placementHeight" : 7.63, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 203.885, + "contentHeight" : 7.63, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/Settlement[6]/PaymentPanel[0]/PaymentRows[1]/PaymentRow_2Layer[2]/PaymentRow_2Holder[0]", + "entityName" : "PaymentRow_2Holder", + "entityKind" : "SectionNode", + "parentPath" : "MerchantInvoice[0]/Settlement[6]/PaymentPanel[0]/PaymentRows[1]/PaymentRow_2Layer[2]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 30.498, + "computedY" : 217.554, + "placementX" : 30.498, + "placementY" : 217.554, + "placementWidth" : 203.885, + "placementHeight" : 7.63, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 203.885, + "contentHeight" : 7.63, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/Settlement[6]/PaymentPanel[0]/PaymentRows[1]/PaymentRow_2Layer[2]/PaymentRow_2Holder[0]/PaymentRow_2[0]", + "entityName" : "PaymentRow_2", + "entityKind" : "RowNode", + "parentPath" : "MerchantInvoice[0]/Settlement[6]/PaymentPanel[0]/PaymentRows[1]/PaymentRow_2Layer[2]/PaymentRow_2Holder[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 30.498, + "computedY" : 217.554, + "placementX" : 30.498, + "placementY" : 217.554, + "placementWidth" : 203.885, + "placementHeight" : 7.63, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 203.885, + "contentHeight" : 7.63, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/Settlement[6]/PaymentPanel[0]/PaymentRows[1]/PaymentRow_2Layer[2]/PaymentRow_2Holder[0]/PaymentRow_2[0]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "MerchantInvoice[0]/Settlement[6]/PaymentPanel[0]/PaymentRows[1]/PaymentRow_2Layer[2]/PaymentRow_2Holder[0]/PaymentRow_2[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 30.498, + "computedY" : 217.554, + "placementX" : 30.498, + "placementY" : 217.554, + "placementWidth" : 60.072, + "placementHeight" : 7.63, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 60.072, + "contentHeight" : 7.63, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/Settlement[6]/PaymentPanel[0]/PaymentRows[1]/PaymentRow_2Layer[2]/PaymentRow_2Holder[0]/PaymentRow_2[0]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "MerchantInvoice[0]/Settlement[6]/PaymentPanel[0]/PaymentRows[1]/PaymentRow_2Layer[2]/PaymentRow_2Holder[0]/PaymentRow_2[0]", + "childIndex" : 1, + "depth" : 8, + "layer" : 8, + "computedX" : 124.816, + "computedY" : 217.554, + "placementX" : 124.816, + "placementY" : 217.554, + "placementWidth" : 55.151, + "placementHeight" : 7.63, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 55.151, + "contentHeight" : 7.63, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/Settlement[6]/PaymentPanel[0]/PaymentRows[1]/PaymentRow_3Layer[3]", + "entityName" : "PaymentRow_3Layer", + "entityKind" : "LayerStackNode", + "parentPath" : "MerchantInvoice[0]/Settlement[6]/PaymentPanel[0]/PaymentRows[1]", + "childIndex" : 3, + "depth" : 5, + "layer" : 5, + "computedX" : 30.498, + "computedY" : 204.858, + "placementX" : 30.498, + "placementY" : 204.858, + "placementWidth" : 203.885, + "placementHeight" : 7.63, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 203.885, + "contentHeight" : 7.63, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/Settlement[6]/PaymentPanel[0]/PaymentRows[1]/PaymentRow_3Layer[3]/PaymentRow_3Holder[0]", + "entityName" : "PaymentRow_3Holder", + "entityKind" : "SectionNode", + "parentPath" : "MerchantInvoice[0]/Settlement[6]/PaymentPanel[0]/PaymentRows[1]/PaymentRow_3Layer[3]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 30.498, + "computedY" : 204.858, + "placementX" : 30.498, + "placementY" : 204.858, + "placementWidth" : 203.885, + "placementHeight" : 7.63, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 203.885, + "contentHeight" : 7.63, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/Settlement[6]/PaymentPanel[0]/PaymentRows[1]/PaymentRow_3Layer[3]/PaymentRow_3Holder[0]/PaymentRow_3[0]", + "entityName" : "PaymentRow_3", + "entityKind" : "RowNode", + "parentPath" : "MerchantInvoice[0]/Settlement[6]/PaymentPanel[0]/PaymentRows[1]/PaymentRow_3Layer[3]/PaymentRow_3Holder[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 30.498, + "computedY" : 204.858, + "placementX" : 30.498, + "placementY" : 204.858, + "placementWidth" : 203.885, + "placementHeight" : 7.63, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 203.885, + "contentHeight" : 7.63, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/Settlement[6]/PaymentPanel[0]/PaymentRows[1]/PaymentRow_3Layer[3]/PaymentRow_3Holder[0]/PaymentRow_3[0]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "MerchantInvoice[0]/Settlement[6]/PaymentPanel[0]/PaymentRows[1]/PaymentRow_3Layer[3]/PaymentRow_3Holder[0]/PaymentRow_3[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 30.498, + "computedY" : 204.858, + "placementX" : 30.498, + "placementY" : 204.858, + "placementWidth" : 43.988, + "placementHeight" : 7.63, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 43.988, + "contentHeight" : 7.63, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/Settlement[6]/PaymentPanel[0]/PaymentRows[1]/PaymentRow_3Layer[3]/PaymentRow_3Holder[0]/PaymentRow_3[0]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "MerchantInvoice[0]/Settlement[6]/PaymentPanel[0]/PaymentRows[1]/PaymentRow_3Layer[3]/PaymentRow_3Holder[0]/PaymentRow_3[0]", + "childIndex" : 1, + "depth" : 8, + "layer" : 8, + "computedX" : 124.816, + "computedY" : 204.858, + "placementX" : 124.816, + "placementY" : 204.858, + "placementWidth" : 38.799, + "placementHeight" : 7.63, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 38.799, + "contentHeight" : 7.63, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/Settlement[6]/PaymentPanel[0]/PaymentRows[1]/PaymentRow_4Layer[4]", + "entityName" : "PaymentRow_4Layer", + "entityKind" : "LayerStackNode", + "parentPath" : "MerchantInvoice[0]/Settlement[6]/PaymentPanel[0]/PaymentRows[1]", + "childIndex" : 4, + "depth" : 5, + "layer" : 5, + "computedX" : 30.498, + "computedY" : 192.161, + "placementX" : 30.498, + "placementY" : 192.161, + "placementWidth" : 203.885, + "placementHeight" : 7.63, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 203.885, + "contentHeight" : 7.63, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/Settlement[6]/PaymentPanel[0]/PaymentRows[1]/PaymentRow_4Layer[4]/PaymentRow_4Holder[0]", + "entityName" : "PaymentRow_4Holder", + "entityKind" : "SectionNode", + "parentPath" : "MerchantInvoice[0]/Settlement[6]/PaymentPanel[0]/PaymentRows[1]/PaymentRow_4Layer[4]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 30.498, + "computedY" : 192.161, + "placementX" : 30.498, + "placementY" : 192.161, + "placementWidth" : 203.885, + "placementHeight" : 7.63, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 203.885, + "contentHeight" : 7.63, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/Settlement[6]/PaymentPanel[0]/PaymentRows[1]/PaymentRow_4Layer[4]/PaymentRow_4Holder[0]/PaymentRow_4[0]", + "entityName" : "PaymentRow_4", + "entityKind" : "RowNode", + "parentPath" : "MerchantInvoice[0]/Settlement[6]/PaymentPanel[0]/PaymentRows[1]/PaymentRow_4Layer[4]/PaymentRow_4Holder[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 30.498, + "computedY" : 192.161, + "placementX" : 30.498, + "placementY" : 192.161, + "placementWidth" : 203.885, + "placementHeight" : 7.63, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 203.885, + "contentHeight" : 7.63, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/Settlement[6]/PaymentPanel[0]/PaymentRows[1]/PaymentRow_4Layer[4]/PaymentRow_4Holder[0]/PaymentRow_4[0]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "MerchantInvoice[0]/Settlement[6]/PaymentPanel[0]/PaymentRows[1]/PaymentRow_4Layer[4]/PaymentRow_4Holder[0]/PaymentRow_4[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 30.498, + "computedY" : 192.161, + "placementX" : 30.498, + "placementY" : 192.161, + "placementWidth" : 67.252, + "placementHeight" : 7.63, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 67.252, + "contentHeight" : 7.63, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/Settlement[6]/PaymentPanel[0]/PaymentRows[1]/PaymentRow_4Layer[4]/PaymentRow_4Holder[0]/PaymentRow_4[0]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "MerchantInvoice[0]/Settlement[6]/PaymentPanel[0]/PaymentRows[1]/PaymentRow_4Layer[4]/PaymentRow_4Holder[0]/PaymentRow_4[0]", + "childIndex" : 1, + "depth" : 8, + "layer" : 8, + "computedX" : 124.816, + "computedY" : 192.161, + "placementX" : 124.816, + "placementY" : 192.161, + "placementWidth" : 12.727, + "placementHeight" : 7.63, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 12.727, + "contentHeight" : 7.63, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/Settlement[6]/PaymentPanel[0]/PaymentRows[1]/PaymentRow_5Layer[5]", + "entityName" : "PaymentRow_5Layer", + "entityKind" : "LayerStackNode", + "parentPath" : "MerchantInvoice[0]/Settlement[6]/PaymentPanel[0]/PaymentRows[1]", + "childIndex" : 5, + "depth" : 5, + "layer" : 5, + "computedX" : 30.498, + "computedY" : 179.465, + "placementX" : 30.498, + "placementY" : 179.465, + "placementWidth" : 203.885, + "placementHeight" : 7.63, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 203.885, + "contentHeight" : 7.63, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/Settlement[6]/PaymentPanel[0]/PaymentRows[1]/PaymentRow_5Layer[5]/PaymentRow_5Holder[0]", + "entityName" : "PaymentRow_5Holder", + "entityKind" : "SectionNode", + "parentPath" : "MerchantInvoice[0]/Settlement[6]/PaymentPanel[0]/PaymentRows[1]/PaymentRow_5Layer[5]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 30.498, + "computedY" : 179.465, + "placementX" : 30.498, + "placementY" : 179.465, + "placementWidth" : 203.885, + "placementHeight" : 7.63, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 203.885, + "contentHeight" : 7.63, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/Settlement[6]/PaymentPanel[0]/PaymentRows[1]/PaymentRow_5Layer[5]/PaymentRow_5Holder[0]/PaymentRow_5[0]", + "entityName" : "PaymentRow_5", + "entityKind" : "RowNode", + "parentPath" : "MerchantInvoice[0]/Settlement[6]/PaymentPanel[0]/PaymentRows[1]/PaymentRow_5Layer[5]/PaymentRow_5Holder[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 30.498, + "computedY" : 179.465, + "placementX" : 30.498, + "placementY" : 179.465, + "placementWidth" : 203.885, + "placementHeight" : 7.63, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 203.885, + "contentHeight" : 7.63, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/Settlement[6]/PaymentPanel[0]/PaymentRows[1]/PaymentRow_5Layer[5]/PaymentRow_5Holder[0]/PaymentRow_5[0]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "MerchantInvoice[0]/Settlement[6]/PaymentPanel[0]/PaymentRows[1]/PaymentRow_5Layer[5]/PaymentRow_5Holder[0]/PaymentRow_5[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 30.498, + "computedY" : 179.465, + "placementX" : 30.498, + "placementY" : 179.465, + "placementWidth" : 55.975, + "placementHeight" : 7.63, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 55.975, + "contentHeight" : 7.63, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/Settlement[6]/PaymentPanel[0]/PaymentRows[1]/PaymentRow_5Layer[5]/PaymentRow_5Holder[0]/PaymentRow_5[0]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "MerchantInvoice[0]/Settlement[6]/PaymentPanel[0]/PaymentRows[1]/PaymentRow_5Layer[5]/PaymentRow_5Holder[0]/PaymentRow_5[0]", + "childIndex" : 1, + "depth" : 8, + "layer" : 8, + "computedX" : 124.816, + "computedY" : 179.465, + "placementX" : 124.816, + "placementY" : 179.465, + "placementWidth" : 21.212, + "placementHeight" : 7.63, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 21.212, + "contentHeight" : 7.63, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/Settlement[6]/PaymentPanel[0]/PaymentRows[1]/PaymentRow_6Layer[6]", + "entityName" : "PaymentRow_6Layer", + "entityKind" : "LayerStackNode", + "parentPath" : "MerchantInvoice[0]/Settlement[6]/PaymentPanel[0]/PaymentRows[1]", + "childIndex" : 6, + "depth" : 5, + "layer" : 5, + "computedX" : 30.498, + "computedY" : 166.769, + "placementX" : 30.498, + "placementY" : 166.769, + "placementWidth" : 203.885, + "placementHeight" : 7.63, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 203.885, + "contentHeight" : 7.63, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/Settlement[6]/PaymentPanel[0]/PaymentRows[1]/PaymentRow_6Layer[6]/PaymentRow_6Holder[0]", + "entityName" : "PaymentRow_6Holder", + "entityKind" : "SectionNode", + "parentPath" : "MerchantInvoice[0]/Settlement[6]/PaymentPanel[0]/PaymentRows[1]/PaymentRow_6Layer[6]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 30.498, + "computedY" : 166.769, + "placementX" : 30.498, + "placementY" : 166.769, + "placementWidth" : 203.885, + "placementHeight" : 7.63, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 203.885, + "contentHeight" : 7.63, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/Settlement[6]/PaymentPanel[0]/PaymentRows[1]/PaymentRow_6Layer[6]/PaymentRow_6Holder[0]/PaymentRow_6[0]", + "entityName" : "PaymentRow_6", + "entityKind" : "RowNode", + "parentPath" : "MerchantInvoice[0]/Settlement[6]/PaymentPanel[0]/PaymentRows[1]/PaymentRow_6Layer[6]/PaymentRow_6Holder[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 30.498, + "computedY" : 166.769, + "placementX" : 30.498, + "placementY" : 166.769, + "placementWidth" : 203.885, + "placementHeight" : 7.63, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 203.885, + "contentHeight" : 7.63, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/Settlement[6]/PaymentPanel[0]/PaymentRows[1]/PaymentRow_6Layer[6]/PaymentRow_6Holder[0]/PaymentRow_6[0]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "MerchantInvoice[0]/Settlement[6]/PaymentPanel[0]/PaymentRows[1]/PaymentRow_6Layer[6]/PaymentRow_6Holder[0]/PaymentRow_6[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 30.498, + "computedY" : 166.769, + "placementX" : 30.498, + "placementY" : 166.769, + "placementWidth" : 49.077, + "placementHeight" : 7.63, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 49.077, + "contentHeight" : 7.63, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/Settlement[6]/PaymentPanel[0]/PaymentRows[1]/PaymentRow_6Layer[6]/PaymentRow_6Holder[0]/PaymentRow_6[0]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "MerchantInvoice[0]/Settlement[6]/PaymentPanel[0]/PaymentRows[1]/PaymentRow_6Layer[6]/PaymentRow_6Holder[0]/PaymentRow_6[0]", + "childIndex" : 1, + "depth" : 8, + "layer" : 8, + "computedX" : 124.816, + "computedY" : 166.769, + "placementX" : 124.816, + "placementY" : 166.769, + "placementWidth" : 44.888, + "placementHeight" : 7.63, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 44.888, + "contentHeight" : 7.63, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/Settlement[6]/PaymentPanel[0]/PaymentRows[1]/PaymentRow_7Layer[7]", + "entityName" : "PaymentRow_7Layer", + "entityKind" : "LayerStackNode", + "parentPath" : "MerchantInvoice[0]/Settlement[6]/PaymentPanel[0]/PaymentRows[1]", + "childIndex" : 7, + "depth" : 5, + "layer" : 5, + "computedX" : 30.498, + "computedY" : 154.073, + "placementX" : 30.498, + "placementY" : 154.073, + "placementWidth" : 203.885, + "placementHeight" : 7.63, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 203.885, + "contentHeight" : 7.63, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/Settlement[6]/PaymentPanel[0]/PaymentRows[1]/PaymentRow_7Layer[7]/PaymentRow_7Holder[0]", + "entityName" : "PaymentRow_7Holder", + "entityKind" : "SectionNode", + "parentPath" : "MerchantInvoice[0]/Settlement[6]/PaymentPanel[0]/PaymentRows[1]/PaymentRow_7Layer[7]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 30.498, + "computedY" : 154.073, + "placementX" : 30.498, + "placementY" : 154.073, + "placementWidth" : 203.885, + "placementHeight" : 7.63, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 203.885, + "contentHeight" : 7.63, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/Settlement[6]/PaymentPanel[0]/PaymentRows[1]/PaymentRow_7Layer[7]/PaymentRow_7Holder[0]/PaymentRow_7[0]", + "entityName" : "PaymentRow_7", + "entityKind" : "RowNode", + "parentPath" : "MerchantInvoice[0]/Settlement[6]/PaymentPanel[0]/PaymentRows[1]/PaymentRow_7Layer[7]/PaymentRow_7Holder[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 30.498, + "computedY" : 154.073, + "placementX" : 30.498, + "placementY" : 154.073, + "placementWidth" : 203.885, + "placementHeight" : 7.63, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 203.885, + "contentHeight" : 7.63, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/Settlement[6]/PaymentPanel[0]/PaymentRows[1]/PaymentRow_7Layer[7]/PaymentRow_7Holder[0]/PaymentRow_7[0]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "MerchantInvoice[0]/Settlement[6]/PaymentPanel[0]/PaymentRows[1]/PaymentRow_7Layer[7]/PaymentRow_7Holder[0]/PaymentRow_7[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 30.498, + "computedY" : 154.073, + "placementX" : 30.498, + "placementY" : 154.073, + "placementWidth" : 63.223, + "placementHeight" : 7.63, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 63.223, + "contentHeight" : 7.63, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/Settlement[6]/PaymentPanel[0]/PaymentRows[1]/PaymentRow_7Layer[7]/PaymentRow_7Holder[0]/PaymentRow_7[0]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "MerchantInvoice[0]/Settlement[6]/PaymentPanel[0]/PaymentRows[1]/PaymentRow_7Layer[7]/PaymentRow_7Holder[0]/PaymentRow_7[0]", + "childIndex" : 1, + "depth" : 8, + "layer" : 8, + "computedX" : 124.816, + "computedY" : 154.073, + "placementX" : 124.816, + "placementY" : 154.073, + "placementWidth" : 91.073, + "placementHeight" : 7.63, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 91.073, + "contentHeight" : 7.63, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/Settlement[6]/PaymentPanel[0]/PaymentRows[1]/PaymentRow_8Layer[8]", + "entityName" : "PaymentRow_8Layer", + "entityKind" : "LayerStackNode", + "parentPath" : "MerchantInvoice[0]/Settlement[6]/PaymentPanel[0]/PaymentRows[1]", + "childIndex" : 8, + "depth" : 5, + "layer" : 5, + "computedX" : 30.498, + "computedY" : 141.377, + "placementX" : 30.498, + "placementY" : 141.377, + "placementWidth" : 203.885, + "placementHeight" : 7.63, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 203.885, + "contentHeight" : 7.63, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/Settlement[6]/PaymentPanel[0]/PaymentRows[1]/PaymentRow_8Layer[8]/PaymentRow_8Holder[0]", + "entityName" : "PaymentRow_8Holder", + "entityKind" : "SectionNode", + "parentPath" : "MerchantInvoice[0]/Settlement[6]/PaymentPanel[0]/PaymentRows[1]/PaymentRow_8Layer[8]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 30.498, + "computedY" : 141.377, + "placementX" : 30.498, + "placementY" : 141.377, + "placementWidth" : 203.885, + "placementHeight" : 7.63, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 203.885, + "contentHeight" : 7.63, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/Settlement[6]/PaymentPanel[0]/PaymentRows[1]/PaymentRow_8Layer[8]/PaymentRow_8Holder[0]/PaymentRow_8[0]", + "entityName" : "PaymentRow_8", + "entityKind" : "RowNode", + "parentPath" : "MerchantInvoice[0]/Settlement[6]/PaymentPanel[0]/PaymentRows[1]/PaymentRow_8Layer[8]/PaymentRow_8Holder[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 30.498, + "computedY" : 141.377, + "placementX" : 30.498, + "placementY" : 141.377, + "placementWidth" : 203.885, + "placementHeight" : 7.63, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 203.885, + "contentHeight" : 7.63, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/Settlement[6]/PaymentPanel[0]/PaymentRows[1]/PaymentRow_8Layer[8]/PaymentRow_8Holder[0]/PaymentRow_8[0]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "MerchantInvoice[0]/Settlement[6]/PaymentPanel[0]/PaymentRows[1]/PaymentRow_8Layer[8]/PaymentRow_8Holder[0]/PaymentRow_8[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 30.498, + "computedY" : 141.377, + "placementX" : 30.498, + "placementY" : 141.377, + "placementWidth" : 37.99, + "placementHeight" : 7.63, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 37.99, + "contentHeight" : 7.63, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/Settlement[6]/PaymentPanel[0]/PaymentRows[1]/PaymentRow_8Layer[8]/PaymentRow_8Holder[0]/PaymentRow_8[0]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "MerchantInvoice[0]/Settlement[6]/PaymentPanel[0]/PaymentRows[1]/PaymentRow_8Layer[8]/PaymentRow_8Holder[0]/PaymentRow_8[0]", + "childIndex" : 1, + "depth" : 8, + "layer" : 8, + "computedX" : 124.816, + "computedY" : 141.377, + "placementX" : 124.816, + "placementY" : 141.377, + "placementWidth" : 63.01, + "placementHeight" : 7.63, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 63.01, + "contentHeight" : 7.63, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/Settlement[6]/PaymentPanel[0]/PaymentPanelRule[2]", + "entityName" : "PaymentPanelRule", + "entityKind" : "LineNode", + "parentPath" : "MerchantInvoice[0]/Settlement[6]/PaymentPanel[0]", + "childIndex" : 2, + "depth" : 4, + "layer" : 4, + "computedX" : 25.98, + "computedY" : 132.829, + "placementX" : 25.98, + "placementY" : 132.829, + "placementWidth" : 210.097, + "placementHeight" : 1.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 210.097, + "contentHeight" : 1.0, + "margin" : { + "top" : 7.548, + "right" : 0.0, + "bottom" : 0.0, + "left" : 6.213 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/Settlement[6]/PaymentPanel[0]/PaymentNoteLayer[3]", + "entityName" : "PaymentNoteLayer", + "entityKind" : "LayerStackNode", + "parentPath" : "MerchantInvoice[0]/Settlement[6]/PaymentPanel[0]", + "childIndex" : 3, + "depth" : 4, + "layer" : 4, + "computedX" : 19.767, + "computedY" : 94.894, + "placementX" : 19.767, + "placementY" : 94.894, + "placementWidth" : 225.911, + "placementHeight" : 37.935, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 225.911, + "contentHeight" : 37.935, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/Settlement[6]/PaymentPanel[0]/PaymentNoteLayer[3]/PaymentNoteHolder[0]", + "entityName" : "PaymentNoteHolder", + "entityKind" : "SectionNode", + "parentPath" : "MerchantInvoice[0]/Settlement[6]/PaymentPanel[0]/PaymentNoteLayer[3]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 19.767, + "computedY" : 94.894, + "placementX" : 19.767, + "placementY" : 94.894, + "placementWidth" : 225.911, + "placementHeight" : 37.935, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 225.911, + "contentHeight" : 37.935, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/Settlement[6]/PaymentPanel[0]/PaymentNoteLayer[3]/PaymentNoteHolder[0]/PaymentNote[0]", + "entityName" : "PaymentNote", + "entityKind" : "RowNode", + "parentPath" : "MerchantInvoice[0]/Settlement[6]/PaymentPanel[0]/PaymentNoteLayer[3]/PaymentNoteHolder[0]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 19.767, + "computedY" : 94.894, + "placementX" : 19.767, + "placementY" : 94.894, + "placementWidth" : 225.911, + "placementHeight" : 37.935, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 225.911, + "contentHeight" : 37.935, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 6.374, + "right" : 0.0, + "bottom" : 8.983, + "left" : 10.731 + } + }, { + "path" : "MerchantInvoice[0]/Settlement[6]/PaymentPanel[0]/PaymentNoteLayer[3]/PaymentNoteHolder[0]/PaymentNote[0]/SvgIcon[0]", + "entityName" : "SvgIcon", + "entityKind" : "LayerStackNode", + "parentPath" : "MerchantInvoice[0]/Settlement[6]/PaymentPanel[0]/PaymentNoteLayer[3]/PaymentNoteHolder[0]/PaymentNote[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 30.498, + "computedY" : 112.906, + "placementX" : 30.498, + "placementY" : 112.906, + "placementWidth" : 13.549, + "placementHeight" : 13.549, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 13.549, + "contentHeight" : 13.549, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/Settlement[6]/PaymentPanel[0]/PaymentNoteLayer[3]/PaymentNoteHolder[0]/PaymentNote[0]/SvgIcon[0]/SvgLayer0[0]", + "entityName" : "SvgLayer0", + "entityKind" : "Path", + "parentPath" : "MerchantInvoice[0]/Settlement[6]/PaymentPanel[0]/PaymentNoteLayer[3]/PaymentNoteHolder[0]/PaymentNote[0]/SvgIcon[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 30.498, + "computedY" : 112.906, + "placementX" : 30.498, + "placementY" : 112.906, + "placementWidth" : 13.549, + "placementHeight" : 13.549, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 13.549, + "contentHeight" : 13.549, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/Settlement[6]/PaymentPanel[0]/PaymentNoteLayer[3]/PaymentNoteHolder[0]/PaymentNote[0]/PaymentNoteText[1]", + "entityName" : "PaymentNoteText", + "entityKind" : "SectionNode", + "parentPath" : "MerchantInvoice[0]/Settlement[6]/PaymentPanel[0]/PaymentNoteLayer[3]/PaymentNoteHolder[0]/PaymentNote[0]", + "childIndex" : 1, + "depth" : 7, + "layer" : 7, + "computedX" : 49.7, + "computedY" : 103.877, + "placementX" : 49.7, + "placementY" : 103.877, + "placementWidth" : 122.011, + "placementHeight" : 22.578, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 122.011, + "contentHeight" : 22.578, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/Settlement[6]/PaymentPanel[0]/PaymentNoteLayer[3]/PaymentNoteHolder[0]/PaymentNote[0]/PaymentNoteText[1]/PaymentNoteLine_0[0]", + "entityName" : "PaymentNoteLine_0", + "entityKind" : "ParagraphNode", + "parentPath" : "MerchantInvoice[0]/Settlement[6]/PaymentPanel[0]/PaymentNoteLayer[3]/PaymentNoteHolder[0]/PaymentNote[0]/PaymentNoteText[1]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 49.7, + "computedY" : 118.548, + "placementX" : 49.7, + "placementY" : 118.548, + "placementWidth" : 122.011, + "placementHeight" : 7.907, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 122.011, + "contentHeight" : 7.907, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/Settlement[6]/PaymentPanel[0]/PaymentNoteLayer[3]/PaymentNoteHolder[0]/PaymentNote[0]/PaymentNoteText[1]/PaymentNoteLine_1[1]", + "entityName" : "PaymentNoteLine_1", + "entityKind" : "ParagraphNode", + "parentPath" : "MerchantInvoice[0]/Settlement[6]/PaymentPanel[0]/PaymentNoteLayer[3]/PaymentNoteHolder[0]/PaymentNote[0]/PaymentNoteText[1]", + "childIndex" : 1, + "depth" : 8, + "layer" : 8, + "computedX" : 49.7, + "computedY" : 103.877, + "placementX" : 49.7, + "placementY" : 103.877, + "placementWidth" : 96.021, + "placementHeight" : 7.907, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 96.021, + "contentHeight" : 7.907, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/Settlement[6]/SettlementGap[1]", + "entityName" : "SettlementGap", + "entityKind" : "SectionNode", + "parentPath" : "MerchantInvoice[0]/Settlement[6]", + "childIndex" : 1, + "depth" : 3, + "layer" : 3, + "computedX" : 245.678, + "computedY" : 279.41, + "placementX" : 245.678, + "placementY" : 279.41, + "placementWidth" : 0.0, + "placementHeight" : 0.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 0.0, + "contentHeight" : 0.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/Settlement[6]/SettlementRight[2]", + "entityName" : "SettlementRight", + "entityKind" : "SectionNode", + "parentPath" : "MerchantInvoice[0]/Settlement[6]", + "childIndex" : 2, + "depth" : 3, + "layer" : 3, + "computedX" : 313.452, + "computedY" : 114.307, + "placementX" : 313.452, + "placementY" : 114.307, + "placementWidth" : 260.362, + "placementHeight" : 165.103, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 260.362, + "contentHeight" : 165.103, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/Settlement[6]/SettlementRight[2]/Totals[0]", + "entityName" : "Totals", + "entityKind" : "SectionNode", + "parentPath" : "MerchantInvoice[0]/Settlement[6]/SettlementRight[2]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 313.452, + "computedY" : 193.741, + "placementX" : 313.452, + "placementY" : 193.741, + "placementWidth" : 260.362, + "placementHeight" : 73.826, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 260.362, + "contentHeight" : 73.826, + "margin" : { + "top" : 11.844, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/Settlement[6]/SettlementRight[2]/Totals[0]/TotalsRow_0Layer[0]", + "entityName" : "TotalsRow_0Layer", + "entityKind" : "LayerStackNode", + "parentPath" : "MerchantInvoice[0]/Settlement[6]/SettlementRight[2]/Totals[0]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 313.452, + "computedY" : 258.338, + "placementX" : 313.452, + "placementY" : 258.338, + "placementWidth" : 260.362, + "placementHeight" : 9.228, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 260.362, + "contentHeight" : 9.228, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/Settlement[6]/SettlementRight[2]/Totals[0]/TotalsRow_0Layer[0]/TotalsRow_0Holder[0]", + "entityName" : "TotalsRow_0Holder", + "entityKind" : "SectionNode", + "parentPath" : "MerchantInvoice[0]/Settlement[6]/SettlementRight[2]/Totals[0]/TotalsRow_0Layer[0]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 313.452, + "computedY" : 258.338, + "placementX" : 313.452, + "placementY" : 258.338, + "placementWidth" : 260.362, + "placementHeight" : 9.228, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 260.362, + "contentHeight" : 9.228, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/Settlement[6]/SettlementRight[2]/Totals[0]/TotalsRow_0Layer[0]/TotalsRow_0Holder[0]/TotalsRow_0[0]", + "entityName" : "TotalsRow_0", + "entityKind" : "RowNode", + "parentPath" : "MerchantInvoice[0]/Settlement[6]/SettlementRight[2]/Totals[0]/TotalsRow_0Layer[0]/TotalsRow_0Holder[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 313.452, + "computedY" : 258.338, + "placementX" : 313.452, + "placementY" : 258.338, + "placementWidth" : 260.362, + "placementHeight" : 9.228, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 260.362, + "contentHeight" : 9.228, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 19.767, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/Settlement[6]/SettlementRight[2]/Totals[0]/TotalsRow_0Layer[0]/TotalsRow_0Holder[0]/TotalsRow_0[0]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "MerchantInvoice[0]/Settlement[6]/SettlementRight[2]/Totals[0]/TotalsRow_0Layer[0]/TotalsRow_0Holder[0]/TotalsRow_0[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 313.452, + "computedY" : 258.993, + "placementX" : 313.452, + "placementY" : 258.993, + "placementWidth" : 32.759, + "placementHeight" : 8.573, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 32.759, + "contentHeight" : 8.573, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/Settlement[6]/SettlementRight[2]/Totals[0]/TotalsRow_0Layer[0]/TotalsRow_0Holder[0]/TotalsRow_0[0]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "MerchantInvoice[0]/Settlement[6]/SettlementRight[2]/Totals[0]/TotalsRow_0Layer[0]/TotalsRow_0Holder[0]/TotalsRow_0[0]", + "childIndex" : 1, + "depth" : 8, + "layer" : 8, + "computedX" : 433.749, + "computedY" : 258.338, + "placementX" : 433.749, + "placementY" : 258.338, + "placementWidth" : 120.298, + "placementHeight" : 9.228, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 120.298, + "contentHeight" : 9.228, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/Settlement[6]/SettlementRight[2]/Totals[0]/TotalsRow_1Layer[1]", + "entityName" : "TotalsRow_1Layer", + "entityKind" : "LayerStackNode", + "parentPath" : "MerchantInvoice[0]/Settlement[6]/SettlementRight[2]/Totals[0]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 313.452, + "computedY" : 237.46, + "placementX" : 313.452, + "placementY" : 237.46, + "placementWidth" : 260.362, + "placementHeight" : 20.878, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 260.362, + "contentHeight" : 20.878, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/Settlement[6]/SettlementRight[2]/Totals[0]/TotalsRow_1Layer[1]/TotalsRow_1Holder[0]", + "entityName" : "TotalsRow_1Holder", + "entityKind" : "SectionNode", + "parentPath" : "MerchantInvoice[0]/Settlement[6]/SettlementRight[2]/Totals[0]/TotalsRow_1Layer[1]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 313.452, + "computedY" : 237.46, + "placementX" : 313.452, + "placementY" : 237.46, + "placementWidth" : 260.362, + "placementHeight" : 20.878, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 260.362, + "contentHeight" : 20.878, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/Settlement[6]/SettlementRight[2]/Totals[0]/TotalsRow_1Layer[1]/TotalsRow_1Holder[0]/TotalsRow_1[0]", + "entityName" : "TotalsRow_1", + "entityKind" : "RowNode", + "parentPath" : "MerchantInvoice[0]/Settlement[6]/SettlementRight[2]/Totals[0]/TotalsRow_1Layer[1]/TotalsRow_1Holder[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 313.452, + "computedY" : 237.46, + "placementX" : 313.452, + "placementY" : 237.46, + "placementWidth" : 260.362, + "placementHeight" : 9.228, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 260.362, + "contentHeight" : 9.228, + "margin" : { + "top" : 11.649, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 19.767, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/Settlement[6]/SettlementRight[2]/Totals[0]/TotalsRow_1Layer[1]/TotalsRow_1Holder[0]/TotalsRow_1[0]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "MerchantInvoice[0]/Settlement[6]/SettlementRight[2]/Totals[0]/TotalsRow_1Layer[1]/TotalsRow_1Holder[0]/TotalsRow_1[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 313.452, + "computedY" : 238.115, + "placementX" : 313.452, + "placementY" : 238.115, + "placementWidth" : 36.248, + "placementHeight" : 8.573, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 36.248, + "contentHeight" : 8.573, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/Settlement[6]/SettlementRight[2]/Totals[0]/TotalsRow_1Layer[1]/TotalsRow_1Holder[0]/TotalsRow_1[0]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "MerchantInvoice[0]/Settlement[6]/SettlementRight[2]/Totals[0]/TotalsRow_1Layer[1]/TotalsRow_1Holder[0]/TotalsRow_1[0]", + "childIndex" : 1, + "depth" : 8, + "layer" : 8, + "computedX" : 433.749, + "computedY" : 237.46, + "placementX" : 433.749, + "placementY" : 237.46, + "placementWidth" : 120.298, + "placementHeight" : 9.228, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 120.298, + "contentHeight" : 9.228, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/Settlement[6]/SettlementRight[2]/Totals[0]/TotalsRule[2]", + "entityName" : "TotalsRule", + "entityKind" : "LineNode", + "parentPath" : "MerchantInvoice[0]/Settlement[6]/SettlementRight[2]/Totals[0]", + "childIndex" : 2, + "depth" : 5, + "layer" : 5, + "computedX" : 313.452, + "computedY" : 222.871, + "placementX" : 313.452, + "placementY" : 222.871, + "placementWidth" : 243.419, + "placementHeight" : 1.241, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 243.419, + "contentHeight" : 1.241, + "margin" : { + "top" : 13.348, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/Settlement[6]/SettlementRight[2]/Totals[0]/TotalDueLayer[3]", + "entityName" : "TotalDueLayer", + "entityKind" : "LayerStackNode", + "parentPath" : "MerchantInvoice[0]/Settlement[6]/SettlementRight[2]/Totals[0]", + "childIndex" : 3, + "depth" : 5, + "layer" : 5, + "computedX" : 313.452, + "computedY" : 193.741, + "placementX" : 313.452, + "placementY" : 193.741, + "placementWidth" : 260.362, + "placementHeight" : 29.13, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 260.362, + "contentHeight" : 29.13, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/Settlement[6]/SettlementRight[2]/Totals[0]/TotalDueLayer[3]/TotalDueHolder[0]", + "entityName" : "TotalDueHolder", + "entityKind" : "SectionNode", + "parentPath" : "MerchantInvoice[0]/Settlement[6]/SettlementRight[2]/Totals[0]/TotalDueLayer[3]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 313.452, + "computedY" : 193.741, + "placementX" : 313.452, + "placementY" : 193.741, + "placementWidth" : 260.362, + "placementHeight" : 29.13, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 260.362, + "contentHeight" : 29.13, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/Settlement[6]/SettlementRight[2]/Totals[0]/TotalDueLayer[3]/TotalDueHolder[0]/TotalDue[0]", + "entityName" : "TotalDue", + "entityKind" : "RowNode", + "parentPath" : "MerchantInvoice[0]/Settlement[6]/SettlementRight[2]/Totals[0]/TotalDueLayer[3]/TotalDueHolder[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 313.452, + "computedY" : 193.741, + "placementX" : 313.452, + "placementY" : 193.741, + "placementWidth" : 260.362, + "placementHeight" : 29.13, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 260.362, + "contentHeight" : 29.13, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 15.005, + "right" : 19.767, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/Settlement[6]/SettlementRight[2]/Totals[0]/TotalDueLayer[3]/TotalDueHolder[0]/TotalDue[0]/TotalDueLabel[0]", + "entityName" : "TotalDueLabel", + "entityKind" : "ParagraphNode", + "parentPath" : "MerchantInvoice[0]/Settlement[6]/SettlementRight[2]/Totals[0]/TotalDueLayer[3]/TotalDueHolder[0]/TotalDue[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 313.452, + "computedY" : 195.393, + "placementX" : 313.452, + "placementY" : 195.393, + "placementWidth" : 59.873, + "placementHeight" : 10.821, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 59.873, + "contentHeight" : 10.821, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/Settlement[6]/SettlementRight[2]/Totals[0]/TotalDueLayer[3]/TotalDueHolder[0]/TotalDue[0]/TotalDueValue[1]", + "entityName" : "TotalDueValue", + "entityKind" : "ParagraphNode", + "parentPath" : "MerchantInvoice[0]/Settlement[6]/SettlementRight[2]/Totals[0]/TotalDueLayer[3]/TotalDueHolder[0]/TotalDue[0]", + "childIndex" : 1, + "depth" : 8, + "layer" : 8, + "computedX" : 433.749, + "computedY" : 193.741, + "placementX" : 433.749, + "placementY" : 193.741, + "placementWidth" : 120.298, + "placementHeight" : 14.125, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 120.298, + "contentHeight" : 14.125, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/Settlement[6]/SettlementRight[2]/DueDateCard[1]", + "entityName" : "DueDateCard", + "entityKind" : "SectionNode", + "parentPath" : "MerchantInvoice[0]/Settlement[6]/SettlementRight[2]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 313.452, + "computedY" : 114.307, + "placementX" : 313.452, + "placementY" : 114.307, + "placementWidth" : 260.362, + "placementHeight" : 59.343, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 260.362, + "contentHeight" : 59.343, + "margin" : { + "top" : 20.09, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/Settlement[6]/SettlementRight[2]/DueDateCard[1]/DueDateRowLayer[0]", + "entityName" : "DueDateRowLayer", + "entityKind" : "LayerStackNode", + "parentPath" : "MerchantInvoice[0]/Settlement[6]/SettlementRight[2]/DueDateCard[1]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 313.452, + "computedY" : 114.307, + "placementX" : 313.452, + "placementY" : 114.307, + "placementWidth" : 260.362, + "placementHeight" : 59.343, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 260.362, + "contentHeight" : 59.343, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/Settlement[6]/SettlementRight[2]/DueDateCard[1]/DueDateRowLayer[0]/DueDateRowHolder[0]", + "entityName" : "DueDateRowHolder", + "entityKind" : "SectionNode", + "parentPath" : "MerchantInvoice[0]/Settlement[6]/SettlementRight[2]/DueDateCard[1]/DueDateRowLayer[0]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 313.452, + "computedY" : 114.307, + "placementX" : 313.452, + "placementY" : 114.307, + "placementWidth" : 260.362, + "placementHeight" : 59.343, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 260.362, + "contentHeight" : 59.343, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/Settlement[6]/SettlementRight[2]/DueDateCard[1]/DueDateRowLayer[0]/DueDateRowHolder[0]/DueDateRow[0]", + "entityName" : "DueDateRow", + "entityKind" : "RowNode", + "parentPath" : "MerchantInvoice[0]/Settlement[6]/SettlementRight[2]/DueDateCard[1]/DueDateRowLayer[0]/DueDateRowHolder[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 313.452, + "computedY" : 114.307, + "placementX" : 313.452, + "placementY" : 114.307, + "placementWidth" : 260.362, + "placementHeight" : 59.343, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 260.362, + "contentHeight" : 59.343, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 16.364, + "right" : 0.0, + "bottom" : 16.917, + "left" : 14.119 + } + }, { + "path" : "MerchantInvoice[0]/Settlement[6]/SettlementRight[2]/DueDateCard[1]/DueDateRowLayer[0]/DueDateRowHolder[0]/DueDateRow[0]/SvgIcon[0]", + "entityName" : "SvgIcon", + "entityKind" : "LayerStackNode", + "parentPath" : "MerchantInvoice[0]/Settlement[6]/SettlementRight[2]/DueDateCard[1]/DueDateRowLayer[0]/DueDateRowHolder[0]/DueDateRow[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 327.571, + "computedY" : 131.554, + "placementX" : 327.571, + "placementY" : 131.554, + "placementWidth" : 25.404, + "placementHeight" : 25.404, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 25.404, + "contentHeight" : 25.404, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/Settlement[6]/SettlementRight[2]/DueDateCard[1]/DueDateRowLayer[0]/DueDateRowHolder[0]/DueDateRow[0]/SvgIcon[0]/SvgLayer0[0]", + "entityName" : "SvgLayer0", + "entityKind" : "Path", + "parentPath" : "MerchantInvoice[0]/Settlement[6]/SettlementRight[2]/DueDateCard[1]/DueDateRowLayer[0]/DueDateRowHolder[0]/DueDateRow[0]/SvgIcon[0]", + "childIndex" : 0, + "depth" : 9, + "layer" : 9, + "computedX" : 327.571, + "computedY" : 131.554, + "placementX" : 327.571, + "placementY" : 131.554, + "placementWidth" : 25.404, + "placementHeight" : 25.404, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 25.404, + "contentHeight" : 25.404, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/Settlement[6]/SettlementRight[2]/DueDateCard[1]/DueDateRowLayer[0]/DueDateRowHolder[0]/DueDateRow[0]/DueDateText[1]", + "entityName" : "DueDateText", + "entityKind" : "SectionNode", + "parentPath" : "MerchantInvoice[0]/Settlement[6]/SettlementRight[2]/DueDateCard[1]/DueDateRowLayer[0]/DueDateRowHolder[0]/DueDateRow[0]", + "childIndex" : 1, + "depth" : 8, + "layer" : 8, + "computedX" : 363.717, + "computedY" : 131.224, + "placementX" : 363.717, + "placementY" : 131.224, + "placementWidth" : 73.991, + "placementHeight" : 26.063, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 73.991, + "contentHeight" : 26.063, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/Settlement[6]/SettlementRight[2]/DueDateCard[1]/DueDateRowLayer[0]/DueDateRowHolder[0]/DueDateRow[0]/DueDateText[1]/DueDateLabel[0]", + "entityName" : "DueDateLabel", + "entityKind" : "ParagraphNode", + "parentPath" : "MerchantInvoice[0]/Settlement[6]/SettlementRight[2]/DueDateCard[1]/DueDateRowLayer[0]/DueDateRowHolder[0]/DueDateRow[0]/DueDateText[1]", + "childIndex" : 0, + "depth" : 9, + "layer" : 9, + "computedX" : 363.717, + "computedY" : 148.911, + "placementX" : 363.717, + "placementY" : 148.911, + "placementWidth" : 73.991, + "placementHeight" : 8.376, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 73.991, + "contentHeight" : 8.376, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/Settlement[6]/SettlementRight[2]/DueDateCard[1]/DueDateRowLayer[0]/DueDateRowHolder[0]/DueDateRow[0]/DueDateText[1]/DueDateValue[1]", + "entityName" : "DueDateValue", + "entityKind" : "ParagraphNode", + "parentPath" : "MerchantInvoice[0]/Settlement[6]/SettlementRight[2]/DueDateCard[1]/DueDateRowLayer[0]/DueDateRowHolder[0]/DueDateRow[0]/DueDateText[1]", + "childIndex" : 1, + "depth" : 9, + "layer" : 9, + "computedX" : 363.717, + "computedY" : 131.224, + "placementX" : 363.717, + "placementY" : 131.224, + "placementWidth" : 72.311, + "placementHeight" : 11.471, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 72.311, + "contentHeight" : 11.471, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/ClosingNoteRule[7]", + "entityName" : "ClosingNoteRule", + "entityKind" : "LineNode", + "parentPath" : "MerchantInvoice[0]", + "childIndex" : 7, + "depth" : 2, + "layer" : 2, + "computedX" : 19.767, + "computedY" : 89.38, + "placementX" : 19.767, + "placementY" : 89.38, + "placementWidth" : 554.047, + "placementHeight" : 1.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 554.047, + "contentHeight" : 1.0, + "margin" : { + "top" : 4.514, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/ClosingNote[8]", + "entityName" : "ClosingNote", + "entityKind" : "RowNode", + "parentPath" : "MerchantInvoice[0]", + "childIndex" : 8, + "depth" : 2, + "layer" : 2, + "computedX" : 19.767, + "computedY" : 55.904, + "placementX" : 19.767, + "placementY" : 55.904, + "placementWidth" : 554.047, + "placementHeight" : 24.32, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 554.047, + "contentHeight" : 24.32, + "margin" : { + "top" : 9.157, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/ClosingNote[8]/ClosingNoteIcon[0]", + "entityName" : "ClosingNoteIcon", + "entityKind" : "SectionNode", + "parentPath" : "MerchantInvoice[0]/ClosingNote[8]", + "childIndex" : 0, + "depth" : 3, + "layer" : 3, + "computedX" : 19.767, + "computedY" : 63.288, + "placementX" : 19.767, + "placementY" : 63.288, + "placementWidth" : 16.936, + "placementHeight" : 16.936, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 16.936, + "contentHeight" : 16.936, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/ClosingNote[8]/ClosingNoteIcon[0]/SvgIcon[0]", + "entityName" : "SvgIcon", + "entityKind" : "LayerStackNode", + "parentPath" : "MerchantInvoice[0]/ClosingNote[8]/ClosingNoteIcon[0]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 19.767, + "computedY" : 63.288, + "placementX" : 19.767, + "placementY" : 63.288, + "placementWidth" : 16.936, + "placementHeight" : 16.936, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 16.936, + "contentHeight" : 16.936, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/ClosingNote[8]/ClosingNoteIcon[0]/SvgIcon[0]/SvgLayer0[0]", + "entityName" : "SvgLayer0", + "entityKind" : "Path", + "parentPath" : "MerchantInvoice[0]/ClosingNote[8]/ClosingNoteIcon[0]/SvgIcon[0]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 19.767, + "computedY" : 63.288, + "placementX" : 19.767, + "placementY" : 63.288, + "placementWidth" : 16.936, + "placementHeight" : 16.936, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 16.936, + "contentHeight" : 16.936, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/ClosingNote[8]/ClosingNoteText[1]", + "entityName" : "ClosingNoteText", + "entityKind" : "SectionNode", + "parentPath" : "MerchantInvoice[0]/ClosingNote[8]", + "childIndex" : 1, + "depth" : 3, + "layer" : 3, + "computedX" : 45.182, + "computedY" : 55.904, + "placementX" : 45.182, + "placementY" : 55.904, + "placementWidth" : 330.687, + "placementHeight" : 20.013, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 330.687, + "contentHeight" : 20.013, + "margin" : { + "top" : 4.306, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/ClosingNote[8]/ClosingNoteText[1]/ClosingNoteLead[0]", + "entityName" : "ClosingNoteLead", + "entityKind" : "ParagraphNode", + "parentPath" : "MerchantInvoice[0]/ClosingNote[8]/ClosingNoteText[1]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 45.182, + "computedY" : 67.733, + "placementX" : 45.182, + "placementY" : 67.733, + "placementWidth" : 131.977, + "placementHeight" : 8.184, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 131.977, + "contentHeight" : 8.184, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/ClosingNote[8]/ClosingNoteText[1]/ClosingNoteProse_0[1]", + "entityName" : "ClosingNoteProse_0", + "entityKind" : "ParagraphNode", + "parentPath" : "MerchantInvoice[0]/ClosingNote[8]/ClosingNoteText[1]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 45.182, + "computedY" : 55.904, + "placementX" : 45.182, + "placementY" : 55.904, + "placementWidth" : 330.687, + "placementHeight" : 7.918, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 330.687, + "contentHeight" : 7.918, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/FooterRule[9]", + "entityName" : "FooterRule", + "entityKind" : "LineNode", + "parentPath" : "MerchantInvoice[0]", + "childIndex" : 9, + "depth" : 2, + "layer" : 2, + "computedX" : 19.767, + "computedY" : 43.674, + "placementX" : 19.767, + "placementY" : 43.674, + "placementWidth" : 554.047, + "placementHeight" : 1.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 554.047, + "contentHeight" : 1.0, + "margin" : { + "top" : 11.229, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/FooterIdentity[10]", + "entityName" : "FooterIdentity", + "entityKind" : "RowNode", + "parentPath" : "MerchantInvoice[0]", + "childIndex" : 10, + "depth" : 2, + "layer" : 2, + "computedX" : 19.767, + "computedY" : 3.919, + "placementX" : 19.767, + "placementY" : 3.919, + "placementWidth" : 554.047, + "placementHeight" : 39.756, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 554.047, + "contentHeight" : 39.756, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/FooterIdentity[10]/FooterTile[0]", + "entityName" : "FooterTile", + "entityKind" : "ShapeContainerNode", + "parentPath" : "MerchantInvoice[0]/FooterIdentity[10]", + "childIndex" : 0, + "depth" : 3, + "layer" : 3, + "computedX" : 19.767, + "computedY" : 3.919, + "placementX" : 19.767, + "placementY" : 3.919, + "placementWidth" : 40.099, + "placementHeight" : 39.756, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 40.099, + "contentHeight" : 39.756, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/FooterIdentity[10]/FooterTile[0]/FooterMonogram[0]", + "entityName" : "FooterMonogram", + "entityKind" : "ParagraphNode", + "parentPath" : "MerchantInvoice[0]/FooterIdentity[10]/FooterTile[0]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 19.767, + "computedY" : 19.964, + "placementX" : 19.767, + "placementY" : 19.964, + "placementWidth" : 40.099, + "placementHeight" : 7.664, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 40.099, + "contentHeight" : 7.664, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/FooterIdentity[10]/FooterText[1]", + "entityName" : "FooterText", + "entityKind" : "SectionNode", + "parentPath" : "MerchantInvoice[0]/FooterIdentity[10]", + "childIndex" : 1, + "depth" : 3, + "layer" : 3, + "computedX" : 59.866, + "computedY" : 6.353, + "placementX" : 59.866, + "placementY" : 6.353, + "placementWidth" : 213.432, + "placementHeight" : 37.321, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 213.432, + "contentHeight" : 37.321, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 14.684 + } + }, { + "path" : "MerchantInvoice[0]/FooterIdentity[10]/FooterText[1]/FooterName[0]", + "entityName" : "FooterName", + "entityKind" : "ParagraphNode", + "parentPath" : "MerchantInvoice[0]/FooterIdentity[10]/FooterText[1]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 74.551, + "computedY" : 29.305, + "placementX" : 74.551, + "placementY" : 29.305, + "placementWidth" : 91.424, + "placementHeight" : 7.664, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 91.424, + "contentHeight" : 7.664, + "margin" : { + "top" : 6.705, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/FooterIdentity[10]/FooterText[1]/FooterAddress[1]", + "entityName" : "FooterAddress", + "entityKind" : "ParagraphNode", + "parentPath" : "MerchantInvoice[0]/FooterIdentity[10]/FooterText[1]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 74.551, + "computedY" : 17.692, + "placementX" : 74.551, + "placementY" : 17.692, + "placementWidth" : 198.748, + "placementHeight" : 7.749, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 198.748, + "contentHeight" : 7.749, + "margin" : { + "top" : 3.865, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MerchantInvoice[0]/FooterIdentity[10]/FooterText[1]/FooterSite[2]", + "entityName" : "FooterSite", + "entityKind" : "ParagraphNode", + "parentPath" : "MerchantInvoice[0]/FooterIdentity[10]/FooterText[1]", + "childIndex" : 2, + "depth" : 4, + "layer" : 4, + "computedX" : 74.551, + "computedY" : 6.353, + "placementX" : 74.551, + "placementY" : 6.353, + "placementWidth" : 77.179, + "placementHeight" : 7.201, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 77.179, + "contentHeight" : 7.201, + "margin" : { + "top" : 4.137, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + } ] +} diff --git a/qa/src/test/resources/layout-snapshots/canonical-templates/invoice/metered_invoice_layout.json b/qa/src/test/resources/layout-snapshots/canonical-templates/invoice/metered_invoice_layout.json new file mode 100644 index 000000000..421c71a7c --- /dev/null +++ b/qa/src/test/resources/layout-snapshots/canonical-templates/invoice/metered_invoice_layout.json @@ -0,0 +1,5897 @@ +{ + "formatVersion" : "2.0", + "canvas" : { + "pageWidth" : 595.276, + "pageHeight" : 841.89, + "innerWidth" : 550.7, + "innerHeight" : 771.895, + "margin" : { + "top" : 24.827, + "right" : 24.545, + "bottom" : 45.169, + "left" : 20.031 + } + }, + "totalPages" : 1, + "nodes" : [ { + "path" : "MeteredInvoice[0]", + "entityName" : "MeteredInvoice", + "entityKind" : "ContainerNode", + "parentPath" : null, + "childIndex" : 0, + "depth" : 1, + "layer" : 1, + "computedX" : 20.031, + "computedY" : 54.204, + "placementX" : 20.031, + "placementY" : 54.204, + "placementWidth" : 550.7, + "placementHeight" : 762.859, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 550.7, + "contentHeight" : 762.859, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]", + "entityName" : "Body", + "entityKind" : "SectionNode", + "parentPath" : "MeteredInvoice[0]", + "childIndex" : 0, + "depth" : 2, + "layer" : 2, + "computedX" : 20.031, + "computedY" : 54.204, + "placementX" : 20.031, + "placementY" : 54.204, + "placementWidth" : 550.7, + "placementHeight" : 762.859, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 550.7, + "contentHeight" : 762.859, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/BrandHeader[0]", + "entityName" : "BrandHeader", + "entityKind" : "RowNode", + "parentPath" : "MeteredInvoice[0]/Body[0]", + "childIndex" : 0, + "depth" : 3, + "layer" : 3, + "computedX" : 20.031, + "computedY" : 770.414, + "placementX" : 20.031, + "placementY" : 770.414, + "placementWidth" : 550.7, + "placementHeight" : 46.649, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 550.7, + "contentHeight" : 46.649, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/BrandHeader[0]/BrandMark[0]", + "entityName" : "BrandMark", + "entityKind" : "SectionNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/BrandHeader[0]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 20.031, + "computedY" : 776.339, + "placementX" : 20.031, + "placementY" : 776.339, + "placementWidth" : 223.445, + "placementHeight" : 34.8, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 223.445, + "contentHeight" : 34.8, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/BrandHeader[0]/BrandMark[0]/BrandName[0]", + "entityName" : "BrandName", + "entityKind" : "ParagraphNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/BrandHeader[0]/BrandMark[0]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 20.031, + "computedY" : 776.339, + "placementX" : 20.031, + "placementY" : 776.339, + "placementWidth" : 223.445, + "placementHeight" : 34.8, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 223.445, + "contentHeight" : 34.8, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/BrandHeader[0]/InvoiceTitle[1]", + "entityName" : "InvoiceTitle", + "entityKind" : "SectionNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/BrandHeader[0]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 295.381, + "computedY" : 770.414, + "placementX" : 295.381, + "placementY" : 770.414, + "placementWidth" : 275.35, + "placementHeight" : 46.649, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 275.35, + "contentHeight" : 46.649, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/BrandHeader[0]/InvoiceTitle[1]/InvoiceTitleText[0]", + "entityName" : "InvoiceTitleText", + "entityKind" : "ParagraphNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/BrandHeader[0]/InvoiceTitle[1]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 295.381, + "computedY" : 782.263, + "placementX" : 295.381, + "placementY" : 782.263, + "placementWidth" : 275.35, + "placementHeight" : 34.8, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 275.35, + "contentHeight" : 34.8, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/BrandHeader[0]/InvoiceTitle[1]/Align[1]", + "entityName" : null, + "entityKind" : "Align", + "parentPath" : "MeteredInvoice[0]/Body[0]/BrandHeader[0]/InvoiceTitle[1]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 295.381, + "computedY" : 770.414, + "placementX" : 295.381, + "placementY" : 770.414, + "placementWidth" : 275.35, + "placementHeight" : 2.257, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 275.35, + "contentHeight" : 2.257, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/BrandHeader[0]/InvoiceTitle[1]/Align[1]/InvoiceTitleRule[0]", + "entityName" : "InvoiceTitleRule", + "entityKind" : "LineNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/BrandHeader[0]/InvoiceTitle[1]/Align[1]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 530.67, + "computedY" : 770.414, + "placementX" : 530.67, + "placementY" : 770.414, + "placementWidth" : 40.061, + "placementHeight" : 2.257, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 40.061, + "contentHeight" : 2.257, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/IdentityRow[1]", + "entityName" : "IdentityRow", + "entityKind" : "RowNode", + "parentPath" : "MeteredInvoice[0]/Body[0]", + "childIndex" : 1, + "depth" : 3, + "layer" : 3, + "computedX" : 20.031, + "computedY" : 630.536, + "placementX" : 20.031, + "placementY" : 630.536, + "placementWidth" : 550.7, + "placementHeight" : 127.465, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 550.7, + "contentHeight" : 127.465, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/IdentityRow[1]/SupplierColumn[0]", + "entityName" : "SupplierColumn", + "entityKind" : "SectionNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/IdentityRow[1]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 20.031, + "computedY" : 630.536, + "placementX" : 20.031, + "placementY" : 630.536, + "placementWidth" : 237.352, + "placementHeight" : 127.465, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 237.352, + "contentHeight" : 127.465, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 11.285, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/IdentityRow[1]/SupplierColumn[0]/SupplierBlock[0]", + "entityName" : "SupplierBlock", + "entityKind" : "SectionNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/IdentityRow[1]/SupplierColumn[0]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 20.031, + "computedY" : 630.536, + "placementX" : 20.031, + "placementY" : 630.536, + "placementWidth" : 226.067, + "placementHeight" : 124.079, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 226.067, + "contentHeight" : 124.079, + "margin" : { + "top" : 3.385, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/IdentityRow[1]/SupplierColumn[0]/SupplierBlock[0]/SupplierIdentity[0]", + "entityName" : "SupplierIdentity", + "entityKind" : "SectionNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/IdentityRow[1]/SupplierColumn[0]/SupplierBlock[0]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 20.031, + "computedY" : 698.253, + "placementX" : 20.031, + "placementY" : 698.253, + "placementWidth" : 142.622, + "placementHeight" : 56.363, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 142.622, + "contentHeight" : 56.363, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/IdentityRow[1]/SupplierColumn[0]/SupplierBlock[0]/SupplierIdentity[0]/SupplierName[0]", + "entityName" : "SupplierName", + "entityKind" : "ParagraphNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/IdentityRow[1]/SupplierColumn[0]/SupplierBlock[0]/SupplierIdentity[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 20.031, + "computedY" : 742.015, + "placementX" : 20.031, + "placementY" : 742.015, + "placementWidth" : 142.622, + "placementHeight" : 12.6, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 142.622, + "contentHeight" : 12.6, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 3.385, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/IdentityRow[1]/SupplierColumn[0]/SupplierBlock[0]/SupplierIdentity[0]/SupplierAddress_0[1]", + "entityName" : "SupplierAddress_0", + "entityKind" : "ParagraphNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/IdentityRow[1]/SupplierColumn[0]/SupplierBlock[0]/SupplierIdentity[0]", + "childIndex" : 1, + "depth" : 7, + "layer" : 7, + "computedX" : 20.031, + "computedY" : 725.171, + "placementX" : 20.031, + "placementY" : 725.171, + "placementWidth" : 75.867, + "placementHeight" : 10.92, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 75.867, + "contentHeight" : 10.92, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/IdentityRow[1]/SupplierColumn[0]/SupplierBlock[0]/SupplierIdentity[0]/SupplierAddress_1[2]", + "entityName" : "SupplierAddress_1", + "entityKind" : "ParagraphNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/IdentityRow[1]/SupplierColumn[0]/SupplierBlock[0]/SupplierIdentity[0]", + "childIndex" : 2, + "depth" : 7, + "layer" : 7, + "computedX" : 20.031, + "computedY" : 711.712, + "placementX" : 20.031, + "placementY" : 711.712, + "placementWidth" : 66.676, + "placementHeight" : 10.92, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 66.676, + "contentHeight" : 10.92, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/IdentityRow[1]/SupplierColumn[0]/SupplierBlock[0]/SupplierIdentity[0]/SupplierAddress_2[3]", + "entityName" : "SupplierAddress_2", + "entityKind" : "ParagraphNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/IdentityRow[1]/SupplierColumn[0]/SupplierBlock[0]/SupplierIdentity[0]", + "childIndex" : 3, + "depth" : 7, + "layer" : 7, + "computedX" : 20.031, + "computedY" : 698.253, + "placementX" : 20.031, + "placementY" : 698.253, + "placementWidth" : 65.229, + "placementHeight" : 10.92, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 65.229, + "contentHeight" : 10.92, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/IdentityRow[1]/SupplierColumn[0]/SupplierBlock[0]/SupplierContacts[1]", + "entityName" : "SupplierContacts", + "entityKind" : "SectionNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/IdentityRow[1]/SupplierColumn[0]/SupplierBlock[0]", + "childIndex" : 1, + "depth" : 6, + "layer" : 6, + "computedX" : 20.031, + "computedY" : 630.536, + "placementX" : 20.031, + "placementY" : 630.536, + "placementWidth" : 226.067, + "placementHeight" : 54.175, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 226.067, + "contentHeight" : 54.175, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/IdentityRow[1]/SupplierColumn[0]/SupplierBlock[0]/SupplierContacts[1]/SupplierContact_0Layer[0]", + "entityName" : "SupplierContact_0Layer", + "entityKind" : "LayerStackNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/IdentityRow[1]/SupplierColumn[0]/SupplierBlock[0]/SupplierContacts[1]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 20.031, + "computedY" : 673.791, + "placementX" : 20.031, + "placementY" : 673.791, + "placementWidth" : 226.067, + "placementHeight" : 10.92, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 226.067, + "contentHeight" : 10.92, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/IdentityRow[1]/SupplierColumn[0]/SupplierBlock[0]/SupplierContacts[1]/SupplierContact_0Layer[0]/SupplierContact_0Holder[0]", + "entityName" : "SupplierContact_0Holder", + "entityKind" : "SectionNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/IdentityRow[1]/SupplierColumn[0]/SupplierBlock[0]/SupplierContacts[1]/SupplierContact_0Layer[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 20.031, + "computedY" : 673.791, + "placementX" : 20.031, + "placementY" : 673.791, + "placementWidth" : 226.067, + "placementHeight" : 10.92, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 226.067, + "contentHeight" : 10.92, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/IdentityRow[1]/SupplierColumn[0]/SupplierBlock[0]/SupplierContacts[1]/SupplierContact_0Layer[0]/SupplierContact_0Holder[0]/SupplierContact_0[0]", + "entityName" : "SupplierContact_0", + "entityKind" : "RowNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/IdentityRow[1]/SupplierColumn[0]/SupplierBlock[0]/SupplierContacts[1]/SupplierContact_0Layer[0]/SupplierContact_0Holder[0]", + "childIndex" : 0, + "depth" : 9, + "layer" : 9, + "computedX" : 20.031, + "computedY" : 673.791, + "placementX" : 20.031, + "placementY" : 673.791, + "placementWidth" : 226.067, + "placementHeight" : 10.92, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 226.067, + "contentHeight" : 10.92, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/IdentityRow[1]/SupplierColumn[0]/SupplierBlock[0]/SupplierContacts[1]/SupplierContact_0Layer[0]/SupplierContact_0Holder[0]/SupplierContact_0[0]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/IdentityRow[1]/SupplierColumn[0]/SupplierBlock[0]/SupplierContacts[1]/SupplierContact_0Layer[0]/SupplierContact_0Holder[0]/SupplierContact_0[0]", + "childIndex" : 0, + "depth" : 10, + "layer" : 10, + "computedX" : 20.031, + "computedY" : 673.791, + "placementX" : 20.031, + "placementY" : 673.791, + "placementWidth" : 31.777, + "placementHeight" : 10.92, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 31.777, + "contentHeight" : 10.92, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/IdentityRow[1]/SupplierColumn[0]/SupplierBlock[0]/SupplierContacts[1]/SupplierContact_0Layer[0]/SupplierContact_0Holder[0]/SupplierContact_0[0]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/IdentityRow[1]/SupplierColumn[0]/SupplierBlock[0]/SupplierContacts[1]/SupplierContact_0Layer[0]/SupplierContact_0Holder[0]/SupplierContact_0[0]", + "childIndex" : 1, + "depth" : 10, + "layer" : 10, + "computedX" : 67.991, + "computedY" : 673.791, + "placementX" : 67.991, + "placementY" : 673.791, + "placementWidth" : 67.031, + "placementHeight" : 10.92, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 67.031, + "contentHeight" : 10.92, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/IdentityRow[1]/SupplierColumn[0]/SupplierBlock[0]/SupplierContacts[1]/SupplierContact_1Layer[1]", + "entityName" : "SupplierContact_1Layer", + "entityKind" : "LayerStackNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/IdentityRow[1]/SupplierColumn[0]/SupplierBlock[0]/SupplierContacts[1]", + "childIndex" : 1, + "depth" : 7, + "layer" : 7, + "computedX" : 20.031, + "computedY" : 659.372, + "placementX" : 20.031, + "placementY" : 659.372, + "placementWidth" : 226.067, + "placementHeight" : 10.92, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 226.067, + "contentHeight" : 10.92, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/IdentityRow[1]/SupplierColumn[0]/SupplierBlock[0]/SupplierContacts[1]/SupplierContact_1Layer[1]/SupplierContact_1Holder[0]", + "entityName" : "SupplierContact_1Holder", + "entityKind" : "SectionNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/IdentityRow[1]/SupplierColumn[0]/SupplierBlock[0]/SupplierContacts[1]/SupplierContact_1Layer[1]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 20.031, + "computedY" : 659.372, + "placementX" : 20.031, + "placementY" : 659.372, + "placementWidth" : 226.067, + "placementHeight" : 10.92, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 226.067, + "contentHeight" : 10.92, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/IdentityRow[1]/SupplierColumn[0]/SupplierBlock[0]/SupplierContacts[1]/SupplierContact_1Layer[1]/SupplierContact_1Holder[0]/SupplierContact_1[0]", + "entityName" : "SupplierContact_1", + "entityKind" : "RowNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/IdentityRow[1]/SupplierColumn[0]/SupplierBlock[0]/SupplierContacts[1]/SupplierContact_1Layer[1]/SupplierContact_1Holder[0]", + "childIndex" : 0, + "depth" : 9, + "layer" : 9, + "computedX" : 20.031, + "computedY" : 659.372, + "placementX" : 20.031, + "placementY" : 659.372, + "placementWidth" : 226.067, + "placementHeight" : 10.92, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 226.067, + "contentHeight" : 10.92, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/IdentityRow[1]/SupplierColumn[0]/SupplierBlock[0]/SupplierContacts[1]/SupplierContact_1Layer[1]/SupplierContact_1Holder[0]/SupplierContact_1[0]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/IdentityRow[1]/SupplierColumn[0]/SupplierBlock[0]/SupplierContacts[1]/SupplierContact_1Layer[1]/SupplierContact_1Holder[0]/SupplierContact_1[0]", + "childIndex" : 0, + "depth" : 10, + "layer" : 10, + "computedX" : 20.031, + "computedY" : 659.372, + "placementX" : 20.031, + "placementY" : 659.372, + "placementWidth" : 27.864, + "placementHeight" : 10.92, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 27.864, + "contentHeight" : 10.92, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/IdentityRow[1]/SupplierColumn[0]/SupplierBlock[0]/SupplierContacts[1]/SupplierContact_1Layer[1]/SupplierContact_1Holder[0]/SupplierContact_1[0]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/IdentityRow[1]/SupplierColumn[0]/SupplierBlock[0]/SupplierContacts[1]/SupplierContact_1Layer[1]/SupplierContact_1Holder[0]/SupplierContact_1[0]", + "childIndex" : 1, + "depth" : 10, + "layer" : 10, + "computedX" : 67.991, + "computedY" : 659.372, + "placementX" : 67.991, + "placementY" : 659.372, + "placementWidth" : 85.74, + "placementHeight" : 10.92, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 85.74, + "contentHeight" : 10.92, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/IdentityRow[1]/SupplierColumn[0]/SupplierBlock[0]/SupplierContacts[1]/SupplierContact_2Layer[2]", + "entityName" : "SupplierContact_2Layer", + "entityKind" : "LayerStackNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/IdentityRow[1]/SupplierColumn[0]/SupplierBlock[0]/SupplierContacts[1]", + "childIndex" : 2, + "depth" : 7, + "layer" : 7, + "computedX" : 20.031, + "computedY" : 644.954, + "placementX" : 20.031, + "placementY" : 644.954, + "placementWidth" : 226.067, + "placementHeight" : 10.92, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 226.067, + "contentHeight" : 10.92, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/IdentityRow[1]/SupplierColumn[0]/SupplierBlock[0]/SupplierContacts[1]/SupplierContact_2Layer[2]/SupplierContact_2Holder[0]", + "entityName" : "SupplierContact_2Holder", + "entityKind" : "SectionNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/IdentityRow[1]/SupplierColumn[0]/SupplierBlock[0]/SupplierContacts[1]/SupplierContact_2Layer[2]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 20.031, + "computedY" : 644.954, + "placementX" : 20.031, + "placementY" : 644.954, + "placementWidth" : 226.067, + "placementHeight" : 10.92, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 226.067, + "contentHeight" : 10.92, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/IdentityRow[1]/SupplierColumn[0]/SupplierBlock[0]/SupplierContacts[1]/SupplierContact_2Layer[2]/SupplierContact_2Holder[0]/SupplierContact_2[0]", + "entityName" : "SupplierContact_2", + "entityKind" : "RowNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/IdentityRow[1]/SupplierColumn[0]/SupplierBlock[0]/SupplierContacts[1]/SupplierContact_2Layer[2]/SupplierContact_2Holder[0]", + "childIndex" : 0, + "depth" : 9, + "layer" : 9, + "computedX" : 20.031, + "computedY" : 644.954, + "placementX" : 20.031, + "placementY" : 644.954, + "placementWidth" : 226.067, + "placementHeight" : 10.92, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 226.067, + "contentHeight" : 10.92, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/IdentityRow[1]/SupplierColumn[0]/SupplierBlock[0]/SupplierContacts[1]/SupplierContact_2Layer[2]/SupplierContact_2Holder[0]/SupplierContact_2[0]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/IdentityRow[1]/SupplierColumn[0]/SupplierBlock[0]/SupplierContacts[1]/SupplierContact_2Layer[2]/SupplierContact_2Holder[0]/SupplierContact_2[0]", + "childIndex" : 0, + "depth" : 10, + "layer" : 10, + "computedX" : 20.031, + "computedY" : 644.954, + "placementX" : 20.031, + "placementY" : 644.954, + "placementWidth" : 23.878, + "placementHeight" : 10.92, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 23.878, + "contentHeight" : 10.92, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/IdentityRow[1]/SupplierColumn[0]/SupplierBlock[0]/SupplierContacts[1]/SupplierContact_2Layer[2]/SupplierContact_2Holder[0]/SupplierContact_2[0]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/IdentityRow[1]/SupplierColumn[0]/SupplierBlock[0]/SupplierContacts[1]/SupplierContact_2Layer[2]/SupplierContact_2Holder[0]/SupplierContact_2[0]", + "childIndex" : 1, + "depth" : 10, + "layer" : 10, + "computedX" : 67.991, + "computedY" : 644.954, + "placementX" : 67.991, + "placementY" : 644.954, + "placementWidth" : 108.144, + "placementHeight" : 10.92, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 108.144, + "contentHeight" : 10.92, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/IdentityRow[1]/SupplierColumn[0]/SupplierBlock[0]/SupplierContacts[1]/SupplierContact_3Layer[3]", + "entityName" : "SupplierContact_3Layer", + "entityKind" : "LayerStackNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/IdentityRow[1]/SupplierColumn[0]/SupplierBlock[0]/SupplierContacts[1]", + "childIndex" : 3, + "depth" : 7, + "layer" : 7, + "computedX" : 20.031, + "computedY" : 630.536, + "placementX" : 20.031, + "placementY" : 630.536, + "placementWidth" : 226.067, + "placementHeight" : 10.92, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 226.067, + "contentHeight" : 10.92, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/IdentityRow[1]/SupplierColumn[0]/SupplierBlock[0]/SupplierContacts[1]/SupplierContact_3Layer[3]/SupplierContact_3Holder[0]", + "entityName" : "SupplierContact_3Holder", + "entityKind" : "SectionNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/IdentityRow[1]/SupplierColumn[0]/SupplierBlock[0]/SupplierContacts[1]/SupplierContact_3Layer[3]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 20.031, + "computedY" : 630.536, + "placementX" : 20.031, + "placementY" : 630.536, + "placementWidth" : 226.067, + "placementHeight" : 10.92, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 226.067, + "contentHeight" : 10.92, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/IdentityRow[1]/SupplierColumn[0]/SupplierBlock[0]/SupplierContacts[1]/SupplierContact_3Layer[3]/SupplierContact_3Holder[0]/SupplierContact_3[0]", + "entityName" : "SupplierContact_3", + "entityKind" : "RowNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/IdentityRow[1]/SupplierColumn[0]/SupplierBlock[0]/SupplierContacts[1]/SupplierContact_3Layer[3]/SupplierContact_3Holder[0]", + "childIndex" : 0, + "depth" : 9, + "layer" : 9, + "computedX" : 20.031, + "computedY" : 630.536, + "placementX" : 20.031, + "placementY" : 630.536, + "placementWidth" : 226.067, + "placementHeight" : 10.92, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 226.067, + "contentHeight" : 10.92, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/IdentityRow[1]/SupplierColumn[0]/SupplierBlock[0]/SupplierContacts[1]/SupplierContact_3Layer[3]/SupplierContact_3Holder[0]/SupplierContact_3[0]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/IdentityRow[1]/SupplierColumn[0]/SupplierBlock[0]/SupplierContacts[1]/SupplierContact_3Layer[3]/SupplierContact_3Holder[0]/SupplierContact_3[0]", + "childIndex" : 0, + "depth" : 10, + "layer" : 10, + "computedX" : 20.031, + "computedY" : 630.536, + "placementX" : 20.031, + "placementY" : 630.536, + "placementWidth" : 35.799, + "placementHeight" : 10.92, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 35.799, + "contentHeight" : 10.92, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/IdentityRow[1]/SupplierColumn[0]/SupplierBlock[0]/SupplierContacts[1]/SupplierContact_3Layer[3]/SupplierContact_3Holder[0]/SupplierContact_3[0]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/IdentityRow[1]/SupplierColumn[0]/SupplierBlock[0]/SupplierContacts[1]/SupplierContact_3Layer[3]/SupplierContact_3Holder[0]/SupplierContact_3[0]", + "childIndex" : 1, + "depth" : 10, + "layer" : 10, + "computedX" : 67.991, + "computedY" : 630.536, + "placementX" : 67.991, + "placementY" : 630.536, + "placementWidth" : 108.117, + "placementHeight" : 10.92, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 108.117, + "contentHeight" : 10.92, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/IdentityRow[1]/MetaColumn[1]", + "entityName" : "MetaColumn", + "entityKind" : "SectionNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/IdentityRow[1]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 257.383, + "computedY" : 640.415, + "placementX" : 257.383, + "placementY" : 640.415, + "placementWidth" : 313.349, + "placementHeight" : 117.586, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 313.349, + "contentHeight" : 117.586, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 79.558 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/IdentityRow[1]/MetaColumn[1]/InvoiceMeta[0]", + "entityName" : "InvoiceMeta", + "entityKind" : "SectionNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/IdentityRow[1]/MetaColumn[1]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 336.941, + "computedY" : 640.415, + "placementX" : 336.941, + "placementY" : 640.415, + "placementWidth" : 233.79, + "placementHeight" : 111.379, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 233.79, + "contentHeight" : 111.379, + "margin" : { + "top" : 6.207, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/IdentityRow[1]/MetaColumn[1]/InvoiceMeta[0]/InvoiceMeta_0Layer[0]", + "entityName" : "InvoiceMeta_0Layer", + "entityKind" : "LayerStackNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/IdentityRow[1]/MetaColumn[1]/InvoiceMeta[0]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 336.941, + "computedY" : 740.154, + "placementX" : 336.941, + "placementY" : 740.154, + "placementWidth" : 233.79, + "placementHeight" : 11.64, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 233.79, + "contentHeight" : 11.64, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/IdentityRow[1]/MetaColumn[1]/InvoiceMeta[0]/InvoiceMeta_0Layer[0]/InvoiceMeta_0Holder[0]", + "entityName" : "InvoiceMeta_0Holder", + "entityKind" : "SectionNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/IdentityRow[1]/MetaColumn[1]/InvoiceMeta[0]/InvoiceMeta_0Layer[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 336.941, + "computedY" : 740.154, + "placementX" : 336.941, + "placementY" : 740.154, + "placementWidth" : 233.79, + "placementHeight" : 11.64, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 233.79, + "contentHeight" : 11.64, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/IdentityRow[1]/MetaColumn[1]/InvoiceMeta[0]/InvoiceMeta_0Layer[0]/InvoiceMeta_0Holder[0]/InvoiceMeta_0[0]", + "entityName" : "InvoiceMeta_0", + "entityKind" : "RowNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/IdentityRow[1]/MetaColumn[1]/InvoiceMeta[0]/InvoiceMeta_0Layer[0]/InvoiceMeta_0Holder[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 336.941, + "computedY" : 740.154, + "placementX" : 336.941, + "placementY" : 740.154, + "placementWidth" : 233.79, + "placementHeight" : 11.64, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 233.79, + "contentHeight" : 11.64, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/IdentityRow[1]/MetaColumn[1]/InvoiceMeta[0]/InvoiceMeta_0Layer[0]/InvoiceMeta_0Holder[0]/InvoiceMeta_0[0]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/IdentityRow[1]/MetaColumn[1]/InvoiceMeta[0]/InvoiceMeta_0Layer[0]/InvoiceMeta_0Holder[0]/InvoiceMeta_0[0]", + "childIndex" : 0, + "depth" : 9, + "layer" : 9, + "computedX" : 336.941, + "computedY" : 741.474, + "placementX" : 336.941, + "placementY" : 741.474, + "placementWidth" : 62.522, + "placementHeight" : 10.32, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 62.522, + "contentHeight" : 10.32, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/IdentityRow[1]/MetaColumn[1]/InvoiceMeta[0]/InvoiceMeta_0Layer[0]/InvoiceMeta_0Holder[0]/InvoiceMeta_0[0]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/IdentityRow[1]/MetaColumn[1]/InvoiceMeta[0]/InvoiceMeta_0Layer[0]/InvoiceMeta_0Holder[0]/InvoiceMeta_0[0]", + "childIndex" : 1, + "depth" : 9, + "layer" : 9, + "computedX" : 423.27, + "computedY" : 740.154, + "placementX" : 423.27, + "placementY" : 740.154, + "placementWidth" : 91.849, + "placementHeight" : 11.64, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 91.849, + "contentHeight" : 11.64, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/IdentityRow[1]/MetaColumn[1]/InvoiceMeta[0]/InvoiceMeta_1Layer[1]", + "entityName" : "InvoiceMeta_1Layer", + "entityKind" : "LayerStackNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/IdentityRow[1]/MetaColumn[1]/InvoiceMeta[0]", + "childIndex" : 1, + "depth" : 6, + "layer" : 6, + "computedX" : 336.941, + "computedY" : 720.206, + "placementX" : 336.941, + "placementY" : 720.206, + "placementWidth" : 233.79, + "placementHeight" : 10.92, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 233.79, + "contentHeight" : 10.92, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/IdentityRow[1]/MetaColumn[1]/InvoiceMeta[0]/InvoiceMeta_1Layer[1]/InvoiceMeta_1Holder[0]", + "entityName" : "InvoiceMeta_1Holder", + "entityKind" : "SectionNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/IdentityRow[1]/MetaColumn[1]/InvoiceMeta[0]/InvoiceMeta_1Layer[1]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 336.941, + "computedY" : 720.206, + "placementX" : 336.941, + "placementY" : 720.206, + "placementWidth" : 233.79, + "placementHeight" : 10.92, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 233.79, + "contentHeight" : 10.92, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/IdentityRow[1]/MetaColumn[1]/InvoiceMeta[0]/InvoiceMeta_1Layer[1]/InvoiceMeta_1Holder[0]/InvoiceMeta_1[0]", + "entityName" : "InvoiceMeta_1", + "entityKind" : "RowNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/IdentityRow[1]/MetaColumn[1]/InvoiceMeta[0]/InvoiceMeta_1Layer[1]/InvoiceMeta_1Holder[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 336.941, + "computedY" : 720.206, + "placementX" : 336.941, + "placementY" : 720.206, + "placementWidth" : 233.79, + "placementHeight" : 10.92, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 233.79, + "contentHeight" : 10.92, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/IdentityRow[1]/MetaColumn[1]/InvoiceMeta[0]/InvoiceMeta_1Layer[1]/InvoiceMeta_1Holder[0]/InvoiceMeta_1[0]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/IdentityRow[1]/MetaColumn[1]/InvoiceMeta[0]/InvoiceMeta_1Layer[1]/InvoiceMeta_1Holder[0]/InvoiceMeta_1[0]", + "childIndex" : 0, + "depth" : 9, + "layer" : 9, + "computedX" : 336.941, + "computedY" : 720.806, + "placementX" : 336.941, + "placementY" : 720.806, + "placementWidth" : 50.035, + "placementHeight" : 10.32, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 50.035, + "contentHeight" : 10.32, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/IdentityRow[1]/MetaColumn[1]/InvoiceMeta[0]/InvoiceMeta_1Layer[1]/InvoiceMeta_1Holder[0]/InvoiceMeta_1[0]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/IdentityRow[1]/MetaColumn[1]/InvoiceMeta[0]/InvoiceMeta_1Layer[1]/InvoiceMeta_1Holder[0]/InvoiceMeta_1[0]", + "childIndex" : 1, + "depth" : 9, + "layer" : 9, + "computedX" : 423.27, + "computedY" : 720.206, + "placementX" : 423.27, + "placementY" : 720.206, + "placementWidth" : 52.098, + "placementHeight" : 10.92, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 52.098, + "contentHeight" : 10.92, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/IdentityRow[1]/MetaColumn[1]/InvoiceMeta[0]/InvoiceMeta_2Layer[2]", + "entityName" : "InvoiceMeta_2Layer", + "entityKind" : "LayerStackNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/IdentityRow[1]/MetaColumn[1]/InvoiceMeta[0]", + "childIndex" : 2, + "depth" : 6, + "layer" : 6, + "computedX" : 336.941, + "computedY" : 700.258, + "placementX" : 336.941, + "placementY" : 700.258, + "placementWidth" : 233.79, + "placementHeight" : 10.92, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 233.79, + "contentHeight" : 10.92, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/IdentityRow[1]/MetaColumn[1]/InvoiceMeta[0]/InvoiceMeta_2Layer[2]/InvoiceMeta_2Holder[0]", + "entityName" : "InvoiceMeta_2Holder", + "entityKind" : "SectionNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/IdentityRow[1]/MetaColumn[1]/InvoiceMeta[0]/InvoiceMeta_2Layer[2]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 336.941, + "computedY" : 700.258, + "placementX" : 336.941, + "placementY" : 700.258, + "placementWidth" : 233.79, + "placementHeight" : 10.92, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 233.79, + "contentHeight" : 10.92, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/IdentityRow[1]/MetaColumn[1]/InvoiceMeta[0]/InvoiceMeta_2Layer[2]/InvoiceMeta_2Holder[0]/InvoiceMeta_2[0]", + "entityName" : "InvoiceMeta_2", + "entityKind" : "RowNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/IdentityRow[1]/MetaColumn[1]/InvoiceMeta[0]/InvoiceMeta_2Layer[2]/InvoiceMeta_2Holder[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 336.941, + "computedY" : 700.258, + "placementX" : 336.941, + "placementY" : 700.258, + "placementWidth" : 233.79, + "placementHeight" : 10.92, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 233.79, + "contentHeight" : 10.92, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/IdentityRow[1]/MetaColumn[1]/InvoiceMeta[0]/InvoiceMeta_2Layer[2]/InvoiceMeta_2Holder[0]/InvoiceMeta_2[0]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/IdentityRow[1]/MetaColumn[1]/InvoiceMeta[0]/InvoiceMeta_2Layer[2]/InvoiceMeta_2Holder[0]/InvoiceMeta_2[0]", + "childIndex" : 0, + "depth" : 9, + "layer" : 9, + "computedX" : 336.941, + "computedY" : 700.858, + "placementX" : 336.941, + "placementY" : 700.858, + "placementWidth" : 38.683, + "placementHeight" : 10.32, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 38.683, + "contentHeight" : 10.32, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/IdentityRow[1]/MetaColumn[1]/InvoiceMeta[0]/InvoiceMeta_2Layer[2]/InvoiceMeta_2Holder[0]/InvoiceMeta_2[0]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/IdentityRow[1]/MetaColumn[1]/InvoiceMeta[0]/InvoiceMeta_2Layer[2]/InvoiceMeta_2Holder[0]/InvoiceMeta_2[0]", + "childIndex" : 1, + "depth" : 9, + "layer" : 9, + "computedX" : 423.27, + "computedY" : 700.258, + "placementX" : 423.27, + "placementY" : 700.258, + "placementWidth" : 64.583, + "placementHeight" : 10.92, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 64.583, + "contentHeight" : 10.92, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/IdentityRow[1]/MetaColumn[1]/InvoiceMeta[0]/InvoiceMeta_3Layer[3]", + "entityName" : "InvoiceMeta_3Layer", + "entityKind" : "LayerStackNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/IdentityRow[1]/MetaColumn[1]/InvoiceMeta[0]", + "childIndex" : 3, + "depth" : 6, + "layer" : 6, + "computedX" : 336.941, + "computedY" : 680.31, + "placementX" : 336.941, + "placementY" : 680.31, + "placementWidth" : 233.79, + "placementHeight" : 10.92, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 233.79, + "contentHeight" : 10.92, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/IdentityRow[1]/MetaColumn[1]/InvoiceMeta[0]/InvoiceMeta_3Layer[3]/InvoiceMeta_3Holder[0]", + "entityName" : "InvoiceMeta_3Holder", + "entityKind" : "SectionNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/IdentityRow[1]/MetaColumn[1]/InvoiceMeta[0]/InvoiceMeta_3Layer[3]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 336.941, + "computedY" : 680.31, + "placementX" : 336.941, + "placementY" : 680.31, + "placementWidth" : 233.79, + "placementHeight" : 10.92, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 233.79, + "contentHeight" : 10.92, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/IdentityRow[1]/MetaColumn[1]/InvoiceMeta[0]/InvoiceMeta_3Layer[3]/InvoiceMeta_3Holder[0]/InvoiceMeta_3[0]", + "entityName" : "InvoiceMeta_3", + "entityKind" : "RowNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/IdentityRow[1]/MetaColumn[1]/InvoiceMeta[0]/InvoiceMeta_3Layer[3]/InvoiceMeta_3Holder[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 336.941, + "computedY" : 680.31, + "placementX" : 336.941, + "placementY" : 680.31, + "placementWidth" : 233.79, + "placementHeight" : 10.92, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 233.79, + "contentHeight" : 10.92, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/IdentityRow[1]/MetaColumn[1]/InvoiceMeta[0]/InvoiceMeta_3Layer[3]/InvoiceMeta_3Holder[0]/InvoiceMeta_3[0]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/IdentityRow[1]/MetaColumn[1]/InvoiceMeta[0]/InvoiceMeta_3Layer[3]/InvoiceMeta_3Holder[0]/InvoiceMeta_3[0]", + "childIndex" : 0, + "depth" : 9, + "layer" : 9, + "computedX" : 336.941, + "computedY" : 680.91, + "placementX" : 336.941, + "placementY" : 680.91, + "placementWidth" : 40.635, + "placementHeight" : 10.32, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 40.635, + "contentHeight" : 10.32, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/IdentityRow[1]/MetaColumn[1]/InvoiceMeta[0]/InvoiceMeta_3Layer[3]/InvoiceMeta_3Holder[0]/InvoiceMeta_3[0]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/IdentityRow[1]/MetaColumn[1]/InvoiceMeta[0]/InvoiceMeta_3Layer[3]/InvoiceMeta_3Holder[0]/InvoiceMeta_3[0]", + "childIndex" : 1, + "depth" : 9, + "layer" : 9, + "computedX" : 423.27, + "computedY" : 680.31, + "placementX" : 423.27, + "placementY" : 680.31, + "placementWidth" : 104.741, + "placementHeight" : 10.92, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 104.741, + "contentHeight" : 10.92, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/IdentityRow[1]/MetaColumn[1]/InvoiceMeta[0]/InvoiceMeta_4Layer[4]", + "entityName" : "InvoiceMeta_4Layer", + "entityKind" : "LayerStackNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/IdentityRow[1]/MetaColumn[1]/InvoiceMeta[0]", + "childIndex" : 4, + "depth" : 6, + "layer" : 6, + "computedX" : 336.941, + "computedY" : 660.363, + "placementX" : 336.941, + "placementY" : 660.363, + "placementWidth" : 233.79, + "placementHeight" : 10.92, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 233.79, + "contentHeight" : 10.92, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/IdentityRow[1]/MetaColumn[1]/InvoiceMeta[0]/InvoiceMeta_4Layer[4]/InvoiceMeta_4Holder[0]", + "entityName" : "InvoiceMeta_4Holder", + "entityKind" : "SectionNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/IdentityRow[1]/MetaColumn[1]/InvoiceMeta[0]/InvoiceMeta_4Layer[4]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 336.941, + "computedY" : 660.363, + "placementX" : 336.941, + "placementY" : 660.363, + "placementWidth" : 233.79, + "placementHeight" : 10.92, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 233.79, + "contentHeight" : 10.92, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/IdentityRow[1]/MetaColumn[1]/InvoiceMeta[0]/InvoiceMeta_4Layer[4]/InvoiceMeta_4Holder[0]/InvoiceMeta_4[0]", + "entityName" : "InvoiceMeta_4", + "entityKind" : "RowNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/IdentityRow[1]/MetaColumn[1]/InvoiceMeta[0]/InvoiceMeta_4Layer[4]/InvoiceMeta_4Holder[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 336.941, + "computedY" : 660.363, + "placementX" : 336.941, + "placementY" : 660.363, + "placementWidth" : 233.79, + "placementHeight" : 10.92, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 233.79, + "contentHeight" : 10.92, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/IdentityRow[1]/MetaColumn[1]/InvoiceMeta[0]/InvoiceMeta_4Layer[4]/InvoiceMeta_4Holder[0]/InvoiceMeta_4[0]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/IdentityRow[1]/MetaColumn[1]/InvoiceMeta[0]/InvoiceMeta_4Layer[4]/InvoiceMeta_4Holder[0]/InvoiceMeta_4[0]", + "childIndex" : 0, + "depth" : 9, + "layer" : 9, + "computedX" : 336.941, + "computedY" : 660.963, + "placementX" : 336.941, + "placementY" : 660.963, + "placementWidth" : 61.292, + "placementHeight" : 10.32, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 61.292, + "contentHeight" : 10.32, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/IdentityRow[1]/MetaColumn[1]/InvoiceMeta[0]/InvoiceMeta_4Layer[4]/InvoiceMeta_4Holder[0]/InvoiceMeta_4[0]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/IdentityRow[1]/MetaColumn[1]/InvoiceMeta[0]/InvoiceMeta_4Layer[4]/InvoiceMeta_4Holder[0]/InvoiceMeta_4[0]", + "childIndex" : 1, + "depth" : 9, + "layer" : 9, + "computedX" : 423.27, + "computedY" : 660.363, + "placementX" : 423.27, + "placementY" : 660.363, + "placementWidth" : 50.323, + "placementHeight" : 10.92, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 50.323, + "contentHeight" : 10.92, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/IdentityRow[1]/MetaColumn[1]/InvoiceMeta[0]/InvoiceMeta_5Layer[5]", + "entityName" : "InvoiceMeta_5Layer", + "entityKind" : "LayerStackNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/IdentityRow[1]/MetaColumn[1]/InvoiceMeta[0]", + "childIndex" : 5, + "depth" : 6, + "layer" : 6, + "computedX" : 336.941, + "computedY" : 640.415, + "placementX" : 336.941, + "placementY" : 640.415, + "placementWidth" : 233.79, + "placementHeight" : 10.92, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 233.79, + "contentHeight" : 10.92, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/IdentityRow[1]/MetaColumn[1]/InvoiceMeta[0]/InvoiceMeta_5Layer[5]/InvoiceMeta_5Holder[0]", + "entityName" : "InvoiceMeta_5Holder", + "entityKind" : "SectionNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/IdentityRow[1]/MetaColumn[1]/InvoiceMeta[0]/InvoiceMeta_5Layer[5]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 336.941, + "computedY" : 640.415, + "placementX" : 336.941, + "placementY" : 640.415, + "placementWidth" : 233.79, + "placementHeight" : 10.92, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 233.79, + "contentHeight" : 10.92, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/IdentityRow[1]/MetaColumn[1]/InvoiceMeta[0]/InvoiceMeta_5Layer[5]/InvoiceMeta_5Holder[0]/InvoiceMeta_5[0]", + "entityName" : "InvoiceMeta_5", + "entityKind" : "RowNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/IdentityRow[1]/MetaColumn[1]/InvoiceMeta[0]/InvoiceMeta_5Layer[5]/InvoiceMeta_5Holder[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 336.941, + "computedY" : 640.415, + "placementX" : 336.941, + "placementY" : 640.415, + "placementWidth" : 233.79, + "placementHeight" : 10.92, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 233.79, + "contentHeight" : 10.92, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/IdentityRow[1]/MetaColumn[1]/InvoiceMeta[0]/InvoiceMeta_5Layer[5]/InvoiceMeta_5Holder[0]/InvoiceMeta_5[0]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/IdentityRow[1]/MetaColumn[1]/InvoiceMeta[0]/InvoiceMeta_5Layer[5]/InvoiceMeta_5Holder[0]/InvoiceMeta_5[0]", + "childIndex" : 0, + "depth" : 9, + "layer" : 9, + "computedX" : 336.941, + "computedY" : 641.015, + "placementX" : 336.941, + "placementY" : 641.015, + "placementWidth" : 36.842, + "placementHeight" : 10.32, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 36.842, + "contentHeight" : 10.32, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/IdentityRow[1]/MetaColumn[1]/InvoiceMeta[0]/InvoiceMeta_5Layer[5]/InvoiceMeta_5Holder[0]/InvoiceMeta_5[0]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/IdentityRow[1]/MetaColumn[1]/InvoiceMeta[0]/InvoiceMeta_5Layer[5]/InvoiceMeta_5Holder[0]/InvoiceMeta_5[0]", + "childIndex" : 1, + "depth" : 9, + "layer" : 9, + "computedX" : 423.27, + "computedY" : 640.415, + "placementX" : 423.27, + "placementY" : 640.415, + "placementWidth" : 18.009, + "placementHeight" : 10.92, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 18.009, + "contentHeight" : 10.92, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/IdentityRule[2]", + "entityName" : "IdentityRule", + "entityKind" : "ShapeNode", + "parentPath" : "MeteredInvoice[0]/Body[0]", + "childIndex" : 2, + "depth" : 3, + "layer" : 3, + "computedX" : 20.031, + "computedY" : 609.264, + "placementX" : 20.031, + "placementY" : 609.264, + "placementWidth" : 550.7, + "placementHeight" : 0.959, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 550.7, + "contentHeight" : 0.959, + "margin" : { + "top" : 7.899, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/PartiesRow[3]", + "entityName" : "PartiesRow", + "entityKind" : "RowNode", + "parentPath" : "MeteredInvoice[0]/Body[0]", + "childIndex" : 3, + "depth" : 3, + "layer" : 3, + "computedX" : 20.031, + "computedY" : 497.688, + "placementX" : 20.031, + "placementY" : 497.688, + "placementWidth" : 550.7, + "placementHeight" : 94.084, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 550.7, + "contentHeight" : 94.084, + "margin" : { + "top" : 5.078, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/PartiesRow[3]/BillToColumn[0]", + "entityName" : "BillToColumn", + "entityKind" : "SectionNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/PartiesRow[3]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 20.031, + "computedY" : 497.688, + "placementX" : 20.031, + "placementY" : 497.688, + "placementWidth" : 272.046, + "placementHeight" : 94.084, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 272.046, + "contentHeight" : 94.084, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 11.285, + "bottom" : 0.0, + "left" : 4.232 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/PartiesRow[3]/BillToColumn[0]/BillTo[0]", + "entityName" : "BillTo", + "entityKind" : "SectionNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/PartiesRow[3]/BillToColumn[0]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 24.262, + "computedY" : 497.688, + "placementX" : 24.262, + "placementY" : 497.688, + "placementWidth" : 256.529, + "placementHeight" : 94.084, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 256.529, + "contentHeight" : 94.084, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/PartiesRow[3]/BillToColumn[0]/BillTo[0]/BillToHeadingLayer[0]", + "entityName" : "BillToHeadingLayer", + "entityKind" : "LayerStackNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/PartiesRow[3]/BillToColumn[0]/BillTo[0]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 24.262, + "computedY" : 578.231, + "placementX" : 24.262, + "placementY" : 578.231, + "placementWidth" : 256.529, + "placementHeight" : 13.542, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 256.529, + "contentHeight" : 13.542, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/PartiesRow[3]/BillToColumn[0]/BillTo[0]/BillToHeadingLayer[0]/BillToHeadingHolder[0]", + "entityName" : "BillToHeadingHolder", + "entityKind" : "SectionNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/PartiesRow[3]/BillToColumn[0]/BillTo[0]/BillToHeadingLayer[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 24.262, + "computedY" : 578.231, + "placementX" : 24.262, + "placementY" : 578.231, + "placementWidth" : 256.529, + "placementHeight" : 13.542, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 256.529, + "contentHeight" : 13.542, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/PartiesRow[3]/BillToColumn[0]/BillTo[0]/BillToHeadingLayer[0]/BillToHeadingHolder[0]/BillToHeading[0]", + "entityName" : "BillToHeading", + "entityKind" : "RowNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/PartiesRow[3]/BillToColumn[0]/BillTo[0]/BillToHeadingLayer[0]/BillToHeadingHolder[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 24.262, + "computedY" : 578.231, + "placementX" : 24.262, + "placementY" : 578.231, + "placementWidth" : 256.529, + "placementHeight" : 13.542, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 256.529, + "contentHeight" : 13.542, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/PartiesRow[3]/BillToColumn[0]/BillTo[0]/BillToHeadingLayer[0]/BillToHeadingHolder[0]/BillToHeading[0]/BillToHeadingIcon[0]", + "entityName" : "BillToHeadingIcon", + "entityKind" : "SectionNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/PartiesRow[3]/BillToColumn[0]/BillTo[0]/BillToHeadingLayer[0]/BillToHeadingHolder[0]/BillToHeading[0]", + "childIndex" : 0, + "depth" : 9, + "layer" : 9, + "computedX" : 24.262, + "computedY" : 578.231, + "placementX" : 24.262, + "placementY" : 578.231, + "placementWidth" : 13.542, + "placementHeight" : 13.542, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 13.542, + "contentHeight" : 13.542, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/PartiesRow[3]/BillToColumn[0]/BillTo[0]/BillToHeadingLayer[0]/BillToHeadingHolder[0]/BillToHeading[0]/BillToHeadingIcon[0]/SvgIcon[0]", + "entityName" : "SvgIcon", + "entityKind" : "LayerStackNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/PartiesRow[3]/BillToColumn[0]/BillTo[0]/BillToHeadingLayer[0]/BillToHeadingHolder[0]/BillToHeading[0]/BillToHeadingIcon[0]", + "childIndex" : 0, + "depth" : 10, + "layer" : 10, + "computedX" : 24.262, + "computedY" : 578.231, + "placementX" : 24.262, + "placementY" : 578.231, + "placementWidth" : 13.542, + "placementHeight" : 13.542, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 13.542, + "contentHeight" : 13.542, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/PartiesRow[3]/BillToColumn[0]/BillTo[0]/BillToHeadingLayer[0]/BillToHeadingHolder[0]/BillToHeading[0]/BillToHeadingIcon[0]/SvgIcon[0]/SvgLayer0[0]", + "entityName" : "SvgLayer0", + "entityKind" : "Path", + "parentPath" : "MeteredInvoice[0]/Body[0]/PartiesRow[3]/BillToColumn[0]/BillTo[0]/BillToHeadingLayer[0]/BillToHeadingHolder[0]/BillToHeading[0]/BillToHeadingIcon[0]/SvgIcon[0]", + "childIndex" : 0, + "depth" : 11, + "layer" : 11, + "computedX" : 24.262, + "computedY" : 578.231, + "placementX" : 24.262, + "placementY" : 578.231, + "placementWidth" : 13.542, + "placementHeight" : 13.542, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 13.542, + "contentHeight" : 13.542, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/PartiesRow[3]/BillToColumn[0]/BillTo[0]/BillToHeadingLayer[0]/BillToHeadingHolder[0]/BillToHeading[0]/BillToHeadingIcon[0]/SvgIcon[0]/SvgLayer1[1]", + "entityName" : "SvgLayer1", + "entityKind" : "Path", + "parentPath" : "MeteredInvoice[0]/Body[0]/PartiesRow[3]/BillToColumn[0]/BillTo[0]/BillToHeadingLayer[0]/BillToHeadingHolder[0]/BillToHeading[0]/BillToHeadingIcon[0]/SvgIcon[0]", + "childIndex" : 1, + "depth" : 11, + "layer" : 11, + "computedX" : 24.262, + "computedY" : 578.231, + "placementX" : 24.262, + "placementY" : 578.231, + "placementWidth" : 13.542, + "placementHeight" : 13.542, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 13.542, + "contentHeight" : 13.542, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/PartiesRow[3]/BillToColumn[0]/BillTo[0]/BillToHeadingLayer[0]/BillToHeadingHolder[0]/BillToHeading[0]/BillToHeadingLabel[1]", + "entityName" : "BillToHeadingLabel", + "entityKind" : "ParagraphNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/PartiesRow[3]/BillToColumn[0]/BillTo[0]/BillToHeadingLayer[0]/BillToHeadingHolder[0]/BillToHeading[0]", + "childIndex" : 1, + "depth" : 9, + "layer" : 9, + "computedX" : 46.268, + "computedY" : 580.261, + "placementX" : 46.268, + "placementY" : 580.261, + "placementWidth" : 28.811, + "placementHeight" : 9.48, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 28.811, + "contentHeight" : 9.48, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/PartiesRow[3]/BillToColumn[0]/BillTo[0]/BillToBody[1]", + "entityName" : "BillToBody", + "entityKind" : "SectionNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/PartiesRow[3]/BillToColumn[0]/BillTo[0]", + "childIndex" : 1, + "depth" : 6, + "layer" : 6, + "computedX" : 24.262, + "computedY" : 497.688, + "placementX" : 24.262, + "placementY" : 497.688, + "placementWidth" : 256.529, + "placementHeight" : 75.464, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 256.529, + "contentHeight" : 75.464, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 22.005 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/PartiesRow[3]/BillToColumn[0]/BillTo[0]/BillToBody[1]/BillToName[0]", + "entityName" : "BillToName", + "entityKind" : "ParagraphNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/PartiesRow[3]/BillToColumn[0]/BillTo[0]/BillToBody[1]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 46.268, + "computedY" : 560.552, + "placementX" : 46.268, + "placementY" : 560.552, + "placementWidth" : 83.024, + "placementHeight" : 12.6, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 83.024, + "contentHeight" : 12.6, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/PartiesRow[3]/BillToColumn[0]/BillTo[0]/BillToBody[1]/BillToAddress_0[1]", + "entityName" : "BillToAddress_0", + "entityKind" : "ParagraphNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/PartiesRow[3]/BillToColumn[0]/BillTo[0]/BillToBody[1]", + "childIndex" : 1, + "depth" : 7, + "layer" : 7, + "computedX" : 46.268, + "computedY" : 546.811, + "placementX" : 46.268, + "placementY" : 546.811, + "placementWidth" : 57.639, + "placementHeight" : 10.92, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 57.639, + "contentHeight" : 10.92, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/PartiesRow[3]/BillToColumn[0]/BillTo[0]/BillToBody[1]/BillToAddress_1[2]", + "entityName" : "BillToAddress_1", + "entityKind" : "ParagraphNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/PartiesRow[3]/BillToColumn[0]/BillTo[0]/BillToBody[1]", + "childIndex" : 2, + "depth" : 7, + "layer" : 7, + "computedX" : 46.268, + "computedY" : 533.07, + "placementX" : 46.268, + "placementY" : 533.07, + "placementWidth" : 89.471, + "placementHeight" : 10.92, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 89.471, + "contentHeight" : 10.92, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/PartiesRow[3]/BillToColumn[0]/BillTo[0]/BillToBody[1]/BillToAddress_2[3]", + "entityName" : "BillToAddress_2", + "entityKind" : "ParagraphNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/PartiesRow[3]/BillToColumn[0]/BillTo[0]/BillToBody[1]", + "childIndex" : 3, + "depth" : 7, + "layer" : 7, + "computedX" : 46.268, + "computedY" : 519.329, + "placementX" : 46.268, + "placementY" : 519.329, + "placementWidth" : 65.229, + "placementHeight" : 10.92, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 65.229, + "contentHeight" : 10.92, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/PartiesRow[3]/BillToColumn[0]/BillTo[0]/BillToBody[1]/BillToRegistrationLayer[4]", + "entityName" : "BillToRegistrationLayer", + "entityKind" : "LayerStackNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/PartiesRow[3]/BillToColumn[0]/BillTo[0]/BillToBody[1]", + "childIndex" : 4, + "depth" : 7, + "layer" : 7, + "computedX" : 46.268, + "computedY" : 497.688, + "placementX" : 46.268, + "placementY" : 497.688, + "placementWidth" : 234.524, + "placementHeight" : 18.819, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 234.524, + "contentHeight" : 18.819, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/PartiesRow[3]/BillToColumn[0]/BillTo[0]/BillToBody[1]/BillToRegistrationLayer[4]/BillToRegistrationHolder[0]", + "entityName" : "BillToRegistrationHolder", + "entityKind" : "SectionNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/PartiesRow[3]/BillToColumn[0]/BillTo[0]/BillToBody[1]/BillToRegistrationLayer[4]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 46.268, + "computedY" : 497.688, + "placementX" : 46.268, + "placementY" : 497.688, + "placementWidth" : 234.524, + "placementHeight" : 18.819, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 234.524, + "contentHeight" : 18.819, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/PartiesRow[3]/BillToColumn[0]/BillTo[0]/BillToBody[1]/BillToRegistrationLayer[4]/BillToRegistrationHolder[0]/BillToRegistration[0]", + "entityName" : "BillToRegistration", + "entityKind" : "RowNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/PartiesRow[3]/BillToColumn[0]/BillTo[0]/BillToBody[1]/BillToRegistrationLayer[4]/BillToRegistrationHolder[0]", + "childIndex" : 0, + "depth" : 9, + "layer" : 9, + "computedX" : 46.268, + "computedY" : 497.688, + "placementX" : 46.268, + "placementY" : 497.688, + "placementWidth" : 234.524, + "placementHeight" : 10.92, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 234.524, + "contentHeight" : 10.92, + "margin" : { + "top" : 7.899, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/PartiesRow[3]/BillToColumn[0]/BillTo[0]/BillToBody[1]/BillToRegistrationLayer[4]/BillToRegistrationHolder[0]/BillToRegistration[0]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/PartiesRow[3]/BillToColumn[0]/BillTo[0]/BillToBody[1]/BillToRegistrationLayer[4]/BillToRegistrationHolder[0]/BillToRegistration[0]", + "childIndex" : 0, + "depth" : 10, + "layer" : 10, + "computedX" : 46.268, + "computedY" : 497.688, + "placementX" : 46.268, + "placementY" : 497.688, + "placementWidth" : 31.777, + "placementHeight" : 10.92, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 31.777, + "contentHeight" : 10.92, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/PartiesRow[3]/BillToColumn[0]/BillTo[0]/BillToBody[1]/BillToRegistrationLayer[4]/BillToRegistrationHolder[0]/BillToRegistration[0]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/PartiesRow[3]/BillToColumn[0]/BillTo[0]/BillToBody[1]/BillToRegistrationLayer[4]/BillToRegistrationHolder[0]/BillToRegistration[0]", + "childIndex" : 1, + "depth" : 10, + "layer" : 10, + "computedX" : 86.329, + "computedY" : 497.688, + "placementX" : 86.329, + "placementY" : 497.688, + "placementWidth" : 67.031, + "placementHeight" : 10.92, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 67.031, + "contentHeight" : 10.92, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/PartiesRow[3]/ShipToColumn[1]", + "entityName" : "ShipToColumn", + "entityKind" : "SectionNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/PartiesRow[3]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 292.077, + "computedY" : 505.587, + "placementX" : 292.077, + "placementY" : 505.587, + "placementWidth" : 278.654, + "placementHeight" : 86.185, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 278.654, + "contentHeight" : 86.185, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 41.754 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/PartiesRow[3]/ShipToColumn[1]/ShipTo[0]", + "entityName" : "ShipTo", + "entityKind" : "SectionNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/PartiesRow[3]/ShipToColumn[1]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 333.831, + "computedY" : 505.587, + "placementX" : 333.831, + "placementY" : 505.587, + "placementWidth" : 236.901, + "placementHeight" : 86.185, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 236.901, + "contentHeight" : 86.185, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/PartiesRow[3]/ShipToColumn[1]/ShipTo[0]/ShipToHeadingLayer[0]", + "entityName" : "ShipToHeadingLayer", + "entityKind" : "LayerStackNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/PartiesRow[3]/ShipToColumn[1]/ShipTo[0]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 333.831, + "computedY" : 578.231, + "placementX" : 333.831, + "placementY" : 578.231, + "placementWidth" : 236.901, + "placementHeight" : 13.542, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 236.901, + "contentHeight" : 13.542, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/PartiesRow[3]/ShipToColumn[1]/ShipTo[0]/ShipToHeadingLayer[0]/ShipToHeadingHolder[0]", + "entityName" : "ShipToHeadingHolder", + "entityKind" : "SectionNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/PartiesRow[3]/ShipToColumn[1]/ShipTo[0]/ShipToHeadingLayer[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 333.831, + "computedY" : 578.231, + "placementX" : 333.831, + "placementY" : 578.231, + "placementWidth" : 236.901, + "placementHeight" : 13.542, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 236.901, + "contentHeight" : 13.542, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/PartiesRow[3]/ShipToColumn[1]/ShipTo[0]/ShipToHeadingLayer[0]/ShipToHeadingHolder[0]/ShipToHeading[0]", + "entityName" : "ShipToHeading", + "entityKind" : "RowNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/PartiesRow[3]/ShipToColumn[1]/ShipTo[0]/ShipToHeadingLayer[0]/ShipToHeadingHolder[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 333.831, + "computedY" : 578.231, + "placementX" : 333.831, + "placementY" : 578.231, + "placementWidth" : 236.901, + "placementHeight" : 13.542, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 236.901, + "contentHeight" : 13.542, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/PartiesRow[3]/ShipToColumn[1]/ShipTo[0]/ShipToHeadingLayer[0]/ShipToHeadingHolder[0]/ShipToHeading[0]/ShipToHeadingIcon[0]", + "entityName" : "ShipToHeadingIcon", + "entityKind" : "SectionNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/PartiesRow[3]/ShipToColumn[1]/ShipTo[0]/ShipToHeadingLayer[0]/ShipToHeadingHolder[0]/ShipToHeading[0]", + "childIndex" : 0, + "depth" : 9, + "layer" : 9, + "computedX" : 333.831, + "computedY" : 578.231, + "placementX" : 333.831, + "placementY" : 578.231, + "placementWidth" : 13.542, + "placementHeight" : 13.542, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 13.542, + "contentHeight" : 13.542, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/PartiesRow[3]/ShipToColumn[1]/ShipTo[0]/ShipToHeadingLayer[0]/ShipToHeadingHolder[0]/ShipToHeading[0]/ShipToHeadingIcon[0]/SvgIcon[0]", + "entityName" : "SvgIcon", + "entityKind" : "LayerStackNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/PartiesRow[3]/ShipToColumn[1]/ShipTo[0]/ShipToHeadingLayer[0]/ShipToHeadingHolder[0]/ShipToHeading[0]/ShipToHeadingIcon[0]", + "childIndex" : 0, + "depth" : 10, + "layer" : 10, + "computedX" : 333.831, + "computedY" : 578.231, + "placementX" : 333.831, + "placementY" : 578.231, + "placementWidth" : 13.542, + "placementHeight" : 13.542, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 13.542, + "contentHeight" : 13.542, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/PartiesRow[3]/ShipToColumn[1]/ShipTo[0]/ShipToHeadingLayer[0]/ShipToHeadingHolder[0]/ShipToHeading[0]/ShipToHeadingIcon[0]/SvgIcon[0]/SvgLayer0[0]", + "entityName" : "SvgLayer0", + "entityKind" : "Path", + "parentPath" : "MeteredInvoice[0]/Body[0]/PartiesRow[3]/ShipToColumn[1]/ShipTo[0]/ShipToHeadingLayer[0]/ShipToHeadingHolder[0]/ShipToHeading[0]/ShipToHeadingIcon[0]/SvgIcon[0]", + "childIndex" : 0, + "depth" : 11, + "layer" : 11, + "computedX" : 333.831, + "computedY" : 578.231, + "placementX" : 333.831, + "placementY" : 578.231, + "placementWidth" : 13.542, + "placementHeight" : 13.542, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 13.542, + "contentHeight" : 13.542, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/PartiesRow[3]/ShipToColumn[1]/ShipTo[0]/ShipToHeadingLayer[0]/ShipToHeadingHolder[0]/ShipToHeading[0]/ShipToHeadingIcon[0]/SvgIcon[0]/SvgLayer1[1]", + "entityName" : "SvgLayer1", + "entityKind" : "Path", + "parentPath" : "MeteredInvoice[0]/Body[0]/PartiesRow[3]/ShipToColumn[1]/ShipTo[0]/ShipToHeadingLayer[0]/ShipToHeadingHolder[0]/ShipToHeading[0]/ShipToHeadingIcon[0]/SvgIcon[0]", + "childIndex" : 1, + "depth" : 11, + "layer" : 11, + "computedX" : 333.831, + "computedY" : 578.231, + "placementX" : 333.831, + "placementY" : 578.231, + "placementWidth" : 13.542, + "placementHeight" : 13.542, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 13.542, + "contentHeight" : 13.542, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/PartiesRow[3]/ShipToColumn[1]/ShipTo[0]/ShipToHeadingLayer[0]/ShipToHeadingHolder[0]/ShipToHeading[0]/ShipToHeadingLabel[1]", + "entityName" : "ShipToHeadingLabel", + "entityKind" : "ParagraphNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/PartiesRow[3]/ShipToColumn[1]/ShipTo[0]/ShipToHeadingLayer[0]/ShipToHeadingHolder[0]/ShipToHeading[0]", + "childIndex" : 1, + "depth" : 9, + "layer" : 9, + "computedX" : 355.836, + "computedY" : 580.261, + "placementX" : 355.836, + "placementY" : 580.261, + "placementWidth" : 30.707, + "placementHeight" : 9.48, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 30.707, + "contentHeight" : 9.48, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/PartiesRow[3]/ShipToColumn[1]/ShipTo[0]/ShipToBody[1]", + "entityName" : "ShipToBody", + "entityKind" : "SectionNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/PartiesRow[3]/ShipToColumn[1]/ShipTo[0]", + "childIndex" : 1, + "depth" : 6, + "layer" : 6, + "computedX" : 333.831, + "computedY" : 505.587, + "placementX" : 333.831, + "placementY" : 505.587, + "placementWidth" : 112.669, + "placementHeight" : 67.565, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 112.669, + "contentHeight" : 67.565, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 22.005 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/PartiesRow[3]/ShipToColumn[1]/ShipTo[0]/ShipToBody[1]/ShipToName[0]", + "entityName" : "ShipToName", + "entityKind" : "ParagraphNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/PartiesRow[3]/ShipToColumn[1]/ShipTo[0]/ShipToBody[1]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 355.836, + "computedY" : 560.552, + "placementX" : 355.836, + "placementY" : 560.552, + "placementWidth" : 83.024, + "placementHeight" : 12.6, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 83.024, + "contentHeight" : 12.6, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/PartiesRow[3]/ShipToColumn[1]/ShipTo[0]/ShipToBody[1]/ShipToAddress_0[1]", + "entityName" : "ShipToAddress_0", + "entityKind" : "ParagraphNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/PartiesRow[3]/ShipToColumn[1]/ShipTo[0]/ShipToBody[1]", + "childIndex" : 1, + "depth" : 7, + "layer" : 7, + "computedX" : 355.836, + "computedY" : 546.811, + "placementX" : 355.836, + "placementY" : 546.811, + "placementWidth" : 53.736, + "placementHeight" : 10.92, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 53.736, + "contentHeight" : 10.92, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/PartiesRow[3]/ShipToColumn[1]/ShipTo[0]/ShipToBody[1]/ShipToAddress_1[2]", + "entityName" : "ShipToAddress_1", + "entityKind" : "ParagraphNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/PartiesRow[3]/ShipToColumn[1]/ShipTo[0]/ShipToBody[1]", + "childIndex" : 2, + "depth" : 7, + "layer" : 7, + "computedX" : 355.836, + "computedY" : 533.07, + "placementX" : 355.836, + "placementY" : 533.07, + "placementWidth" : 90.663, + "placementHeight" : 10.92, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 90.663, + "contentHeight" : 10.92, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/PartiesRow[3]/ShipToColumn[1]/ShipTo[0]/ShipToBody[1]/ShipToAddress_2[3]", + "entityName" : "ShipToAddress_2", + "entityKind" : "ParagraphNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/PartiesRow[3]/ShipToColumn[1]/ShipTo[0]/ShipToBody[1]", + "childIndex" : 3, + "depth" : 7, + "layer" : 7, + "computedX" : 355.836, + "computedY" : 519.329, + "placementX" : 355.836, + "placementY" : 519.329, + "placementWidth" : 89.471, + "placementHeight" : 10.92, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 89.471, + "contentHeight" : 10.92, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/PartiesRow[3]/ShipToColumn[1]/ShipTo[0]/ShipToBody[1]/ShipToAddress_3[4]", + "entityName" : "ShipToAddress_3", + "entityKind" : "ParagraphNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/PartiesRow[3]/ShipToColumn[1]/ShipTo[0]/ShipToBody[1]", + "childIndex" : 4, + "depth" : 7, + "layer" : 7, + "computedX" : 355.836, + "computedY" : 505.587, + "placementX" : 355.836, + "placementY" : 505.587, + "placementWidth" : 65.229, + "placementHeight" : 10.92, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 65.229, + "contentHeight" : 10.92, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/LineItems[4]", + "entityName" : "LineItems", + "entityKind" : "TableNode", + "parentPath" : "MeteredInvoice[0]/Body[0]", + "childIndex" : 4, + "depth" : 3, + "layer" : 3, + "computedX" : 20.031, + "computedY" : 248.321, + "placementX" : 20.031, + "placementY" : 248.321, + "placementWidth" : 550.7, + "placementHeight" : 230.183, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 550.7, + "contentHeight" : 230.183, + "margin" : { + "top" : 6.771, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/ClosingRow[5]", + "entityName" : "ClosingRow", + "entityKind" : "RowNode", + "parentPath" : "MeteredInvoice[0]/Body[0]", + "childIndex" : 5, + "depth" : 3, + "layer" : 3, + "computedX" : 20.031, + "computedY" : 105.426, + "placementX" : 20.031, + "placementY" : 105.426, + "placementWidth" : 550.7, + "placementHeight" : 130.482, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 550.7, + "contentHeight" : 130.482, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/ClosingRow[5]/PaymentColumn[0]", + "entityName" : "PaymentColumn", + "entityKind" : "SectionNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/ClosingRow[5]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 20.031, + "computedY" : 105.426, + "placementX" : 20.031, + "placementY" : 105.426, + "placementWidth" : 243.188, + "placementHeight" : 127.229, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 243.188, + "contentHeight" : 127.229, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/ClosingRow[5]/PaymentColumn[0]/PaymentDetails[0]", + "entityName" : "PaymentDetails", + "entityKind" : "SectionNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/ClosingRow[5]/PaymentColumn[0]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 20.031, + "computedY" : 105.426, + "placementX" : 20.031, + "placementY" : 105.426, + "placementWidth" : 243.188, + "placementHeight" : 127.229, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 243.188, + "contentHeight" : 127.229, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 11.849, + "right" : 11.849, + "bottom" : 11.849, + "left" : 11.849 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/ClosingRow[5]/PaymentColumn[0]/PaymentDetails[0]/PaymentDetailsHeadingLayer[0]", + "entityName" : "PaymentDetailsHeadingLayer", + "entityKind" : "LayerStackNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/ClosingRow[5]/PaymentColumn[0]/PaymentDetails[0]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 31.88, + "computedY" : 206.135, + "placementX" : 31.88, + "placementY" : 206.135, + "placementWidth" : 219.49, + "placementHeight" : 14.67, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 219.49, + "contentHeight" : 14.67, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/ClosingRow[5]/PaymentColumn[0]/PaymentDetails[0]/PaymentDetailsHeadingLayer[0]/PaymentDetailsHeadingHolder[0]", + "entityName" : "PaymentDetailsHeadingHolder", + "entityKind" : "SectionNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/ClosingRow[5]/PaymentColumn[0]/PaymentDetails[0]/PaymentDetailsHeadingLayer[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 31.88, + "computedY" : 206.135, + "placementX" : 31.88, + "placementY" : 206.135, + "placementWidth" : 219.49, + "placementHeight" : 14.67, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 219.49, + "contentHeight" : 14.67, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/ClosingRow[5]/PaymentColumn[0]/PaymentDetails[0]/PaymentDetailsHeadingLayer[0]/PaymentDetailsHeadingHolder[0]/PaymentDetailsHeading[0]", + "entityName" : "PaymentDetailsHeading", + "entityKind" : "RowNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/ClosingRow[5]/PaymentColumn[0]/PaymentDetails[0]/PaymentDetailsHeadingLayer[0]/PaymentDetailsHeadingHolder[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 31.88, + "computedY" : 206.135, + "placementX" : 31.88, + "placementY" : 206.135, + "placementWidth" : 219.49, + "placementHeight" : 14.67, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 219.49, + "contentHeight" : 14.67, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/ClosingRow[5]/PaymentColumn[0]/PaymentDetails[0]/PaymentDetailsHeadingLayer[0]/PaymentDetailsHeadingHolder[0]/PaymentDetailsHeading[0]/PaymentDetailsHeadingIcon[0]", + "entityName" : "PaymentDetailsHeadingIcon", + "entityKind" : "SectionNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/ClosingRow[5]/PaymentColumn[0]/PaymentDetails[0]/PaymentDetailsHeadingLayer[0]/PaymentDetailsHeadingHolder[0]/PaymentDetailsHeading[0]", + "childIndex" : 0, + "depth" : 9, + "layer" : 9, + "computedX" : 31.88, + "computedY" : 206.135, + "placementX" : 31.88, + "placementY" : 206.135, + "placementWidth" : 14.67, + "placementHeight" : 14.67, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 14.67, + "contentHeight" : 14.67, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/ClosingRow[5]/PaymentColumn[0]/PaymentDetails[0]/PaymentDetailsHeadingLayer[0]/PaymentDetailsHeadingHolder[0]/PaymentDetailsHeading[0]/PaymentDetailsHeadingIcon[0]/SvgIcon[0]", + "entityName" : "SvgIcon", + "entityKind" : "LayerStackNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/ClosingRow[5]/PaymentColumn[0]/PaymentDetails[0]/PaymentDetailsHeadingLayer[0]/PaymentDetailsHeadingHolder[0]/PaymentDetailsHeading[0]/PaymentDetailsHeadingIcon[0]", + "childIndex" : 0, + "depth" : 10, + "layer" : 10, + "computedX" : 31.88, + "computedY" : 206.135, + "placementX" : 31.88, + "placementY" : 206.135, + "placementWidth" : 14.67, + "placementHeight" : 14.67, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 14.67, + "contentHeight" : 14.67, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/ClosingRow[5]/PaymentColumn[0]/PaymentDetails[0]/PaymentDetailsHeadingLayer[0]/PaymentDetailsHeadingHolder[0]/PaymentDetailsHeading[0]/PaymentDetailsHeadingIcon[0]/SvgIcon[0]/SvgLayer0[0]", + "entityName" : "SvgLayer0", + "entityKind" : "Path", + "parentPath" : "MeteredInvoice[0]/Body[0]/ClosingRow[5]/PaymentColumn[0]/PaymentDetails[0]/PaymentDetailsHeadingLayer[0]/PaymentDetailsHeadingHolder[0]/PaymentDetailsHeading[0]/PaymentDetailsHeadingIcon[0]/SvgIcon[0]", + "childIndex" : 0, + "depth" : 11, + "layer" : 11, + "computedX" : 31.88, + "computedY" : 206.135, + "placementX" : 31.88, + "placementY" : 206.135, + "placementWidth" : 14.67, + "placementHeight" : 14.67, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 14.67, + "contentHeight" : 14.67, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/ClosingRow[5]/PaymentColumn[0]/PaymentDetails[0]/PaymentDetailsHeadingLayer[0]/PaymentDetailsHeadingHolder[0]/PaymentDetailsHeading[0]/PaymentDetailsHeadingIcon[0]/SvgIcon[0]/SvgLayer1[1]", + "entityName" : "SvgLayer1", + "entityKind" : "Path", + "parentPath" : "MeteredInvoice[0]/Body[0]/ClosingRow[5]/PaymentColumn[0]/PaymentDetails[0]/PaymentDetailsHeadingLayer[0]/PaymentDetailsHeadingHolder[0]/PaymentDetailsHeading[0]/PaymentDetailsHeadingIcon[0]/SvgIcon[0]", + "childIndex" : 1, + "depth" : 11, + "layer" : 11, + "computedX" : 31.88, + "computedY" : 206.135, + "placementX" : 31.88, + "placementY" : 206.135, + "placementWidth" : 14.67, + "placementHeight" : 14.67, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 14.67, + "contentHeight" : 14.67, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/ClosingRow[5]/PaymentColumn[0]/PaymentDetails[0]/PaymentDetailsHeadingLayer[0]/PaymentDetailsHeadingHolder[0]/PaymentDetailsHeading[0]/PaymentDetailsHeadingLabel[1]", + "entityName" : "PaymentDetailsHeadingLabel", + "entityKind" : "ParagraphNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/ClosingRow[5]/PaymentColumn[0]/PaymentDetails[0]/PaymentDetailsHeadingLayer[0]/PaymentDetailsHeadingHolder[0]/PaymentDetailsHeading[0]", + "childIndex" : 1, + "depth" : 9, + "layer" : 9, + "computedX" : 49.371, + "computedY" : 208.221, + "placementX" : 49.371, + "placementY" : 208.221, + "placementWidth" : 79.564, + "placementHeight" : 10.5, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 79.564, + "contentHeight" : 10.5, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/ClosingRow[5]/PaymentColumn[0]/PaymentDetails[0]/PaymentDetailsRows[1]", + "entityName" : "PaymentDetailsRows", + "entityKind" : "SectionNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/ClosingRow[5]/PaymentColumn[0]/PaymentDetails[0]", + "childIndex" : 1, + "depth" : 6, + "layer" : 6, + "computedX" : 31.88, + "computedY" : 117.275, + "placementX" : 31.88, + "placementY" : 117.275, + "placementWidth" : 219.49, + "placementHeight" : 83.952, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 219.49, + "contentHeight" : 83.952, + "margin" : { + "top" : 2.257, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/ClosingRow[5]/PaymentColumn[0]/PaymentDetails[0]/PaymentDetailsRows[1]/PaymentDetail_0Layer[0]", + "entityName" : "PaymentDetail_0Layer", + "entityKind" : "LayerStackNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/ClosingRow[5]/PaymentColumn[0]/PaymentDetails[0]/PaymentDetailsRows[1]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 31.88, + "computedY" : 191.506, + "placementX" : 31.88, + "placementY" : 191.506, + "placementWidth" : 219.49, + "placementHeight" : 9.72, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 219.49, + "contentHeight" : 9.72, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/ClosingRow[5]/PaymentColumn[0]/PaymentDetails[0]/PaymentDetailsRows[1]/PaymentDetail_0Layer[0]/PaymentDetail_0Holder[0]", + "entityName" : "PaymentDetail_0Holder", + "entityKind" : "SectionNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/ClosingRow[5]/PaymentColumn[0]/PaymentDetails[0]/PaymentDetailsRows[1]/PaymentDetail_0Layer[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 31.88, + "computedY" : 191.506, + "placementX" : 31.88, + "placementY" : 191.506, + "placementWidth" : 219.49, + "placementHeight" : 9.72, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 219.49, + "contentHeight" : 9.72, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/ClosingRow[5]/PaymentColumn[0]/PaymentDetails[0]/PaymentDetailsRows[1]/PaymentDetail_0Layer[0]/PaymentDetail_0Holder[0]/PaymentDetail_0[0]", + "entityName" : "PaymentDetail_0", + "entityKind" : "RowNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/ClosingRow[5]/PaymentColumn[0]/PaymentDetails[0]/PaymentDetailsRows[1]/PaymentDetail_0Layer[0]/PaymentDetail_0Holder[0]", + "childIndex" : 0, + "depth" : 9, + "layer" : 9, + "computedX" : 31.88, + "computedY" : 191.506, + "placementX" : 31.88, + "placementY" : 191.506, + "placementWidth" : 219.49, + "placementHeight" : 9.72, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 219.49, + "contentHeight" : 9.72, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/ClosingRow[5]/PaymentColumn[0]/PaymentDetails[0]/PaymentDetailsRows[1]/PaymentDetail_0Layer[0]/PaymentDetail_0Holder[0]/PaymentDetail_0[0]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/ClosingRow[5]/PaymentColumn[0]/PaymentDetails[0]/PaymentDetailsRows[1]/PaymentDetail_0Layer[0]/PaymentDetail_0Holder[0]/PaymentDetail_0[0]", + "childIndex" : 0, + "depth" : 10, + "layer" : 10, + "computedX" : 31.88, + "computedY" : 191.506, + "placementX" : 31.88, + "placementY" : 191.506, + "placementWidth" : 55.023, + "placementHeight" : 9.72, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 55.023, + "contentHeight" : 9.72, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/ClosingRow[5]/PaymentColumn[0]/PaymentDetails[0]/PaymentDetailsRows[1]/PaymentDetail_0Layer[0]/PaymentDetail_0Holder[0]/PaymentDetail_0[0]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/ClosingRow[5]/PaymentColumn[0]/PaymentDetails[0]/PaymentDetailsRows[1]/PaymentDetail_0Layer[0]/PaymentDetail_0Holder[0]/PaymentDetail_0[0]", + "childIndex" : 1, + "depth" : 10, + "layer" : 10, + "computedX" : 128.929, + "computedY" : 191.506, + "placementX" : 128.929, + "placementY" : 191.506, + "placementWidth" : 108.637, + "placementHeight" : 9.72, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 108.637, + "contentHeight" : 9.72, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/ClosingRow[5]/PaymentColumn[0]/PaymentDetails[0]/PaymentDetailsRows[1]/PaymentDetail_1Layer[1]", + "entityName" : "PaymentDetail_1Layer", + "entityKind" : "LayerStackNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/ClosingRow[5]/PaymentColumn[0]/PaymentDetails[0]/PaymentDetailsRows[1]", + "childIndex" : 1, + "depth" : 7, + "layer" : 7, + "computedX" : 31.88, + "computedY" : 179.135, + "placementX" : 31.88, + "placementY" : 179.135, + "placementWidth" : 219.49, + "placementHeight" : 9.72, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 219.49, + "contentHeight" : 9.72, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/ClosingRow[5]/PaymentColumn[0]/PaymentDetails[0]/PaymentDetailsRows[1]/PaymentDetail_1Layer[1]/PaymentDetail_1Holder[0]", + "entityName" : "PaymentDetail_1Holder", + "entityKind" : "SectionNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/ClosingRow[5]/PaymentColumn[0]/PaymentDetails[0]/PaymentDetailsRows[1]/PaymentDetail_1Layer[1]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 31.88, + "computedY" : 179.135, + "placementX" : 31.88, + "placementY" : 179.135, + "placementWidth" : 219.49, + "placementHeight" : 9.72, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 219.49, + "contentHeight" : 9.72, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/ClosingRow[5]/PaymentColumn[0]/PaymentDetails[0]/PaymentDetailsRows[1]/PaymentDetail_1Layer[1]/PaymentDetail_1Holder[0]/PaymentDetail_1[0]", + "entityName" : "PaymentDetail_1", + "entityKind" : "RowNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/ClosingRow[5]/PaymentColumn[0]/PaymentDetails[0]/PaymentDetailsRows[1]/PaymentDetail_1Layer[1]/PaymentDetail_1Holder[0]", + "childIndex" : 0, + "depth" : 9, + "layer" : 9, + "computedX" : 31.88, + "computedY" : 179.135, + "placementX" : 31.88, + "placementY" : 179.135, + "placementWidth" : 219.49, + "placementHeight" : 9.72, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 219.49, + "contentHeight" : 9.72, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/ClosingRow[5]/PaymentColumn[0]/PaymentDetails[0]/PaymentDetailsRows[1]/PaymentDetail_1Layer[1]/PaymentDetail_1Holder[0]/PaymentDetail_1[0]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/ClosingRow[5]/PaymentColumn[0]/PaymentDetails[0]/PaymentDetailsRows[1]/PaymentDetail_1Layer[1]/PaymentDetail_1Holder[0]/PaymentDetail_1[0]", + "childIndex" : 0, + "depth" : 10, + "layer" : 10, + "computedX" : 31.88, + "computedY" : 179.135, + "placementX" : 31.88, + "placementY" : 179.135, + "placementWidth" : 43.157, + "placementHeight" : 9.72, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 43.157, + "contentHeight" : 9.72, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/ClosingRow[5]/PaymentColumn[0]/PaymentDetails[0]/PaymentDetailsRows[1]/PaymentDetail_1Layer[1]/PaymentDetail_1Holder[0]/PaymentDetail_1[0]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/ClosingRow[5]/PaymentColumn[0]/PaymentDetails[0]/PaymentDetailsRows[1]/PaymentDetail_1Layer[1]/PaymentDetail_1Holder[0]/PaymentDetail_1[0]", + "childIndex" : 1, + "depth" : 10, + "layer" : 10, + "computedX" : 128.929, + "computedY" : 179.135, + "placementX" : 128.929, + "placementY" : 179.135, + "placementWidth" : 48.025, + "placementHeight" : 9.72, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 48.025, + "contentHeight" : 9.72, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/ClosingRow[5]/PaymentColumn[0]/PaymentDetails[0]/PaymentDetailsRows[1]/PaymentDetail_2Layer[2]", + "entityName" : "PaymentDetail_2Layer", + "entityKind" : "LayerStackNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/ClosingRow[5]/PaymentColumn[0]/PaymentDetails[0]/PaymentDetailsRows[1]", + "childIndex" : 2, + "depth" : 7, + "layer" : 7, + "computedX" : 31.88, + "computedY" : 166.763, + "placementX" : 31.88, + "placementY" : 166.763, + "placementWidth" : 219.49, + "placementHeight" : 9.72, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 219.49, + "contentHeight" : 9.72, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/ClosingRow[5]/PaymentColumn[0]/PaymentDetails[0]/PaymentDetailsRows[1]/PaymentDetail_2Layer[2]/PaymentDetail_2Holder[0]", + "entityName" : "PaymentDetail_2Holder", + "entityKind" : "SectionNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/ClosingRow[5]/PaymentColumn[0]/PaymentDetails[0]/PaymentDetailsRows[1]/PaymentDetail_2Layer[2]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 31.88, + "computedY" : 166.763, + "placementX" : 31.88, + "placementY" : 166.763, + "placementWidth" : 219.49, + "placementHeight" : 9.72, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 219.49, + "contentHeight" : 9.72, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/ClosingRow[5]/PaymentColumn[0]/PaymentDetails[0]/PaymentDetailsRows[1]/PaymentDetail_2Layer[2]/PaymentDetail_2Holder[0]/PaymentDetail_2[0]", + "entityName" : "PaymentDetail_2", + "entityKind" : "RowNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/ClosingRow[5]/PaymentColumn[0]/PaymentDetails[0]/PaymentDetailsRows[1]/PaymentDetail_2Layer[2]/PaymentDetail_2Holder[0]", + "childIndex" : 0, + "depth" : 9, + "layer" : 9, + "computedX" : 31.88, + "computedY" : 166.763, + "placementX" : 31.88, + "placementY" : 166.763, + "placementWidth" : 219.49, + "placementHeight" : 9.72, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 219.49, + "contentHeight" : 9.72, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/ClosingRow[5]/PaymentColumn[0]/PaymentDetails[0]/PaymentDetailsRows[1]/PaymentDetail_2Layer[2]/PaymentDetail_2Holder[0]/PaymentDetail_2[0]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/ClosingRow[5]/PaymentColumn[0]/PaymentDetails[0]/PaymentDetailsRows[1]/PaymentDetail_2Layer[2]/PaymentDetail_2Holder[0]/PaymentDetail_2[0]", + "childIndex" : 0, + "depth" : 10, + "layer" : 10, + "computedX" : 31.88, + "computedY" : 166.763, + "placementX" : 31.88, + "placementY" : 166.763, + "placementWidth" : 63.002, + "placementHeight" : 9.72, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 63.002, + "contentHeight" : 9.72, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/ClosingRow[5]/PaymentColumn[0]/PaymentDetails[0]/PaymentDetailsRows[1]/PaymentDetail_2Layer[2]/PaymentDetail_2Holder[0]/PaymentDetail_2[0]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/ClosingRow[5]/PaymentColumn[0]/PaymentDetails[0]/PaymentDetailsRows[1]/PaymentDetail_2Layer[2]/PaymentDetail_2Holder[0]/PaymentDetail_2[0]", + "childIndex" : 1, + "depth" : 10, + "layer" : 10, + "computedX" : 128.929, + "computedY" : 166.763, + "placementX" : 128.929, + "placementY" : 166.763, + "placementWidth" : 37.584, + "placementHeight" : 9.72, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 37.584, + "contentHeight" : 9.72, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/ClosingRow[5]/PaymentColumn[0]/PaymentDetails[0]/PaymentDetailsRows[1]/PaymentDetail_3Layer[3]", + "entityName" : "PaymentDetail_3Layer", + "entityKind" : "LayerStackNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/ClosingRow[5]/PaymentColumn[0]/PaymentDetails[0]/PaymentDetailsRows[1]", + "childIndex" : 3, + "depth" : 7, + "layer" : 7, + "computedX" : 31.88, + "computedY" : 154.391, + "placementX" : 31.88, + "placementY" : 154.391, + "placementWidth" : 219.49, + "placementHeight" : 9.72, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 219.49, + "contentHeight" : 9.72, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/ClosingRow[5]/PaymentColumn[0]/PaymentDetails[0]/PaymentDetailsRows[1]/PaymentDetail_3Layer[3]/PaymentDetail_3Holder[0]", + "entityName" : "PaymentDetail_3Holder", + "entityKind" : "SectionNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/ClosingRow[5]/PaymentColumn[0]/PaymentDetails[0]/PaymentDetailsRows[1]/PaymentDetail_3Layer[3]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 31.88, + "computedY" : 154.391, + "placementX" : 31.88, + "placementY" : 154.391, + "placementWidth" : 219.49, + "placementHeight" : 9.72, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 219.49, + "contentHeight" : 9.72, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/ClosingRow[5]/PaymentColumn[0]/PaymentDetails[0]/PaymentDetailsRows[1]/PaymentDetail_3Layer[3]/PaymentDetail_3Holder[0]/PaymentDetail_3[0]", + "entityName" : "PaymentDetail_3", + "entityKind" : "RowNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/ClosingRow[5]/PaymentColumn[0]/PaymentDetails[0]/PaymentDetailsRows[1]/PaymentDetail_3Layer[3]/PaymentDetail_3Holder[0]", + "childIndex" : 0, + "depth" : 9, + "layer" : 9, + "computedX" : 31.88, + "computedY" : 154.391, + "placementX" : 31.88, + "placementY" : 154.391, + "placementWidth" : 219.49, + "placementHeight" : 9.72, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 219.49, + "contentHeight" : 9.72, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/ClosingRow[5]/PaymentColumn[0]/PaymentDetails[0]/PaymentDetailsRows[1]/PaymentDetail_3Layer[3]/PaymentDetail_3Holder[0]/PaymentDetail_3[0]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/ClosingRow[5]/PaymentColumn[0]/PaymentDetails[0]/PaymentDetailsRows[1]/PaymentDetail_3Layer[3]/PaymentDetail_3Holder[0]/PaymentDetail_3[0]", + "childIndex" : 0, + "depth" : 10, + "layer" : 10, + "computedX" : 31.88, + "computedY" : 154.391, + "placementX" : 31.88, + "placementY" : 154.391, + "placementWidth" : 37.762, + "placementHeight" : 9.72, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 37.762, + "contentHeight" : 9.72, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/ClosingRow[5]/PaymentColumn[0]/PaymentDetails[0]/PaymentDetailsRows[1]/PaymentDetail_3Layer[3]/PaymentDetail_3Holder[0]/PaymentDetail_3[0]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/ClosingRow[5]/PaymentColumn[0]/PaymentDetails[0]/PaymentDetailsRows[1]/PaymentDetail_3Layer[3]/PaymentDetail_3Holder[0]/PaymentDetail_3[0]", + "childIndex" : 1, + "depth" : 10, + "layer" : 10, + "computedX" : 128.929, + "computedY" : 154.391, + "placementX" : 128.929, + "placementY" : 154.391, + "placementWidth" : 34.214, + "placementHeight" : 9.72, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 34.214, + "contentHeight" : 9.72, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/ClosingRow[5]/PaymentColumn[0]/PaymentDetails[0]/PaymentDetailsRows[1]/PaymentDetail_4Layer[4]", + "entityName" : "PaymentDetail_4Layer", + "entityKind" : "LayerStackNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/ClosingRow[5]/PaymentColumn[0]/PaymentDetails[0]/PaymentDetailsRows[1]", + "childIndex" : 4, + "depth" : 7, + "layer" : 7, + "computedX" : 31.88, + "computedY" : 142.019, + "placementX" : 31.88, + "placementY" : 142.019, + "placementWidth" : 219.49, + "placementHeight" : 9.72, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 219.49, + "contentHeight" : 9.72, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/ClosingRow[5]/PaymentColumn[0]/PaymentDetails[0]/PaymentDetailsRows[1]/PaymentDetail_4Layer[4]/PaymentDetail_4Holder[0]", + "entityName" : "PaymentDetail_4Holder", + "entityKind" : "SectionNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/ClosingRow[5]/PaymentColumn[0]/PaymentDetails[0]/PaymentDetailsRows[1]/PaymentDetail_4Layer[4]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 31.88, + "computedY" : 142.019, + "placementX" : 31.88, + "placementY" : 142.019, + "placementWidth" : 219.49, + "placementHeight" : 9.72, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 219.49, + "contentHeight" : 9.72, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/ClosingRow[5]/PaymentColumn[0]/PaymentDetails[0]/PaymentDetailsRows[1]/PaymentDetail_4Layer[4]/PaymentDetail_4Holder[0]/PaymentDetail_4[0]", + "entityName" : "PaymentDetail_4", + "entityKind" : "RowNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/ClosingRow[5]/PaymentColumn[0]/PaymentDetails[0]/PaymentDetailsRows[1]/PaymentDetail_4Layer[4]/PaymentDetail_4Holder[0]", + "childIndex" : 0, + "depth" : 9, + "layer" : 9, + "computedX" : 31.88, + "computedY" : 142.019, + "placementX" : 31.88, + "placementY" : 142.019, + "placementWidth" : 219.49, + "placementHeight" : 9.72, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 219.49, + "contentHeight" : 9.72, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/ClosingRow[5]/PaymentColumn[0]/PaymentDetails[0]/PaymentDetailsRows[1]/PaymentDetail_4Layer[4]/PaymentDetail_4Holder[0]/PaymentDetail_4[0]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/ClosingRow[5]/PaymentColumn[0]/PaymentDetails[0]/PaymentDetailsRows[1]/PaymentDetail_4Layer[4]/PaymentDetail_4Holder[0]/PaymentDetail_4[0]", + "childIndex" : 0, + "depth" : 10, + "layer" : 10, + "computedX" : 31.88, + "computedY" : 142.019, + "placementX" : 31.88, + "placementY" : 142.019, + "placementWidth" : 21.206, + "placementHeight" : 9.72, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 21.206, + "contentHeight" : 9.72, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/ClosingRow[5]/PaymentColumn[0]/PaymentDetails[0]/PaymentDetailsRows[1]/PaymentDetail_4Layer[4]/PaymentDetail_4Holder[0]/PaymentDetail_4[0]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/ClosingRow[5]/PaymentColumn[0]/PaymentDetails[0]/PaymentDetailsRows[1]/PaymentDetail_4Layer[4]/PaymentDetail_4Holder[0]/PaymentDetail_4[0]", + "childIndex" : 1, + "depth" : 10, + "layer" : 10, + "computedX" : 128.929, + "computedY" : 142.019, + "placementX" : 128.929, + "placementY" : 142.019, + "placementWidth" : 114.769, + "placementHeight" : 9.72, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 114.769, + "contentHeight" : 9.72, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/ClosingRow[5]/PaymentColumn[0]/PaymentDetails[0]/PaymentDetailsRows[1]/PaymentDetail_5Layer[5]", + "entityName" : "PaymentDetail_5Layer", + "entityKind" : "LayerStackNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/ClosingRow[5]/PaymentColumn[0]/PaymentDetails[0]/PaymentDetailsRows[1]", + "childIndex" : 5, + "depth" : 7, + "layer" : 7, + "computedX" : 31.88, + "computedY" : 129.647, + "placementX" : 31.88, + "placementY" : 129.647, + "placementWidth" : 219.49, + "placementHeight" : 9.72, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 219.49, + "contentHeight" : 9.72, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/ClosingRow[5]/PaymentColumn[0]/PaymentDetails[0]/PaymentDetailsRows[1]/PaymentDetail_5Layer[5]/PaymentDetail_5Holder[0]", + "entityName" : "PaymentDetail_5Holder", + "entityKind" : "SectionNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/ClosingRow[5]/PaymentColumn[0]/PaymentDetails[0]/PaymentDetailsRows[1]/PaymentDetail_5Layer[5]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 31.88, + "computedY" : 129.647, + "placementX" : 31.88, + "placementY" : 129.647, + "placementWidth" : 219.49, + "placementHeight" : 9.72, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 219.49, + "contentHeight" : 9.72, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/ClosingRow[5]/PaymentColumn[0]/PaymentDetails[0]/PaymentDetailsRows[1]/PaymentDetail_5Layer[5]/PaymentDetail_5Holder[0]/PaymentDetail_5[0]", + "entityName" : "PaymentDetail_5", + "entityKind" : "RowNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/ClosingRow[5]/PaymentColumn[0]/PaymentDetails[0]/PaymentDetailsRows[1]/PaymentDetail_5Layer[5]/PaymentDetail_5Holder[0]", + "childIndex" : 0, + "depth" : 9, + "layer" : 9, + "computedX" : 31.88, + "computedY" : 129.647, + "placementX" : 31.88, + "placementY" : 129.647, + "placementWidth" : 219.49, + "placementHeight" : 9.72, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 219.49, + "contentHeight" : 9.72, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/ClosingRow[5]/PaymentColumn[0]/PaymentDetails[0]/PaymentDetailsRows[1]/PaymentDetail_5Layer[5]/PaymentDetail_5Holder[0]/PaymentDetail_5[0]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/ClosingRow[5]/PaymentColumn[0]/PaymentDetails[0]/PaymentDetailsRows[1]/PaymentDetail_5Layer[5]/PaymentDetail_5Holder[0]/PaymentDetail_5[0]", + "childIndex" : 0, + "depth" : 10, + "layer" : 10, + "computedX" : 31.88, + "computedY" : 129.647, + "placementX" : 31.88, + "placementY" : 129.647, + "placementWidth" : 51.654, + "placementHeight" : 9.72, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 51.654, + "contentHeight" : 9.72, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/ClosingRow[5]/PaymentColumn[0]/PaymentDetails[0]/PaymentDetailsRows[1]/PaymentDetail_5Layer[5]/PaymentDetail_5Holder[0]/PaymentDetail_5[0]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/ClosingRow[5]/PaymentColumn[0]/PaymentDetails[0]/PaymentDetailsRows[1]/PaymentDetail_5Layer[5]/PaymentDetail_5Holder[0]/PaymentDetail_5[0]", + "childIndex" : 1, + "depth" : 10, + "layer" : 10, + "computedX" : 128.929, + "computedY" : 129.647, + "placementX" : 128.929, + "placementY" : 129.647, + "placementWidth" : 60.629, + "placementHeight" : 9.72, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 60.629, + "contentHeight" : 9.72, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/ClosingRow[5]/PaymentColumn[0]/PaymentDetails[0]/PaymentDetailsRows[1]/PaymentDetail_6Layer[6]", + "entityName" : "PaymentDetail_6Layer", + "entityKind" : "LayerStackNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/ClosingRow[5]/PaymentColumn[0]/PaymentDetails[0]/PaymentDetailsRows[1]", + "childIndex" : 6, + "depth" : 7, + "layer" : 7, + "computedX" : 31.88, + "computedY" : 117.275, + "placementX" : 31.88, + "placementY" : 117.275, + "placementWidth" : 219.49, + "placementHeight" : 9.72, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 219.49, + "contentHeight" : 9.72, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/ClosingRow[5]/PaymentColumn[0]/PaymentDetails[0]/PaymentDetailsRows[1]/PaymentDetail_6Layer[6]/PaymentDetail_6Holder[0]", + "entityName" : "PaymentDetail_6Holder", + "entityKind" : "SectionNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/ClosingRow[5]/PaymentColumn[0]/PaymentDetails[0]/PaymentDetailsRows[1]/PaymentDetail_6Layer[6]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 31.88, + "computedY" : 117.275, + "placementX" : 31.88, + "placementY" : 117.275, + "placementWidth" : 219.49, + "placementHeight" : 9.72, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 219.49, + "contentHeight" : 9.72, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/ClosingRow[5]/PaymentColumn[0]/PaymentDetails[0]/PaymentDetailsRows[1]/PaymentDetail_6Layer[6]/PaymentDetail_6Holder[0]/PaymentDetail_6[0]", + "entityName" : "PaymentDetail_6", + "entityKind" : "RowNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/ClosingRow[5]/PaymentColumn[0]/PaymentDetails[0]/PaymentDetailsRows[1]/PaymentDetail_6Layer[6]/PaymentDetail_6Holder[0]", + "childIndex" : 0, + "depth" : 9, + "layer" : 9, + "computedX" : 31.88, + "computedY" : 117.275, + "placementX" : 31.88, + "placementY" : 117.275, + "placementWidth" : 219.49, + "placementHeight" : 9.72, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 219.49, + "contentHeight" : 9.72, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/ClosingRow[5]/PaymentColumn[0]/PaymentDetails[0]/PaymentDetailsRows[1]/PaymentDetail_6Layer[6]/PaymentDetail_6Holder[0]/PaymentDetail_6[0]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/ClosingRow[5]/PaymentColumn[0]/PaymentDetails[0]/PaymentDetailsRows[1]/PaymentDetail_6Layer[6]/PaymentDetail_6Holder[0]/PaymentDetail_6[0]", + "childIndex" : 0, + "depth" : 10, + "layer" : 10, + "computedX" : 31.88, + "computedY" : 117.275, + "placementX" : 31.88, + "placementY" : 117.275, + "placementWidth" : 63.804, + "placementHeight" : 9.72, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 63.804, + "contentHeight" : 9.72, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/ClosingRow[5]/PaymentColumn[0]/PaymentDetails[0]/PaymentDetailsRows[1]/PaymentDetail_6Layer[6]/PaymentDetail_6Holder[0]/PaymentDetail_6[0]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/ClosingRow[5]/PaymentColumn[0]/PaymentDetails[0]/PaymentDetailsRows[1]/PaymentDetail_6Layer[6]/PaymentDetail_6Holder[0]/PaymentDetail_6[0]", + "childIndex" : 1, + "depth" : 10, + "layer" : 10, + "computedX" : 128.929, + "computedY" : 117.275, + "placementX" : 128.929, + "placementY" : 117.275, + "placementWidth" : 96.26, + "placementHeight" : 9.72, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 96.26, + "contentHeight" : 9.72, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/ClosingRow[5]/SummaryColumn[1]", + "entityName" : "SummaryColumn", + "entityKind" : "SectionNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/ClosingRow[5]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 326.978, + "computedY" : 105.426, + "placementX" : 326.978, + "placementY" : 105.426, + "placementWidth" : 238.11, + "placementHeight" : 130.482, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 238.11, + "contentHeight" : 130.482, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/ClosingRow[5]/SummaryColumn[1]/Totals[0]", + "entityName" : "Totals", + "entityKind" : "SectionNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/ClosingRow[5]/SummaryColumn[1]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 326.978, + "computedY" : 167.638, + "placementX" : 326.978, + "placementY" : 167.638, + "placementWidth" : 238.11, + "placementHeight" : 68.269, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 238.11, + "contentHeight" : 68.269, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 4.232, + "bottom" : 0.0, + "left" : 3.385 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/ClosingRow[5]/SummaryColumn[1]/Totals[0]/TotalsRow_0Layer[0]", + "entityName" : "TotalsRow_0Layer", + "entityKind" : "LayerStackNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/ClosingRow[5]/SummaryColumn[1]/Totals[0]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 330.364, + "computedY" : 225.588, + "placementX" : 330.364, + "placementY" : 225.588, + "placementWidth" : 230.493, + "placementHeight" : 10.32, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 230.493, + "contentHeight" : 10.32, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/ClosingRow[5]/SummaryColumn[1]/Totals[0]/TotalsRow_0Layer[0]/TotalsRow_0Holder[0]", + "entityName" : "TotalsRow_0Holder", + "entityKind" : "SectionNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/ClosingRow[5]/SummaryColumn[1]/Totals[0]/TotalsRow_0Layer[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 330.364, + "computedY" : 225.588, + "placementX" : 330.364, + "placementY" : 225.588, + "placementWidth" : 230.493, + "placementHeight" : 10.32, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 230.493, + "contentHeight" : 10.32, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/ClosingRow[5]/SummaryColumn[1]/Totals[0]/TotalsRow_0Layer[0]/TotalsRow_0Holder[0]/TotalsRow_0[0]", + "entityName" : "TotalsRow_0", + "entityKind" : "RowNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/ClosingRow[5]/SummaryColumn[1]/Totals[0]/TotalsRow_0Layer[0]/TotalsRow_0Holder[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 330.364, + "computedY" : 225.588, + "placementX" : 330.364, + "placementY" : 225.588, + "placementWidth" : 230.493, + "placementHeight" : 10.32, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 230.493, + "contentHeight" : 10.32, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/ClosingRow[5]/SummaryColumn[1]/Totals[0]/TotalsRow_0Layer[0]/TotalsRow_0Holder[0]/TotalsRow_0[0]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/ClosingRow[5]/SummaryColumn[1]/Totals[0]/TotalsRow_0Layer[0]/TotalsRow_0Holder[0]/TotalsRow_0[0]", + "childIndex" : 0, + "depth" : 9, + "layer" : 9, + "computedX" : 330.364, + "computedY" : 225.588, + "placementX" : 330.364, + "placementY" : 225.588, + "placementWidth" : 31.639, + "placementHeight" : 10.32, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 31.639, + "contentHeight" : 10.32, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/ClosingRow[5]/SummaryColumn[1]/Totals[0]/TotalsRow_0Layer[0]/TotalsRow_0Holder[0]/TotalsRow_0[0]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/ClosingRow[5]/SummaryColumn[1]/Totals[0]/TotalsRow_0Layer[0]/TotalsRow_0Holder[0]/TotalsRow_0[0]", + "childIndex" : 1, + "depth" : 9, + "layer" : 9, + "computedX" : 445.61, + "computedY" : 225.588, + "placementX" : 445.61, + "placementY" : 225.588, + "placementWidth" : 115.246, + "placementHeight" : 10.32, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 115.246, + "contentHeight" : 10.32, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/ClosingRow[5]/SummaryColumn[1]/Totals[0]/TotalsRow_1Layer[1]", + "entityName" : "TotalsRow_1Layer", + "entityKind" : "LayerStackNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/ClosingRow[5]/SummaryColumn[1]/Totals[0]", + "childIndex" : 1, + "depth" : 6, + "layer" : 6, + "computedX" : 330.364, + "computedY" : 206.804, + "placementX" : 330.364, + "placementY" : 206.804, + "placementWidth" : 230.493, + "placementHeight" : 10.32, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 230.493, + "contentHeight" : 10.32, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/ClosingRow[5]/SummaryColumn[1]/Totals[0]/TotalsRow_1Layer[1]/TotalsRow_1Holder[0]", + "entityName" : "TotalsRow_1Holder", + "entityKind" : "SectionNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/ClosingRow[5]/SummaryColumn[1]/Totals[0]/TotalsRow_1Layer[1]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 330.364, + "computedY" : 206.804, + "placementX" : 330.364, + "placementY" : 206.804, + "placementWidth" : 230.493, + "placementHeight" : 10.32, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 230.493, + "contentHeight" : 10.32, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/ClosingRow[5]/SummaryColumn[1]/Totals[0]/TotalsRow_1Layer[1]/TotalsRow_1Holder[0]/TotalsRow_1[0]", + "entityName" : "TotalsRow_1", + "entityKind" : "RowNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/ClosingRow[5]/SummaryColumn[1]/Totals[0]/TotalsRow_1Layer[1]/TotalsRow_1Holder[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 330.364, + "computedY" : 206.804, + "placementX" : 330.364, + "placementY" : 206.804, + "placementWidth" : 230.493, + "placementHeight" : 10.32, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 230.493, + "contentHeight" : 10.32, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/ClosingRow[5]/SummaryColumn[1]/Totals[0]/TotalsRow_1Layer[1]/TotalsRow_1Holder[0]/TotalsRow_1[0]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/ClosingRow[5]/SummaryColumn[1]/Totals[0]/TotalsRow_1Layer[1]/TotalsRow_1Holder[0]/TotalsRow_1[0]", + "childIndex" : 0, + "depth" : 9, + "layer" : 9, + "computedX" : 330.364, + "computedY" : 206.804, + "placementX" : 330.364, + "placementY" : 206.804, + "placementWidth" : 40.394, + "placementHeight" : 10.32, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 40.394, + "contentHeight" : 10.32, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/ClosingRow[5]/SummaryColumn[1]/Totals[0]/TotalsRow_1Layer[1]/TotalsRow_1Holder[0]/TotalsRow_1[0]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/ClosingRow[5]/SummaryColumn[1]/Totals[0]/TotalsRow_1Layer[1]/TotalsRow_1Holder[0]/TotalsRow_1[0]", + "childIndex" : 1, + "depth" : 9, + "layer" : 9, + "computedX" : 445.61, + "computedY" : 206.804, + "placementX" : 445.61, + "placementY" : 206.804, + "placementWidth" : 115.246, + "placementHeight" : 10.32, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 115.246, + "contentHeight" : 10.32, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/ClosingRow[5]/SummaryColumn[1]/Totals[0]/TotalsRule[2]", + "entityName" : "TotalsRule", + "entityKind" : "LineNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/ClosingRow[5]/SummaryColumn[1]/Totals[0]", + "childIndex" : 2, + "depth" : 6, + "layer" : 6, + "computedX" : 330.364, + "computedY" : 195.237, + "placementX" : 330.364, + "placementY" : 195.237, + "placementWidth" : 230.493, + "placementHeight" : 1.128, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 230.493, + "contentHeight" : 1.128, + "margin" : { + "top" : 1.975, + "right" : 0.0, + "bottom" : 1.975, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/ClosingRow[5]/SummaryColumn[1]/Totals[0]/TotalDueLayer[3]", + "entityName" : "TotalDueLayer", + "entityKind" : "LayerStackNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/ClosingRow[5]/SummaryColumn[1]/Totals[0]", + "childIndex" : 3, + "depth" : 6, + "layer" : 6, + "computedX" : 330.364, + "computedY" : 167.638, + "placementX" : 330.364, + "placementY" : 167.638, + "placementWidth" : 230.493, + "placementHeight" : 17.16, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 230.493, + "contentHeight" : 17.16, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/ClosingRow[5]/SummaryColumn[1]/Totals[0]/TotalDueLayer[3]/TotalDueHolder[0]", + "entityName" : "TotalDueHolder", + "entityKind" : "SectionNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/ClosingRow[5]/SummaryColumn[1]/Totals[0]/TotalDueLayer[3]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 330.364, + "computedY" : 167.638, + "placementX" : 330.364, + "placementY" : 167.638, + "placementWidth" : 230.493, + "placementHeight" : 17.16, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 230.493, + "contentHeight" : 17.16, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/ClosingRow[5]/SummaryColumn[1]/Totals[0]/TotalDueLayer[3]/TotalDueHolder[0]/TotalDue[0]", + "entityName" : "TotalDue", + "entityKind" : "RowNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/ClosingRow[5]/SummaryColumn[1]/Totals[0]/TotalDueLayer[3]/TotalDueHolder[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 330.364, + "computedY" : 167.638, + "placementX" : 330.364, + "placementY" : 167.638, + "placementWidth" : 230.493, + "placementHeight" : 17.16, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 230.493, + "contentHeight" : 17.16, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/ClosingRow[5]/SummaryColumn[1]/Totals[0]/TotalDueLayer[3]/TotalDueHolder[0]/TotalDue[0]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/ClosingRow[5]/SummaryColumn[1]/Totals[0]/TotalDueLayer[3]/TotalDueHolder[0]/TotalDue[0]", + "childIndex" : 0, + "depth" : 9, + "layer" : 9, + "computedX" : 330.364, + "computedY" : 169.618, + "placementX" : 330.364, + "placementY" : 169.618, + "placementWidth" : 60.995, + "placementHeight" : 13.2, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 60.995, + "contentHeight" : 13.2, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/ClosingRow[5]/SummaryColumn[1]/Totals[0]/TotalDueLayer[3]/TotalDueHolder[0]/TotalDue[0]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/ClosingRow[5]/SummaryColumn[1]/Totals[0]/TotalDueLayer[3]/TotalDueHolder[0]/TotalDue[0]", + "childIndex" : 1, + "depth" : 9, + "layer" : 9, + "computedX" : 445.61, + "computedY" : 167.638, + "placementX" : 445.61, + "placementY" : 167.638, + "placementWidth" : 115.246, + "placementHeight" : 17.16, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 115.246, + "contentHeight" : 17.16, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/ClosingRow[5]/SummaryColumn[1]/PaymentDueBy[1]", + "entityName" : "PaymentDueBy", + "entityKind" : "SectionNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/ClosingRow[5]/SummaryColumn[1]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 326.978, + "computedY" : 105.426, + "placementX" : 326.978, + "placementY" : 105.426, + "placementWidth" : 238.11, + "placementHeight" : 45.85, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 238.11, + "contentHeight" : 45.85, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 11.285, + "right" : 11.285, + "bottom" : 11.285, + "left" : 11.285 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/ClosingRow[5]/SummaryColumn[1]/PaymentDueBy[1]/PaymentDueByRowLayer[0]", + "entityName" : "PaymentDueByRowLayer", + "entityKind" : "LayerStackNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/ClosingRow[5]/SummaryColumn[1]/PaymentDueBy[1]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 338.263, + "computedY" : 116.711, + "placementX" : 338.263, + "placementY" : 116.711, + "placementWidth" : 215.541, + "placementHeight" : 23.28, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 215.541, + "contentHeight" : 23.28, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/ClosingRow[5]/SummaryColumn[1]/PaymentDueBy[1]/PaymentDueByRowLayer[0]/PaymentDueByRowHolder[0]", + "entityName" : "PaymentDueByRowHolder", + "entityKind" : "SectionNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/ClosingRow[5]/SummaryColumn[1]/PaymentDueBy[1]/PaymentDueByRowLayer[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 338.263, + "computedY" : 116.711, + "placementX" : 338.263, + "placementY" : 116.711, + "placementWidth" : 215.541, + "placementHeight" : 23.28, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 215.541, + "contentHeight" : 23.28, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/ClosingRow[5]/SummaryColumn[1]/PaymentDueBy[1]/PaymentDueByRowLayer[0]/PaymentDueByRowHolder[0]/PaymentDueByRow[0]", + "entityName" : "PaymentDueByRow", + "entityKind" : "RowNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/ClosingRow[5]/SummaryColumn[1]/PaymentDueBy[1]/PaymentDueByRowLayer[0]/PaymentDueByRowHolder[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 338.263, + "computedY" : 116.711, + "placementX" : 338.263, + "placementY" : 116.711, + "placementWidth" : 215.541, + "placementHeight" : 23.28, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 215.541, + "contentHeight" : 23.28, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/ClosingRow[5]/SummaryColumn[1]/PaymentDueBy[1]/PaymentDueByRowLayer[0]/PaymentDueByRowHolder[0]/PaymentDueByRow[0]/PaymentDueByIcon[0]", + "entityName" : "PaymentDueByIcon", + "entityKind" : "SectionNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/ClosingRow[5]/SummaryColumn[1]/PaymentDueBy[1]/PaymentDueByRowLayer[0]/PaymentDueByRowHolder[0]/PaymentDueByRow[0]", + "childIndex" : 0, + "depth" : 9, + "layer" : 9, + "computedX" : 338.263, + "computedY" : 118.194, + "placementX" : 338.263, + "placementY" : 118.194, + "placementWidth" : 20.313, + "placementHeight" : 20.313, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 20.313, + "contentHeight" : 20.313, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/ClosingRow[5]/SummaryColumn[1]/PaymentDueBy[1]/PaymentDueByRowLayer[0]/PaymentDueByRowHolder[0]/PaymentDueByRow[0]/PaymentDueByIcon[0]/SvgIcon[0]", + "entityName" : "SvgIcon", + "entityKind" : "LayerStackNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/ClosingRow[5]/SummaryColumn[1]/PaymentDueBy[1]/PaymentDueByRowLayer[0]/PaymentDueByRowHolder[0]/PaymentDueByRow[0]/PaymentDueByIcon[0]", + "childIndex" : 0, + "depth" : 10, + "layer" : 10, + "computedX" : 338.263, + "computedY" : 118.194, + "placementX" : 338.263, + "placementY" : 118.194, + "placementWidth" : 20.313, + "placementHeight" : 20.313, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 20.313, + "contentHeight" : 20.313, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/ClosingRow[5]/SummaryColumn[1]/PaymentDueBy[1]/PaymentDueByRowLayer[0]/PaymentDueByRowHolder[0]/PaymentDueByRow[0]/PaymentDueByIcon[0]/SvgIcon[0]/SvgLayer0[0]", + "entityName" : "SvgLayer0", + "entityKind" : "Path", + "parentPath" : "MeteredInvoice[0]/Body[0]/ClosingRow[5]/SummaryColumn[1]/PaymentDueBy[1]/PaymentDueByRowLayer[0]/PaymentDueByRowHolder[0]/PaymentDueByRow[0]/PaymentDueByIcon[0]/SvgIcon[0]", + "childIndex" : 0, + "depth" : 11, + "layer" : 11, + "computedX" : 338.263, + "computedY" : 118.194, + "placementX" : 338.263, + "placementY" : 118.194, + "placementWidth" : 20.313, + "placementHeight" : 20.313, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 20.313, + "contentHeight" : 20.313, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/ClosingRow[5]/SummaryColumn[1]/PaymentDueBy[1]/PaymentDueByRowLayer[0]/PaymentDueByRowHolder[0]/PaymentDueByRow[0]/PaymentDueByIcon[0]/SvgIcon[0]/SvgLayer1[1]", + "entityName" : "SvgLayer1", + "entityKind" : "Path", + "parentPath" : "MeteredInvoice[0]/Body[0]/ClosingRow[5]/SummaryColumn[1]/PaymentDueBy[1]/PaymentDueByRowLayer[0]/PaymentDueByRowHolder[0]/PaymentDueByRow[0]/PaymentDueByIcon[0]/SvgIcon[0]", + "childIndex" : 1, + "depth" : 11, + "layer" : 11, + "computedX" : 338.263, + "computedY" : 118.194, + "placementX" : 338.263, + "placementY" : 118.194, + "placementWidth" : 20.313, + "placementHeight" : 20.313, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 20.313, + "contentHeight" : 20.313, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/ClosingRow[5]/SummaryColumn[1]/PaymentDueBy[1]/PaymentDueByRowLayer[0]/PaymentDueByRowHolder[0]/PaymentDueByRow[0]/PaymentDueByIcon[0]/SvgIcon[0]/SvgLayer2[2]", + "entityName" : "SvgLayer2", + "entityKind" : "Path", + "parentPath" : "MeteredInvoice[0]/Body[0]/ClosingRow[5]/SummaryColumn[1]/PaymentDueBy[1]/PaymentDueByRowLayer[0]/PaymentDueByRowHolder[0]/PaymentDueByRow[0]/PaymentDueByIcon[0]/SvgIcon[0]", + "childIndex" : 2, + "depth" : 11, + "layer" : 11, + "computedX" : 338.263, + "computedY" : 118.194, + "placementX" : 338.263, + "placementY" : 118.194, + "placementWidth" : 20.313, + "placementHeight" : 20.313, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 20.313, + "contentHeight" : 20.313, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/ClosingRow[5]/SummaryColumn[1]/PaymentDueBy[1]/PaymentDueByRowLayer[0]/PaymentDueByRowHolder[0]/PaymentDueByRow[0]/PaymentDueByText[1]", + "entityName" : "PaymentDueByText", + "entityKind" : "SectionNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/ClosingRow[5]/SummaryColumn[1]/PaymentDueBy[1]/PaymentDueByRowLayer[0]/PaymentDueByRowHolder[0]/PaymentDueByRow[0]", + "childIndex" : 1, + "depth" : 9, + "layer" : 9, + "computedX" : 370.989, + "computedY" : 116.711, + "placementX" : 370.989, + "placementY" : 116.711, + "placementWidth" : 81.271, + "placementHeight" : 23.28, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 81.271, + "contentHeight" : 23.28, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/ClosingRow[5]/SummaryColumn[1]/PaymentDueBy[1]/PaymentDueByRowLayer[0]/PaymentDueByRowHolder[0]/PaymentDueByRow[0]/PaymentDueByText[1]/PaymentDueByLabel[0]", + "entityName" : "PaymentDueByLabel", + "entityKind" : "ParagraphNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/ClosingRow[5]/SummaryColumn[1]/PaymentDueBy[1]/PaymentDueByRowLayer[0]/PaymentDueByRowHolder[0]/PaymentDueByRow[0]/PaymentDueByText[1]", + "childIndex" : 0, + "depth" : 10, + "layer" : 10, + "computedX" : 370.989, + "computedY" : 130.391, + "placementX" : 370.989, + "placementY" : 130.391, + "placementWidth" : 69.8, + "placementHeight" : 9.6, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 69.8, + "contentHeight" : 9.6, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/ClosingRow[5]/SummaryColumn[1]/PaymentDueBy[1]/PaymentDueByRowLayer[0]/PaymentDueByRowHolder[0]/PaymentDueByRow[0]/PaymentDueByText[1]/PaymentDueByValue[1]", + "entityName" : "PaymentDueByValue", + "entityKind" : "ParagraphNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/ClosingRow[5]/SummaryColumn[1]/PaymentDueBy[1]/PaymentDueByRowLayer[0]/PaymentDueByRowHolder[0]/PaymentDueByRow[0]/PaymentDueByText[1]", + "childIndex" : 1, + "depth" : 10, + "layer" : 10, + "computedX" : 370.989, + "computedY" : 116.711, + "placementX" : 370.989, + "placementY" : 116.711, + "placementWidth" : 81.271, + "placementHeight" : 13.68, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 81.271, + "contentHeight" : 13.68, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/ClosingNote[6]", + "entityName" : "ClosingNote", + "entityKind" : "SectionNode", + "parentPath" : "MeteredInvoice[0]/Body[0]", + "childIndex" : 6, + "depth" : 3, + "layer" : 3, + "computedX" : 20.031, + "computedY" : 54.204, + "placementX" : 20.031, + "placementY" : 54.204, + "placementWidth" : 550.7, + "placementHeight" : 37.116, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 550.7, + "contentHeight" : 37.116, + "margin" : { + "top" : 1.693, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/ClosingNote[6]/ClosingNoteRule[0]", + "entityName" : "ClosingNoteRule", + "entityKind" : "ShapeNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/ClosingNote[6]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 20.031, + "computedY" : 90.36, + "placementX" : 20.031, + "placementY" : 90.36, + "placementWidth" : 550.7, + "placementHeight" : 0.959, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 550.7, + "contentHeight" : 0.959, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/ClosingNote[6]/ClosingNoteBody[1]", + "entityName" : "ClosingNoteBody", + "entityKind" : "RowNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/ClosingNote[6]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 20.031, + "computedY" : 54.204, + "placementX" : 20.031, + "placementY" : 54.204, + "placementWidth" : 550.7, + "placementHeight" : 23.461, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 550.7, + "contentHeight" : 23.461, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 3.103 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/ClosingNote[6]/ClosingNoteBody[1]/ClosingNoteIcon[0]", + "entityName" : "ClosingNoteIcon", + "entityKind" : "SectionNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/ClosingNote[6]/ClosingNoteBody[1]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 23.134, + "computedY" : 64.123, + "placementX" : 23.134, + "placementY" : 64.123, + "placementWidth" : 13.542, + "placementHeight" : 13.542, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 13.542, + "contentHeight" : 13.542, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/ClosingNote[6]/ClosingNoteBody[1]/ClosingNoteIcon[0]/SvgIcon[0]", + "entityName" : "SvgIcon", + "entityKind" : "LayerStackNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/ClosingNote[6]/ClosingNoteBody[1]/ClosingNoteIcon[0]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 23.134, + "computedY" : 64.123, + "placementX" : 23.134, + "placementY" : 64.123, + "placementWidth" : 13.542, + "placementHeight" : 13.542, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 13.542, + "contentHeight" : 13.542, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/ClosingNote[6]/ClosingNoteBody[1]/ClosingNoteIcon[0]/SvgIcon[0]/SvgLayer0[0]", + "entityName" : "SvgLayer0", + "entityKind" : "Path", + "parentPath" : "MeteredInvoice[0]/Body[0]/ClosingNote[6]/ClosingNoteBody[1]/ClosingNoteIcon[0]/SvgIcon[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 23.134, + "computedY" : 64.123, + "placementX" : 23.134, + "placementY" : 64.123, + "placementWidth" : 13.542, + "placementHeight" : 13.542, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 13.542, + "contentHeight" : 13.542, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/ClosingNote[6]/ClosingNoteBody[1]/ClosingNoteIcon[0]/SvgIcon[0]/SvgLayer1[1]", + "entityName" : "SvgLayer1", + "entityKind" : "Path", + "parentPath" : "MeteredInvoice[0]/Body[0]/ClosingNote[6]/ClosingNoteBody[1]/ClosingNoteIcon[0]/SvgIcon[0]", + "childIndex" : 1, + "depth" : 7, + "layer" : 7, + "computedX" : 23.134, + "computedY" : 64.123, + "placementX" : 23.134, + "placementY" : 64.123, + "placementWidth" : 13.542, + "placementHeight" : 13.542, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 13.542, + "contentHeight" : 13.542, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/ClosingNote[6]/ClosingNoteBody[1]/ClosingNoteText[1]", + "entityName" : "ClosingNoteText", + "entityKind" : "SectionNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/ClosingNote[6]/ClosingNoteBody[1]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 47.396, + "computedY" : 54.204, + "placementX" : 47.396, + "placementY" : 54.204, + "placementWidth" : 367.011, + "placementHeight" : 23.461, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 367.011, + "contentHeight" : 23.461, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/ClosingNote[6]/ClosingNoteBody[1]/ClosingNoteText[1]/ClosingNoteLead[0]", + "entityName" : "ClosingNoteLead", + "entityKind" : "ParagraphNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/ClosingNote[6]/ClosingNoteBody[1]/ClosingNoteText[1]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 47.396, + "computedY" : 66.745, + "placementX" : 47.396, + "placementY" : 66.745, + "placementWidth" : 177.022, + "placementHeight" : 10.92, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 177.022, + "contentHeight" : 10.92, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "MeteredInvoice[0]/Body[0]/ClosingNote[6]/ClosingNoteBody[1]/ClosingNoteText[1]/ClosingNoteProse_0[1]", + "entityName" : "ClosingNoteProse_0", + "entityKind" : "ParagraphNode", + "parentPath" : "MeteredInvoice[0]/Body[0]/ClosingNote[6]/ClosingNoteBody[1]/ClosingNoteText[1]", + "childIndex" : 1, + "depth" : 6, + "layer" : 6, + "computedX" : 47.396, + "computedY" : 54.204, + "placementX" : 47.396, + "placementY" : 54.204, + "placementWidth" : 367.011, + "placementHeight" : 9.72, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 367.011, + "contentHeight" : 9.72, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + } ] +} diff --git a/qa/src/test/resources/layout-snapshots/canonical-templates/invoice/obsidian_invoice_layout.json b/qa/src/test/resources/layout-snapshots/canonical-templates/invoice/obsidian_invoice_layout.json new file mode 100644 index 000000000..56afc5820 --- /dev/null +++ b/qa/src/test/resources/layout-snapshots/canonical-templates/invoice/obsidian_invoice_layout.json @@ -0,0 +1,4187 @@ +{ + "formatVersion" : "2.0", + "canvas" : { + "pageWidth" : 595.276, + "pageHeight" : 841.89, + "innerWidth" : 542.237, + "innerHeight" : 802.393, + "margin" : { + "top" : 25.955, + "right" : 27.084, + "bottom" : 13.542, + "left" : 25.955 + } + }, + "totalPages" : 1, + "nodes" : [ { + "path" : "ObsidianInvoice[0]", + "entityName" : "ObsidianInvoice", + "entityKind" : "ContainerNode", + "parentPath" : null, + "childIndex" : 0, + "depth" : 1, + "layer" : 1, + "computedX" : 25.955, + "computedY" : 16.015, + "placementX" : 25.955, + "placementY" : 16.015, + "placementWidth" : 542.237, + "placementHeight" : 799.92, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 542.237, + "contentHeight" : 799.92, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ObsidianInvoice[0]/Masthead[0]", + "entityName" : "Masthead", + "entityKind" : "RowNode", + "parentPath" : "ObsidianInvoice[0]", + "childIndex" : 0, + "depth" : 2, + "layer" : 2, + "computedX" : 25.955, + "computedY" : 690.441, + "placementX" : 25.955, + "placementY" : 690.441, + "placementWidth" : 542.237, + "placementHeight" : 125.493, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 542.237, + "contentHeight" : 125.493, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ObsidianInvoice[0]/Masthead[0]/BrandColumn[0]", + "entityName" : "BrandColumn", + "entityKind" : "SectionNode", + "parentPath" : "ObsidianInvoice[0]/Masthead[0]", + "childIndex" : 0, + "depth" : 3, + "layer" : 3, + "computedX" : 25.955, + "computedY" : 691.709, + "placementX" : 25.955, + "placementY" : 691.709, + "placementWidth" : 167.991, + "placementHeight" : 124.226, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 167.991, + "contentHeight" : 124.226, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ObsidianInvoice[0]/Masthead[0]/BrandColumn[0]/BrandWordmark[0]", + "entityName" : "BrandWordmark", + "entityKind" : "ParagraphNode", + "parentPath" : "ObsidianInvoice[0]/Masthead[0]/BrandColumn[0]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 25.955, + "computedY" : 789.772, + "placementX" : 25.955, + "placementY" : 789.772, + "placementWidth" : 90.274, + "placementHeight" : 26.265, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 90.274, + "contentHeight" : 26.265, + "margin" : { + "top" : -0.103, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ObsidianInvoice[0]/Masthead[0]/BrandColumn[0]/InvoiceTitle[1]", + "entityName" : "InvoiceTitle", + "entityKind" : "ParagraphNode", + "parentPath" : "ObsidianInvoice[0]/Masthead[0]/BrandColumn[0]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 25.955, + "computedY" : 691.709, + "placementX" : 25.955, + "placementY" : 691.709, + "placementWidth" : 167.991, + "placementHeight" : 41.306, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 167.991, + "contentHeight" : 41.306, + "margin" : { + "top" : 56.757, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ObsidianInvoice[0]/Masthead[0]/InvoiceMeta[1]", + "entityName" : "InvoiceMeta", + "entityKind" : "SectionNode", + "parentPath" : "ObsidianInvoice[0]/Masthead[0]", + "childIndex" : 1, + "depth" : 3, + "layer" : 3, + "computedX" : 350.959, + "computedY" : 690.441, + "placementX" : 350.959, + "placementY" : 690.441, + "placementWidth" : 217.233, + "placementHeight" : 125.493, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 217.233, + "contentHeight" : 125.493, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ObsidianInvoice[0]/Masthead[0]/InvoiceMeta[1]/MetaNumberLabel[0]", + "entityName" : "MetaNumberLabel", + "entityKind" : "ParagraphNode", + "parentPath" : "ObsidianInvoice[0]/Masthead[0]/InvoiceMeta[1]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 350.959, + "computedY" : 804.961, + "placementX" : 350.959, + "placementY" : 804.961, + "placementWidth" : 56.184, + "placementHeight" : 8.128, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 56.184, + "contentHeight" : 8.128, + "margin" : { + "top" : 2.845, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ObsidianInvoice[0]/Masthead[0]/InvoiceMeta[1]/MetaNumber[1]", + "entityName" : "MetaNumber", + "entityKind" : "ParagraphNode", + "parentPath" : "ObsidianInvoice[0]/Masthead[0]/InvoiceMeta[1]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 350.959, + "computedY" : 785.681, + "placementX" : 350.959, + "placementY" : 785.681, + "placementWidth" : 84.786, + "placementHeight" : 11.228, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 84.786, + "contentHeight" : 11.228, + "margin" : { + "top" : 8.052, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ObsidianInvoice[0]/Masthead[0]/InvoiceMeta[1]/MetaRow_0Rule[2]", + "entityName" : "MetaRow_0Rule", + "entityKind" : "LineNode", + "parentPath" : "ObsidianInvoice[0]/Masthead[0]/InvoiceMeta[1]", + "childIndex" : 2, + "depth" : 4, + "layer" : 4, + "computedX" : 350.959, + "computedY" : 775.593, + "placementX" : 350.959, + "placementY" : 775.593, + "placementWidth" : 217.233, + "placementHeight" : 1.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 217.233, + "contentHeight" : 1.0, + "margin" : { + "top" : 9.087, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ObsidianInvoice[0]/Masthead[0]/InvoiceMeta[1]/MetaRow_0Layer[3]", + "entityName" : "MetaRow_0Layer", + "entityKind" : "LayerStackNode", + "parentPath" : "ObsidianInvoice[0]/Masthead[0]/InvoiceMeta[1]", + "childIndex" : 3, + "depth" : 4, + "layer" : 4, + "computedX" : 350.959, + "computedY" : 758.15, + "placementX" : 350.959, + "placementY" : 758.15, + "placementWidth" : 217.233, + "placementHeight" : 17.443, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 217.233, + "contentHeight" : 17.443, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ObsidianInvoice[0]/Masthead[0]/InvoiceMeta[1]/MetaRow_0Layer[3]/MetaRow_0Holder[0]", + "entityName" : "MetaRow_0Holder", + "entityKind" : "SectionNode", + "parentPath" : "ObsidianInvoice[0]/Masthead[0]/InvoiceMeta[1]/MetaRow_0Layer[3]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 350.959, + "computedY" : 758.15, + "placementX" : 350.959, + "placementY" : 758.15, + "placementWidth" : 217.233, + "placementHeight" : 17.443, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 217.233, + "contentHeight" : 17.443, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ObsidianInvoice[0]/Masthead[0]/InvoiceMeta[1]/MetaRow_0Layer[3]/MetaRow_0Holder[0]/MetaRow_0[0]", + "entityName" : "MetaRow_0", + "entityKind" : "RowNode", + "parentPath" : "ObsidianInvoice[0]/Masthead[0]/InvoiceMeta[1]/MetaRow_0Layer[3]/MetaRow_0Holder[0]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 350.959, + "computedY" : 758.15, + "placementX" : 350.959, + "placementY" : 758.15, + "placementWidth" : 217.233, + "placementHeight" : 9.179, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 217.233, + "contentHeight" : 9.179, + "margin" : { + "top" : 8.264, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ObsidianInvoice[0]/Masthead[0]/InvoiceMeta[1]/MetaRow_0Layer[3]/MetaRow_0Holder[0]/MetaRow_0[0]/MetaRow_0Label[0]", + "entityName" : "MetaRow_0Label", + "entityKind" : "ParagraphNode", + "parentPath" : "ObsidianInvoice[0]/Masthead[0]/InvoiceMeta[1]/MetaRow_0Layer[3]/MetaRow_0Holder[0]/MetaRow_0[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 350.959, + "computedY" : 758.15, + "placementX" : 350.959, + "placementY" : 758.15, + "placementWidth" : 42.72, + "placementHeight" : 9.179, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 42.72, + "contentHeight" : 9.179, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ObsidianInvoice[0]/Masthead[0]/InvoiceMeta[1]/MetaRow_0Layer[3]/MetaRow_0Holder[0]/MetaRow_0[0]/MetaRow_0Value[1]", + "entityName" : "MetaRow_0Value", + "entityKind" : "ParagraphNode", + "parentPath" : "ObsidianInvoice[0]/Masthead[0]/InvoiceMeta[1]/MetaRow_0Layer[3]/MetaRow_0Holder[0]/MetaRow_0[0]", + "childIndex" : 1, + "depth" : 7, + "layer" : 7, + "computedX" : 459.575, + "computedY" : 758.15, + "placementX" : 459.575, + "placementY" : 758.15, + "placementWidth" : 108.617, + "placementHeight" : 9.179, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 108.617, + "contentHeight" : 9.179, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ObsidianInvoice[0]/Masthead[0]/InvoiceMeta[1]/MetaRow_1Rule[4]", + "entityName" : "MetaRow_1Rule", + "entityKind" : "LineNode", + "parentPath" : "ObsidianInvoice[0]/Masthead[0]/InvoiceMeta[1]", + "childIndex" : 4, + "depth" : 4, + "layer" : 4, + "computedX" : 350.959, + "computedY" : 751.331, + "placementX" : 350.959, + "placementY" : 751.331, + "placementWidth" : 217.233, + "placementHeight" : 1.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 217.233, + "contentHeight" : 1.0, + "margin" : { + "top" : 5.819, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ObsidianInvoice[0]/Masthead[0]/InvoiceMeta[1]/MetaRow_1Layer[5]", + "entityName" : "MetaRow_1Layer", + "entityKind" : "LayerStackNode", + "parentPath" : "ObsidianInvoice[0]/Masthead[0]/InvoiceMeta[1]", + "childIndex" : 5, + "depth" : 4, + "layer" : 4, + "computedX" : 350.959, + "computedY" : 735.581, + "placementX" : 350.959, + "placementY" : 735.581, + "placementWidth" : 217.233, + "placementHeight" : 15.75, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 217.233, + "contentHeight" : 15.75, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ObsidianInvoice[0]/Masthead[0]/InvoiceMeta[1]/MetaRow_1Layer[5]/MetaRow_1Holder[0]", + "entityName" : "MetaRow_1Holder", + "entityKind" : "SectionNode", + "parentPath" : "ObsidianInvoice[0]/Masthead[0]/InvoiceMeta[1]/MetaRow_1Layer[5]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 350.959, + "computedY" : 735.581, + "placementX" : 350.959, + "placementY" : 735.581, + "placementWidth" : 217.233, + "placementHeight" : 15.75, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 217.233, + "contentHeight" : 15.75, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ObsidianInvoice[0]/Masthead[0]/InvoiceMeta[1]/MetaRow_1Layer[5]/MetaRow_1Holder[0]/MetaRow_1[0]", + "entityName" : "MetaRow_1", + "entityKind" : "RowNode", + "parentPath" : "ObsidianInvoice[0]/Masthead[0]/InvoiceMeta[1]/MetaRow_1Layer[5]/MetaRow_1Holder[0]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 350.959, + "computedY" : 735.581, + "placementX" : 350.959, + "placementY" : 735.581, + "placementWidth" : 217.233, + "placementHeight" : 9.179, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 217.233, + "contentHeight" : 9.179, + "margin" : { + "top" : 6.571, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ObsidianInvoice[0]/Masthead[0]/InvoiceMeta[1]/MetaRow_1Layer[5]/MetaRow_1Holder[0]/MetaRow_1[0]/MetaRow_1Label[0]", + "entityName" : "MetaRow_1Label", + "entityKind" : "ParagraphNode", + "parentPath" : "ObsidianInvoice[0]/Masthead[0]/InvoiceMeta[1]/MetaRow_1Layer[5]/MetaRow_1Holder[0]/MetaRow_1[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 350.959, + "computedY" : 735.581, + "placementX" : 350.959, + "placementY" : 735.581, + "placementWidth" : 37.46, + "placementHeight" : 9.179, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 37.46, + "contentHeight" : 9.179, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ObsidianInvoice[0]/Masthead[0]/InvoiceMeta[1]/MetaRow_1Layer[5]/MetaRow_1Holder[0]/MetaRow_1[0]/MetaRow_1Value[1]", + "entityName" : "MetaRow_1Value", + "entityKind" : "ParagraphNode", + "parentPath" : "ObsidianInvoice[0]/Masthead[0]/InvoiceMeta[1]/MetaRow_1Layer[5]/MetaRow_1Holder[0]/MetaRow_1[0]", + "childIndex" : 1, + "depth" : 7, + "layer" : 7, + "computedX" : 459.575, + "computedY" : 735.581, + "placementX" : 459.575, + "placementY" : 735.581, + "placementWidth" : 108.617, + "placementHeight" : 9.179, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 108.617, + "contentHeight" : 9.179, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ObsidianInvoice[0]/Masthead[0]/InvoiceMeta[1]/MetaRow_2Rule[6]", + "entityName" : "MetaRow_2Rule", + "entityKind" : "LineNode", + "parentPath" : "ObsidianInvoice[0]/Masthead[0]/InvoiceMeta[1]", + "childIndex" : 6, + "depth" : 4, + "layer" : 4, + "computedX" : 350.959, + "computedY" : 729.325, + "placementX" : 350.959, + "placementY" : 729.325, + "placementWidth" : 217.233, + "placementHeight" : 1.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 217.233, + "contentHeight" : 1.0, + "margin" : { + "top" : 5.255, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ObsidianInvoice[0]/Masthead[0]/InvoiceMeta[1]/MetaRow_2Layer[7]", + "entityName" : "MetaRow_2Layer", + "entityKind" : "LayerStackNode", + "parentPath" : "ObsidianInvoice[0]/Masthead[0]/InvoiceMeta[1]", + "childIndex" : 7, + "depth" : 4, + "layer" : 4, + "computedX" : 350.959, + "computedY" : 713.011, + "placementX" : 350.959, + "placementY" : 713.011, + "placementWidth" : 217.233, + "placementHeight" : 16.314, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 217.233, + "contentHeight" : 16.314, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ObsidianInvoice[0]/Masthead[0]/InvoiceMeta[1]/MetaRow_2Layer[7]/MetaRow_2Holder[0]", + "entityName" : "MetaRow_2Holder", + "entityKind" : "SectionNode", + "parentPath" : "ObsidianInvoice[0]/Masthead[0]/InvoiceMeta[1]/MetaRow_2Layer[7]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 350.959, + "computedY" : 713.011, + "placementX" : 350.959, + "placementY" : 713.011, + "placementWidth" : 217.233, + "placementHeight" : 16.314, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 217.233, + "contentHeight" : 16.314, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ObsidianInvoice[0]/Masthead[0]/InvoiceMeta[1]/MetaRow_2Layer[7]/MetaRow_2Holder[0]/MetaRow_2[0]", + "entityName" : "MetaRow_2", + "entityKind" : "RowNode", + "parentPath" : "ObsidianInvoice[0]/Masthead[0]/InvoiceMeta[1]/MetaRow_2Layer[7]/MetaRow_2Holder[0]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 350.959, + "computedY" : 713.011, + "placementX" : 350.959, + "placementY" : 713.011, + "placementWidth" : 217.233, + "placementHeight" : 9.179, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 217.233, + "contentHeight" : 9.179, + "margin" : { + "top" : 7.135, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ObsidianInvoice[0]/Masthead[0]/InvoiceMeta[1]/MetaRow_2Layer[7]/MetaRow_2Holder[0]/MetaRow_2[0]/MetaRow_2Label[0]", + "entityName" : "MetaRow_2Label", + "entityKind" : "ParagraphNode", + "parentPath" : "ObsidianInvoice[0]/Masthead[0]/InvoiceMeta[1]/MetaRow_2Layer[7]/MetaRow_2Holder[0]/MetaRow_2[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 350.959, + "computedY" : 713.011, + "placementX" : 350.959, + "placementY" : 713.011, + "placementWidth" : 62.647, + "placementHeight" : 9.179, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 62.647, + "contentHeight" : 9.179, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ObsidianInvoice[0]/Masthead[0]/InvoiceMeta[1]/MetaRow_2Layer[7]/MetaRow_2Holder[0]/MetaRow_2[0]/MetaRow_2Value[1]", + "entityName" : "MetaRow_2Value", + "entityKind" : "ParagraphNode", + "parentPath" : "ObsidianInvoice[0]/Masthead[0]/InvoiceMeta[1]/MetaRow_2Layer[7]/MetaRow_2Holder[0]/MetaRow_2[0]", + "childIndex" : 1, + "depth" : 7, + "layer" : 7, + "computedX" : 459.575, + "computedY" : 713.011, + "placementX" : 459.575, + "placementY" : 713.011, + "placementWidth" : 108.617, + "placementHeight" : 9.179, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 108.617, + "contentHeight" : 9.179, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ObsidianInvoice[0]/Masthead[0]/InvoiceMeta[1]/MetaRow_3Rule[8]", + "entityName" : "MetaRow_3Rule", + "entityKind" : "LineNode", + "parentPath" : "ObsidianInvoice[0]/Masthead[0]/InvoiceMeta[1]", + "childIndex" : 8, + "depth" : 4, + "layer" : 4, + "computedX" : 350.959, + "computedY" : 706.756, + "placementX" : 350.959, + "placementY" : 706.756, + "placementWidth" : 217.233, + "placementHeight" : 1.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 217.233, + "contentHeight" : 1.0, + "margin" : { + "top" : 5.255, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ObsidianInvoice[0]/Masthead[0]/InvoiceMeta[1]/MetaRow_3Layer[9]", + "entityName" : "MetaRow_3Layer", + "entityKind" : "LayerStackNode", + "parentPath" : "ObsidianInvoice[0]/Masthead[0]/InvoiceMeta[1]", + "childIndex" : 9, + "depth" : 4, + "layer" : 4, + "computedX" : 350.959, + "computedY" : 690.441, + "placementX" : 350.959, + "placementY" : 690.441, + "placementWidth" : 217.233, + "placementHeight" : 16.314, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 217.233, + "contentHeight" : 16.314, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ObsidianInvoice[0]/Masthead[0]/InvoiceMeta[1]/MetaRow_3Layer[9]/MetaRow_3Holder[0]", + "entityName" : "MetaRow_3Holder", + "entityKind" : "SectionNode", + "parentPath" : "ObsidianInvoice[0]/Masthead[0]/InvoiceMeta[1]/MetaRow_3Layer[9]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 350.959, + "computedY" : 690.441, + "placementX" : 350.959, + "placementY" : 690.441, + "placementWidth" : 217.233, + "placementHeight" : 16.314, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 217.233, + "contentHeight" : 16.314, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ObsidianInvoice[0]/Masthead[0]/InvoiceMeta[1]/MetaRow_3Layer[9]/MetaRow_3Holder[0]/MetaRow_3[0]", + "entityName" : "MetaRow_3", + "entityKind" : "RowNode", + "parentPath" : "ObsidianInvoice[0]/Masthead[0]/InvoiceMeta[1]/MetaRow_3Layer[9]/MetaRow_3Holder[0]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 350.959, + "computedY" : 690.441, + "placementX" : 350.959, + "placementY" : 690.441, + "placementWidth" : 217.233, + "placementHeight" : 9.179, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 217.233, + "contentHeight" : 9.179, + "margin" : { + "top" : 7.135, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ObsidianInvoice[0]/Masthead[0]/InvoiceMeta[1]/MetaRow_3Layer[9]/MetaRow_3Holder[0]/MetaRow_3[0]/MetaRow_3Label[0]", + "entityName" : "MetaRow_3Label", + "entityKind" : "ParagraphNode", + "parentPath" : "ObsidianInvoice[0]/Masthead[0]/InvoiceMeta[1]/MetaRow_3Layer[9]/MetaRow_3Holder[0]/MetaRow_3[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 350.959, + "computedY" : 690.441, + "placementX" : 350.959, + "placementY" : 690.441, + "placementWidth" : 37.111, + "placementHeight" : 9.179, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 37.111, + "contentHeight" : 9.179, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ObsidianInvoice[0]/Masthead[0]/InvoiceMeta[1]/MetaRow_3Layer[9]/MetaRow_3Holder[0]/MetaRow_3[0]/MetaRow_3Value[1]", + "entityName" : "MetaRow_3Value", + "entityKind" : "ParagraphNode", + "parentPath" : "ObsidianInvoice[0]/Masthead[0]/InvoiceMeta[1]/MetaRow_3Layer[9]/MetaRow_3Holder[0]/MetaRow_3[0]", + "childIndex" : 1, + "depth" : 7, + "layer" : 7, + "computedX" : 459.575, + "computedY" : 690.441, + "placementX" : 459.575, + "placementY" : 690.441, + "placementWidth" : 108.617, + "placementHeight" : 9.179, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 108.617, + "contentHeight" : 9.179, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ObsidianInvoice[0]/HeaderDivider[1]", + "entityName" : "HeaderDivider", + "entityKind" : "LineNode", + "parentPath" : "ObsidianInvoice[0]", + "childIndex" : 1, + "depth" : 2, + "layer" : 2, + "computedX" : 25.955, + "computedY" : 675.216, + "placementX" : 25.955, + "placementY" : 675.216, + "placementWidth" : 542.237, + "placementHeight" : 1.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 542.237, + "contentHeight" : 1.0, + "margin" : { + "top" : 14.226, + "right" : 0.0, + "bottom" : 15.235, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ObsidianInvoice[0]/PartiesRow[2]", + "entityName" : "PartiesRow", + "entityKind" : "RowNode", + "parentPath" : "ObsidianInvoice[0]", + "childIndex" : 2, + "depth" : 2, + "layer" : 2, + "computedX" : 25.955, + "computedY" : 535.433, + "placementX" : 25.955, + "placementY" : 535.433, + "placementWidth" : 542.237, + "placementHeight" : 124.548, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 542.237, + "contentHeight" : 124.548, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ObsidianInvoice[0]/PartiesRow[2]/SupplierCard[0]", + "entityName" : "SupplierCard", + "entityKind" : "SectionNode", + "parentPath" : "ObsidianInvoice[0]/PartiesRow[2]", + "childIndex" : 0, + "depth" : 3, + "layer" : 3, + "computedX" : 25.955, + "computedY" : 535.433, + "placementX" : 25.955, + "placementY" : 535.433, + "placementWidth" : 260.398, + "placementHeight" : 124.548, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 260.398, + "contentHeight" : 124.548, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 13.542, + "bottom" : 14.67, + "left" : 13.542 + } + }, { + "path" : "ObsidianInvoice[0]/PartiesRow[2]/SupplierCard[0]/SupplierLabel[0]", + "entityName" : "SupplierLabel", + "entityKind" : "ParagraphNode", + "parentPath" : "ObsidianInvoice[0]/PartiesRow[2]/SupplierCard[0]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 39.497, + "computedY" : 634.377, + "placementX" : 39.497, + "placementY" : 634.377, + "placementWidth" : 21.785, + "placementHeight" : 8.903, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 21.785, + "contentHeight" : 8.903, + "margin" : { + "top" : 16.701, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ObsidianInvoice[0]/PartiesRow[2]/SupplierCard[0]/SupplierBodyLayer[1]", + "entityName" : "SupplierBodyLayer", + "entityKind" : "LayerStackNode", + "parentPath" : "ObsidianInvoice[0]/PartiesRow[2]/SupplierCard[0]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 39.497, + "computedY" : 550.103, + "placementX" : 39.497, + "placementY" : 550.103, + "placementWidth" : 233.314, + "placementHeight" : 84.274, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 233.314, + "contentHeight" : 84.274, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ObsidianInvoice[0]/PartiesRow[2]/SupplierCard[0]/SupplierBodyLayer[1]/SupplierBodyHolder[0]", + "entityName" : "SupplierBodyHolder", + "entityKind" : "SectionNode", + "parentPath" : "ObsidianInvoice[0]/PartiesRow[2]/SupplierCard[0]/SupplierBodyLayer[1]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 39.497, + "computedY" : 550.103, + "placementX" : 39.497, + "placementY" : 550.103, + "placementWidth" : 233.314, + "placementHeight" : 84.274, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 233.314, + "contentHeight" : 84.274, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ObsidianInvoice[0]/PartiesRow[2]/SupplierCard[0]/SupplierBodyLayer[1]/SupplierBodyHolder[0]/SupplierBody[0]", + "entityName" : "SupplierBody", + "entityKind" : "RowNode", + "parentPath" : "ObsidianInvoice[0]/PartiesRow[2]/SupplierCard[0]/SupplierBodyLayer[1]/SupplierBodyHolder[0]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 39.497, + "computedY" : 550.103, + "placementX" : 39.497, + "placementY" : 550.103, + "placementWidth" : 233.314, + "placementHeight" : 74.331, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 233.314, + "contentHeight" : 74.331, + "margin" : { + "top" : 9.944, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ObsidianInvoice[0]/PartiesRow[2]/SupplierCard[0]/SupplierBodyLayer[1]/SupplierBodyHolder[0]/SupplierBody[0]/SupplierDisc[0]", + "entityName" : "SupplierDisc", + "entityKind" : "SectionNode", + "parentPath" : "ObsidianInvoice[0]/PartiesRow[2]/SupplierCard[0]/SupplierBodyLayer[1]/SupplierBodyHolder[0]/SupplierBody[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 39.497, + "computedY" : 588.886, + "placementX" : 39.497, + "placementY" : 588.886, + "placementWidth" : 35.547, + "placementHeight" : 35.547, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 35.547, + "contentHeight" : 35.547, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ObsidianInvoice[0]/PartiesRow[2]/SupplierCard[0]/SupplierBodyLayer[1]/SupplierBodyHolder[0]/SupplierBody[0]/SupplierDisc[0]/SupplierDisc[0]", + "entityName" : "SupplierDisc", + "entityKind" : "ShapeContainerNode", + "parentPath" : "ObsidianInvoice[0]/PartiesRow[2]/SupplierCard[0]/SupplierBodyLayer[1]/SupplierBodyHolder[0]/SupplierBody[0]/SupplierDisc[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 39.497, + "computedY" : 588.886, + "placementX" : 39.497, + "placementY" : 588.886, + "placementWidth" : 35.547, + "placementHeight" : 35.547, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 35.547, + "contentHeight" : 35.547, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ObsidianInvoice[0]/PartiesRow[2]/SupplierCard[0]/SupplierBodyLayer[1]/SupplierBodyHolder[0]/SupplierBody[0]/SupplierDisc[0]/SupplierDisc[0]/SupplierDiscInitials[0]", + "entityName" : "SupplierDiscInitials", + "entityKind" : "ParagraphNode", + "parentPath" : "ObsidianInvoice[0]/PartiesRow[2]/SupplierCard[0]/SupplierBodyLayer[1]/SupplierBodyHolder[0]/SupplierBody[0]/SupplierDisc[0]/SupplierDisc[0]", + "childIndex" : 0, + "depth" : 9, + "layer" : 9, + "computedX" : 39.497, + "computedY" : 601.849, + "placementX" : 39.497, + "placementY" : 601.849, + "placementWidth" : 35.547, + "placementHeight" : 9.621, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 35.547, + "contentHeight" : 9.621, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ObsidianInvoice[0]/PartiesRow[2]/SupplierCard[0]/SupplierBodyLayer[1]/SupplierBodyHolder[0]/SupplierBody[0]/SupplierText[1]", + "entityName" : "SupplierText", + "entityKind" : "SectionNode", + "parentPath" : "ObsidianInvoice[0]/PartiesRow[2]/SupplierCard[0]/SupplierBodyLayer[1]/SupplierBodyHolder[0]/SupplierBody[0]", + "childIndex" : 1, + "depth" : 7, + "layer" : 7, + "computedX" : 102.128, + "computedY" : 550.103, + "placementX" : 102.128, + "placementY" : 550.103, + "placementWidth" : 86.34, + "placementHeight" : 74.331, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 86.34, + "contentHeight" : 74.331, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ObsidianInvoice[0]/PartiesRow[2]/SupplierCard[0]/SupplierBodyLayer[1]/SupplierBodyHolder[0]/SupplierBody[0]/SupplierText[1]/SupplierName[0]", + "entityName" : "SupplierName", + "entityKind" : "ParagraphNode", + "parentPath" : "ObsidianInvoice[0]/PartiesRow[2]/SupplierCard[0]/SupplierBodyLayer[1]/SupplierBodyHolder[0]/SupplierBody[0]/SupplierText[1]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 102.128, + "computedY" : 612.8, + "placementX" : 102.128, + "placementY" : 612.8, + "placementWidth" : 76.798, + "placementHeight" : 9.621, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 76.798, + "contentHeight" : 9.621, + "margin" : { + "top" : 2.013, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ObsidianInvoice[0]/PartiesRow[2]/SupplierCard[0]/SupplierBodyLayer[1]/SupplierBodyHolder[0]/SupplierBody[0]/SupplierText[1]/SupplierAddressLine_0[1]", + "entityName" : "SupplierAddressLine_0", + "entityKind" : "ParagraphNode", + "parentPath" : "ObsidianInvoice[0]/PartiesRow[2]/SupplierCard[0]/SupplierBodyLayer[1]/SupplierBodyHolder[0]/SupplierBody[0]/SupplierText[1]", + "childIndex" : 1, + "depth" : 8, + "layer" : 8, + "computedX" : 102.128, + "computedY" : 597.5, + "placementX" : 102.128, + "placementY" : 597.5, + "placementWidth" : 83.639, + "placementHeight" : 9.511, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 83.639, + "contentHeight" : 9.511, + "margin" : { + "top" : 5.789, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ObsidianInvoice[0]/PartiesRow[2]/SupplierCard[0]/SupplierBodyLayer[1]/SupplierBodyHolder[0]/SupplierBody[0]/SupplierText[1]/SupplierAddressLine_1[2]", + "entityName" : "SupplierAddressLine_1", + "entityKind" : "ParagraphNode", + "parentPath" : "ObsidianInvoice[0]/PartiesRow[2]/SupplierCard[0]/SupplierBodyLayer[1]/SupplierBodyHolder[0]/SupplierBody[0]/SupplierText[1]", + "childIndex" : 2, + "depth" : 8, + "layer" : 8, + "computedX" : 102.128, + "computedY" : 582.829, + "placementX" : 102.128, + "placementY" : 582.829, + "placementWidth" : 66.205, + "placementHeight" : 9.511, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 66.205, + "contentHeight" : 9.511, + "margin" : { + "top" : 5.159, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ObsidianInvoice[0]/PartiesRow[2]/SupplierCard[0]/SupplierBodyLayer[1]/SupplierBodyHolder[0]/SupplierBody[0]/SupplierText[1]/SupplierAddressLine_2[3]", + "entityName" : "SupplierAddressLine_2", + "entityKind" : "ParagraphNode", + "parentPath" : "ObsidianInvoice[0]/PartiesRow[2]/SupplierCard[0]/SupplierBodyLayer[1]/SupplierBodyHolder[0]/SupplierBody[0]/SupplierText[1]", + "childIndex" : 3, + "depth" : 8, + "layer" : 8, + "computedX" : 102.128, + "computedY" : 568.723, + "placementX" : 102.128, + "placementY" : 568.723, + "placementWidth" : 69.287, + "placementHeight" : 9.511, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 69.287, + "contentHeight" : 9.511, + "margin" : { + "top" : 4.595, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ObsidianInvoice[0]/PartiesRow[2]/SupplierCard[0]/SupplierBodyLayer[1]/SupplierBodyHolder[0]/SupplierBody[0]/SupplierText[1]/SupplierContact[4]", + "entityName" : "SupplierContact", + "entityKind" : "ParagraphNode", + "parentPath" : "ObsidianInvoice[0]/PartiesRow[2]/SupplierCard[0]/SupplierBodyLayer[1]/SupplierBodyHolder[0]/SupplierBody[0]/SupplierText[1]", + "childIndex" : 4, + "depth" : 8, + "layer" : 8, + "computedX" : 102.128, + "computedY" : 550.103, + "placementX" : 102.128, + "placementY" : 550.103, + "placementWidth" : 86.34, + "placementHeight" : 9.511, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 86.34, + "contentHeight" : 9.511, + "margin" : { + "top" : 9.109, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ObsidianInvoice[0]/PartiesRow[2]/BillToCard[1]", + "entityName" : "BillToCard", + "entityKind" : "SectionNode", + "parentPath" : "ObsidianInvoice[0]/PartiesRow[2]", + "childIndex" : 1, + "depth" : 3, + "layer" : 3, + "computedX" : 307.794, + "computedY" : 554.053, + "placementX" : 307.794, + "placementY" : 554.053, + "placementWidth" : 260.398, + "placementHeight" : 105.928, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 260.398, + "contentHeight" : 105.928, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 13.542, + "bottom" : 14.67, + "left" : 13.542 + } + }, { + "path" : "ObsidianInvoice[0]/PartiesRow[2]/BillToCard[1]/BillToLabel[0]", + "entityName" : "BillToLabel", + "entityKind" : "ParagraphNode", + "parentPath" : "ObsidianInvoice[0]/PartiesRow[2]/BillToCard[1]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 321.336, + "computedY" : 634.377, + "placementX" : 321.336, + "placementY" : 634.377, + "placementWidth" : 24.518, + "placementHeight" : 8.903, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 24.518, + "contentHeight" : 8.903, + "margin" : { + "top" : 16.701, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ObsidianInvoice[0]/PartiesRow[2]/BillToCard[1]/BillToBodyLayer[1]", + "entityName" : "BillToBodyLayer", + "entityKind" : "LayerStackNode", + "parentPath" : "ObsidianInvoice[0]/PartiesRow[2]/BillToCard[1]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 321.336, + "computedY" : 568.723, + "placementX" : 321.336, + "placementY" : 568.723, + "placementWidth" : 233.314, + "placementHeight" : 65.654, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 233.314, + "contentHeight" : 65.654, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ObsidianInvoice[0]/PartiesRow[2]/BillToCard[1]/BillToBodyLayer[1]/BillToBodyHolder[0]", + "entityName" : "BillToBodyHolder", + "entityKind" : "SectionNode", + "parentPath" : "ObsidianInvoice[0]/PartiesRow[2]/BillToCard[1]/BillToBodyLayer[1]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 321.336, + "computedY" : 568.723, + "placementX" : 321.336, + "placementY" : 568.723, + "placementWidth" : 233.314, + "placementHeight" : 65.654, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 233.314, + "contentHeight" : 65.654, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ObsidianInvoice[0]/PartiesRow[2]/BillToCard[1]/BillToBodyLayer[1]/BillToBodyHolder[0]/BillToBody[0]", + "entityName" : "BillToBody", + "entityKind" : "RowNode", + "parentPath" : "ObsidianInvoice[0]/PartiesRow[2]/BillToCard[1]/BillToBodyLayer[1]/BillToBodyHolder[0]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 321.336, + "computedY" : 568.723, + "placementX" : 321.336, + "placementY" : 568.723, + "placementWidth" : 233.314, + "placementHeight" : 55.711, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 233.314, + "contentHeight" : 55.711, + "margin" : { + "top" : 9.944, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ObsidianInvoice[0]/PartiesRow[2]/BillToCard[1]/BillToBodyLayer[1]/BillToBodyHolder[0]/BillToBody[0]/BillToDisc[0]", + "entityName" : "BillToDisc", + "entityKind" : "SectionNode", + "parentPath" : "ObsidianInvoice[0]/PartiesRow[2]/BillToCard[1]/BillToBodyLayer[1]/BillToBodyHolder[0]/BillToBody[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 321.336, + "computedY" : 588.886, + "placementX" : 321.336, + "placementY" : 588.886, + "placementWidth" : 35.547, + "placementHeight" : 35.547, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 35.547, + "contentHeight" : 35.547, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ObsidianInvoice[0]/PartiesRow[2]/BillToCard[1]/BillToBodyLayer[1]/BillToBodyHolder[0]/BillToBody[0]/BillToDisc[0]/BillToDisc[0]", + "entityName" : "BillToDisc", + "entityKind" : "ShapeContainerNode", + "parentPath" : "ObsidianInvoice[0]/PartiesRow[2]/BillToCard[1]/BillToBodyLayer[1]/BillToBodyHolder[0]/BillToBody[0]/BillToDisc[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 321.336, + "computedY" : 588.886, + "placementX" : 321.336, + "placementY" : 588.886, + "placementWidth" : 35.547, + "placementHeight" : 35.547, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 35.547, + "contentHeight" : 35.547, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ObsidianInvoice[0]/PartiesRow[2]/BillToCard[1]/BillToBodyLayer[1]/BillToBodyHolder[0]/BillToBody[0]/BillToDisc[0]/BillToDisc[0]/BillToDiscInitials[0]", + "entityName" : "BillToDiscInitials", + "entityKind" : "ParagraphNode", + "parentPath" : "ObsidianInvoice[0]/PartiesRow[2]/BillToCard[1]/BillToBodyLayer[1]/BillToBodyHolder[0]/BillToBody[0]/BillToDisc[0]/BillToDisc[0]", + "childIndex" : 0, + "depth" : 9, + "layer" : 9, + "computedX" : 321.336, + "computedY" : 601.849, + "placementX" : 321.336, + "placementY" : 601.849, + "placementWidth" : 35.547, + "placementHeight" : 9.621, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 35.547, + "contentHeight" : 9.621, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ObsidianInvoice[0]/PartiesRow[2]/BillToCard[1]/BillToBodyLayer[1]/BillToBodyHolder[0]/BillToBody[0]/BillToText[1]", + "entityName" : "BillToText", + "entityKind" : "SectionNode", + "parentPath" : "ObsidianInvoice[0]/PartiesRow[2]/BillToCard[1]/BillToBodyLayer[1]/BillToBodyHolder[0]/BillToBody[0]", + "childIndex" : 1, + "depth" : 7, + "layer" : 7, + "computedX" : 383.967, + "computedY" : 568.723, + "placementX" : 383.967, + "placementY" : 568.723, + "placementWidth" : 111.801, + "placementHeight" : 55.711, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 111.801, + "contentHeight" : 55.711, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ObsidianInvoice[0]/PartiesRow[2]/BillToCard[1]/BillToBodyLayer[1]/BillToBodyHolder[0]/BillToBody[0]/BillToText[1]/BillToName[0]", + "entityName" : "BillToName", + "entityKind" : "ParagraphNode", + "parentPath" : "ObsidianInvoice[0]/PartiesRow[2]/BillToCard[1]/BillToBodyLayer[1]/BillToBodyHolder[0]/BillToBody[0]/BillToText[1]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 383.967, + "computedY" : 612.8, + "placementX" : 383.967, + "placementY" : 612.8, + "placementWidth" : 111.801, + "placementHeight" : 9.621, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 111.801, + "contentHeight" : 9.621, + "margin" : { + "top" : 2.013, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ObsidianInvoice[0]/PartiesRow[2]/BillToCard[1]/BillToBodyLayer[1]/BillToBodyHolder[0]/BillToBody[0]/BillToText[1]/BillToAddressLine_0[1]", + "entityName" : "BillToAddressLine_0", + "entityKind" : "ParagraphNode", + "parentPath" : "ObsidianInvoice[0]/PartiesRow[2]/BillToCard[1]/BillToBodyLayer[1]/BillToBodyHolder[0]/BillToBody[0]/BillToText[1]", + "childIndex" : 1, + "depth" : 8, + "layer" : 8, + "computedX" : 383.967, + "computedY" : 597.5, + "placementX" : 383.967, + "placementY" : 597.5, + "placementWidth" : 64.208, + "placementHeight" : 9.511, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 64.208, + "contentHeight" : 9.511, + "margin" : { + "top" : 5.789, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ObsidianInvoice[0]/PartiesRow[2]/BillToCard[1]/BillToBodyLayer[1]/BillToBodyHolder[0]/BillToBody[0]/BillToText[1]/BillToAddressLine_1[2]", + "entityName" : "BillToAddressLine_1", + "entityKind" : "ParagraphNode", + "parentPath" : "ObsidianInvoice[0]/PartiesRow[2]/BillToCard[1]/BillToBodyLayer[1]/BillToBodyHolder[0]/BillToBody[0]/BillToText[1]", + "childIndex" : 2, + "depth" : 8, + "layer" : 8, + "computedX" : 383.967, + "computedY" : 582.829, + "placementX" : 383.967, + "placementY" : 582.829, + "placementWidth" : 70.19, + "placementHeight" : 9.511, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 70.19, + "contentHeight" : 9.511, + "margin" : { + "top" : 5.159, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ObsidianInvoice[0]/PartiesRow[2]/BillToCard[1]/BillToBodyLayer[1]/BillToBodyHolder[0]/BillToBody[0]/BillToText[1]/BillToAddressLine_2[3]", + "entityName" : "BillToAddressLine_2", + "entityKind" : "ParagraphNode", + "parentPath" : "ObsidianInvoice[0]/PartiesRow[2]/BillToCard[1]/BillToBodyLayer[1]/BillToBodyHolder[0]/BillToBody[0]/BillToText[1]", + "childIndex" : 3, + "depth" : 8, + "layer" : 8, + "computedX" : 383.967, + "computedY" : 568.723, + "placementX" : 383.967, + "placementY" : 568.723, + "placementWidth" : 69.287, + "placementHeight" : 9.511, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 69.287, + "contentHeight" : 9.511, + "margin" : { + "top" : 4.595, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ObsidianInvoice[0]/LineItemsCard[3]", + "entityName" : "LineItemsCard", + "entityKind" : "SectionNode", + "parentPath" : "ObsidianInvoice[0]", + "childIndex" : 3, + "depth" : 2, + "layer" : 2, + "computedX" : 25.955, + "computedY" : 286.962, + "placementX" : 25.955, + "placementY" : 286.962, + "placementWidth" : 542.237, + "placementHeight" : 232.673, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 542.237, + "contentHeight" : 232.673, + "margin" : { + "top" : 15.799, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 12.413, + "bottom" : 0.0, + "left" : 12.413 + } + }, { + "path" : "ObsidianInvoice[0]/LineItemsCard[3]/LineItems[0]", + "entityName" : "LineItems", + "entityKind" : "TableNode", + "parentPath" : "ObsidianInvoice[0]/LineItemsCard[3]", + "childIndex" : 0, + "depth" : 3, + "layer" : 3, + "computedX" : 38.368, + "computedY" : 286.962, + "placementX" : 38.368, + "placementY" : 286.962, + "placementWidth" : 517.41, + "placementHeight" : 232.673, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 517.41, + "contentHeight" : 232.673, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ObsidianInvoice[0]/TotalsRow[4]", + "entityName" : "TotalsRow", + "entityKind" : "RowNode", + "parentPath" : "ObsidianInvoice[0]", + "childIndex" : 4, + "depth" : 2, + "layer" : 2, + "computedX" : 25.955, + "computedY" : 197.162, + "placementX" : 25.955, + "placementY" : 197.162, + "placementWidth" : 542.237, + "placementHeight" : 89.799, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 542.237, + "contentHeight" : 89.799, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ObsidianInvoice[0]/TotalsRow[4]/TotalsSpacer[0]", + "entityName" : "TotalsSpacer", + "entityKind" : "SectionNode", + "parentPath" : "ObsidianInvoice[0]/TotalsRow[4]", + "childIndex" : 0, + "depth" : 3, + "layer" : 3, + "computedX" : 25.955, + "computedY" : 286.962, + "placementX" : 25.955, + "placementY" : 286.962, + "placementWidth" : 0.0, + "placementHeight" : 0.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 0.0, + "contentHeight" : 0.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ObsidianInvoice[0]/TotalsRow[4]/TotalsCard[1]", + "entityName" : "TotalsCard", + "entityKind" : "SectionNode", + "parentPath" : "ObsidianInvoice[0]/TotalsRow[4]", + "childIndex" : 1, + "depth" : 3, + "layer" : 3, + "computedX" : 307.794, + "computedY" : 197.162, + "placementX" : 307.794, + "placementY" : 197.162, + "placementWidth" : 260.398, + "placementHeight" : 82.464, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 260.398, + "contentHeight" : 82.464, + "margin" : { + "top" : 7.335, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 16.927, + "bottom" : 8.038, + "left" : 13.542 + } + }, { + "path" : "ObsidianInvoice[0]/TotalsRow[4]/TotalsCard[1]/TotalsRow_0Layer[0]", + "entityName" : "TotalsRow_0Layer", + "entityKind" : "LayerStackNode", + "parentPath" : "ObsidianInvoice[0]/TotalsRow[4]/TotalsCard[1]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 321.336, + "computedY" : 259.939, + "placementX" : 321.336, + "placementY" : 259.939, + "placementWidth" : 229.929, + "placementHeight" : 19.687, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 229.929, + "contentHeight" : 19.687, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ObsidianInvoice[0]/TotalsRow[4]/TotalsCard[1]/TotalsRow_0Layer[0]/TotalsRow_0Holder[0]", + "entityName" : "TotalsRow_0Holder", + "entityKind" : "SectionNode", + "parentPath" : "ObsidianInvoice[0]/TotalsRow[4]/TotalsCard[1]/TotalsRow_0Layer[0]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 321.336, + "computedY" : 259.939, + "placementX" : 321.336, + "placementY" : 259.939, + "placementWidth" : 229.929, + "placementHeight" : 19.687, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 229.929, + "contentHeight" : 19.687, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ObsidianInvoice[0]/TotalsRow[4]/TotalsCard[1]/TotalsRow_0Layer[0]/TotalsRow_0Holder[0]/TotalsRow_0[0]", + "entityName" : "TotalsRow_0", + "entityKind" : "RowNode", + "parentPath" : "ObsidianInvoice[0]/TotalsRow[4]/TotalsCard[1]/TotalsRow_0Layer[0]/TotalsRow_0Holder[0]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 321.336, + "computedY" : 259.939, + "placementX" : 321.336, + "placementY" : 259.939, + "placementWidth" : 229.929, + "placementHeight" : 10.119, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 229.929, + "contentHeight" : 10.119, + "margin" : { + "top" : 9.568, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ObsidianInvoice[0]/TotalsRow[4]/TotalsCard[1]/TotalsRow_0Layer[0]/TotalsRow_0Holder[0]/TotalsRow_0[0]/TotalsRow_0Label[0]", + "entityName" : "TotalsRow_0Label", + "entityKind" : "ParagraphNode", + "parentPath" : "ObsidianInvoice[0]/TotalsRow[4]/TotalsCard[1]/TotalsRow_0Layer[0]/TotalsRow_0Holder[0]/TotalsRow_0[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 321.336, + "computedY" : 261.211, + "placementX" : 321.336, + "placementY" : 261.211, + "placementWidth" : 33.806, + "placementHeight" : 8.847, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 33.806, + "contentHeight" : 8.847, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ObsidianInvoice[0]/TotalsRow[4]/TotalsCard[1]/TotalsRow_0Layer[0]/TotalsRow_0Holder[0]/TotalsRow_0[0]/TotalsRow_0Value[1]", + "entityName" : "TotalsRow_0Value", + "entityKind" : "ParagraphNode", + "parentPath" : "ObsidianInvoice[0]/TotalsRow[4]/TotalsCard[1]/TotalsRow_0Layer[0]/TotalsRow_0Holder[0]/TotalsRow_0[0]", + "childIndex" : 1, + "depth" : 7, + "layer" : 7, + "computedX" : 436.3, + "computedY" : 259.939, + "placementX" : 436.3, + "placementY" : 259.939, + "placementWidth" : 114.964, + "placementHeight" : 10.119, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 114.964, + "contentHeight" : 10.119, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ObsidianInvoice[0]/TotalsRow[4]/TotalsCard[1]/TotalsRow_1Layer[1]", + "entityName" : "TotalsRow_1Layer", + "entityKind" : "LayerStackNode", + "parentPath" : "ObsidianInvoice[0]/TotalsRow[4]/TotalsCard[1]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 321.336, + "computedY" : 240.755, + "placementX" : 321.336, + "placementY" : 240.755, + "placementWidth" : 229.929, + "placementHeight" : 19.184, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 229.929, + "contentHeight" : 19.184, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ObsidianInvoice[0]/TotalsRow[4]/TotalsCard[1]/TotalsRow_1Layer[1]/TotalsRow_1Holder[0]", + "entityName" : "TotalsRow_1Holder", + "entityKind" : "SectionNode", + "parentPath" : "ObsidianInvoice[0]/TotalsRow[4]/TotalsCard[1]/TotalsRow_1Layer[1]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 321.336, + "computedY" : 240.755, + "placementX" : 321.336, + "placementY" : 240.755, + "placementWidth" : 229.929, + "placementHeight" : 19.184, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 229.929, + "contentHeight" : 19.184, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ObsidianInvoice[0]/TotalsRow[4]/TotalsCard[1]/TotalsRow_1Layer[1]/TotalsRow_1Holder[0]/TotalsRow_1[0]", + "entityName" : "TotalsRow_1", + "entityKind" : "RowNode", + "parentPath" : "ObsidianInvoice[0]/TotalsRow[4]/TotalsCard[1]/TotalsRow_1Layer[1]/TotalsRow_1Holder[0]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 321.336, + "computedY" : 240.755, + "placementX" : 321.336, + "placementY" : 240.755, + "placementWidth" : 229.929, + "placementHeight" : 10.119, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 229.929, + "contentHeight" : 10.119, + "margin" : { + "top" : 9.065, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ObsidianInvoice[0]/TotalsRow[4]/TotalsCard[1]/TotalsRow_1Layer[1]/TotalsRow_1Holder[0]/TotalsRow_1[0]/TotalsRow_1Label[0]", + "entityName" : "TotalsRow_1Label", + "entityKind" : "ParagraphNode", + "parentPath" : "ObsidianInvoice[0]/TotalsRow[4]/TotalsCard[1]/TotalsRow_1Layer[1]/TotalsRow_1Holder[0]/TotalsRow_1[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 321.336, + "computedY" : 242.027, + "placementX" : 321.336, + "placementY" : 242.027, + "placementWidth" : 43.998, + "placementHeight" : 8.847, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 43.998, + "contentHeight" : 8.847, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ObsidianInvoice[0]/TotalsRow[4]/TotalsCard[1]/TotalsRow_1Layer[1]/TotalsRow_1Holder[0]/TotalsRow_1[0]/TotalsRow_1Value[1]", + "entityName" : "TotalsRow_1Value", + "entityKind" : "ParagraphNode", + "parentPath" : "ObsidianInvoice[0]/TotalsRow[4]/TotalsCard[1]/TotalsRow_1Layer[1]/TotalsRow_1Holder[0]/TotalsRow_1[0]", + "childIndex" : 1, + "depth" : 7, + "layer" : 7, + "computedX" : 436.3, + "computedY" : 240.755, + "placementX" : 436.3, + "placementY" : 240.755, + "placementWidth" : 114.964, + "placementHeight" : 10.119, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 114.964, + "contentHeight" : 10.119, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ObsidianInvoice[0]/TotalsRow[4]/TotalsCard[1]/TotalsRule[2]", + "entityName" : "TotalsRule", + "entityKind" : "LineNode", + "parentPath" : "ObsidianInvoice[0]/TotalsRow[4]/TotalsCard[1]", + "childIndex" : 2, + "depth" : 4, + "layer" : 4, + "computedX" : 317.386, + "computedY" : 231.992, + "placementX" : 317.386, + "placementY" : 231.992, + "placementWidth" : 237.828, + "placementHeight" : 1.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 237.828, + "contentHeight" : 1.0, + "margin" : { + "top" : 7.763, + "right" : -3.95, + "bottom" : 0.0, + "left" : -3.95 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ObsidianInvoice[0]/TotalsRow[4]/TotalsCard[1]/TotalDueLayer[3]", + "entityName" : "TotalDueLayer", + "entityKind" : "LayerStackNode", + "parentPath" : "ObsidianInvoice[0]/TotalsRow[4]/TotalsCard[1]", + "childIndex" : 3, + "depth" : 4, + "layer" : 4, + "computedX" : 321.336, + "computedY" : 205.2, + "placementX" : 321.336, + "placementY" : 205.2, + "placementWidth" : 229.929, + "placementHeight" : 26.791, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 229.929, + "contentHeight" : 26.791, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ObsidianInvoice[0]/TotalsRow[4]/TotalsCard[1]/TotalDueLayer[3]/TotalDueHolder[0]", + "entityName" : "TotalDueHolder", + "entityKind" : "SectionNode", + "parentPath" : "ObsidianInvoice[0]/TotalsRow[4]/TotalsCard[1]/TotalDueLayer[3]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 321.336, + "computedY" : 205.2, + "placementX" : 321.336, + "placementY" : 205.2, + "placementWidth" : 229.929, + "placementHeight" : 26.791, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 229.929, + "contentHeight" : 26.791, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ObsidianInvoice[0]/TotalsRow[4]/TotalsCard[1]/TotalDueLayer[3]/TotalDueHolder[0]/TotalDue[0]", + "entityName" : "TotalDue", + "entityKind" : "RowNode", + "parentPath" : "ObsidianInvoice[0]/TotalsRow[4]/TotalsCard[1]/TotalDueLayer[3]/TotalDueHolder[0]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 321.336, + "computedY" : 205.2, + "placementX" : 321.336, + "placementY" : 205.2, + "placementWidth" : 229.929, + "placementHeight" : 17.805, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 229.929, + "contentHeight" : 17.805, + "margin" : { + "top" : 8.986, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ObsidianInvoice[0]/TotalsRow[4]/TotalsCard[1]/TotalDueLayer[3]/TotalDueHolder[0]/TotalDue[0]/TotalDueLabel[0]", + "entityName" : "TotalDueLabel", + "entityKind" : "ParagraphNode", + "parentPath" : "ObsidianInvoice[0]/TotalsRow[4]/TotalsCard[1]/TotalDueLayer[3]/TotalDueHolder[0]/TotalDue[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 321.336, + "computedY" : 209.265, + "placementX" : 321.336, + "placementY" : 209.265, + "placementWidth" : 43.11, + "placementHeight" : 9.677, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 43.11, + "contentHeight" : 9.677, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ObsidianInvoice[0]/TotalsRow[4]/TotalsCard[1]/TotalDueLayer[3]/TotalDueHolder[0]/TotalDue[0]/TotalDueValue[1]", + "entityName" : "TotalDueValue", + "entityKind" : "ParagraphNode", + "parentPath" : "ObsidianInvoice[0]/TotalsRow[4]/TotalsCard[1]/TotalDueLayer[3]/TotalDueHolder[0]/TotalDue[0]", + "childIndex" : 1, + "depth" : 7, + "layer" : 7, + "computedX" : 436.3, + "computedY" : 205.2, + "placementX" : 436.3, + "placementY" : 205.2, + "placementWidth" : 114.964, + "placementHeight" : 17.805, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 114.964, + "contentHeight" : 17.805, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ObsidianInvoice[0]/InfoCardsRow[5]", + "entityName" : "InfoCardsRow", + "entityKind" : "RowNode", + "parentPath" : "ObsidianInvoice[0]", + "childIndex" : 5, + "depth" : 2, + "layer" : 2, + "computedX" : 25.955, + "computedY" : 65.386, + "placementX" : 25.955, + "placementY" : 65.386, + "placementWidth" : 542.237, + "placementHeight" : 123.313, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 542.237, + "contentHeight" : 123.313, + "margin" : { + "top" : 8.464, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ObsidianInvoice[0]/InfoCardsRow[5]/NotesCard[0]", + "entityName" : "NotesCard", + "entityKind" : "SectionNode", + "parentPath" : "ObsidianInvoice[0]/InfoCardsRow[5]", + "childIndex" : 0, + "depth" : 3, + "layer" : 3, + "computedX" : 25.955, + "computedY" : 65.976, + "placementX" : 25.955, + "placementY" : 65.976, + "placementWidth" : 260.398, + "placementHeight" : 122.723, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 260.398, + "contentHeight" : 122.723, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 13.542, + "bottom" : 16.981, + "left" : 13.542 + } + }, { + "path" : "ObsidianInvoice[0]/InfoCardsRow[5]/NotesCard[0]/NotesHeadLayer[0]", + "entityName" : "NotesHeadLayer", + "entityKind" : "LayerStackNode", + "parentPath" : "ObsidianInvoice[0]/InfoCardsRow[5]/NotesCard[0]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 39.497, + "computedY" : 160.487, + "placementX" : 39.497, + "placementY" : 160.487, + "placementWidth" : 233.314, + "placementHeight" : 28.212, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 233.314, + "contentHeight" : 28.212, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ObsidianInvoice[0]/InfoCardsRow[5]/NotesCard[0]/NotesHeadLayer[0]/NotesHeadHolder[0]", + "entityName" : "NotesHeadHolder", + "entityKind" : "SectionNode", + "parentPath" : "ObsidianInvoice[0]/InfoCardsRow[5]/NotesCard[0]/NotesHeadLayer[0]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 39.497, + "computedY" : 160.487, + "placementX" : 39.497, + "placementY" : 160.487, + "placementWidth" : 233.314, + "placementHeight" : 28.212, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 233.314, + "contentHeight" : 28.212, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ObsidianInvoice[0]/InfoCardsRow[5]/NotesCard[0]/NotesHeadLayer[0]/NotesHeadHolder[0]/NotesHead[0]", + "entityName" : "NotesHead", + "entityKind" : "RowNode", + "parentPath" : "ObsidianInvoice[0]/InfoCardsRow[5]/NotesCard[0]/NotesHeadLayer[0]/NotesHeadHolder[0]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 39.497, + "computedY" : 160.487, + "placementX" : 39.497, + "placementY" : 160.487, + "placementWidth" : 233.314, + "placementHeight" : 14.106, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 233.314, + "contentHeight" : 14.106, + "margin" : { + "top" : 14.106, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ObsidianInvoice[0]/InfoCardsRow[5]/NotesCard[0]/NotesHeadLayer[0]/NotesHeadHolder[0]/NotesHead[0]/NotesIcon[0]", + "entityName" : "NotesIcon", + "entityKind" : "SectionNode", + "parentPath" : "ObsidianInvoice[0]/InfoCardsRow[5]/NotesCard[0]/NotesHeadLayer[0]/NotesHeadHolder[0]/NotesHead[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 39.497, + "computedY" : 160.487, + "placementX" : 39.497, + "placementY" : 160.487, + "placementWidth" : 26.519, + "placementHeight" : 14.106, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 26.519, + "contentHeight" : 14.106, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ObsidianInvoice[0]/InfoCardsRow[5]/NotesCard[0]/NotesHeadLayer[0]/NotesHeadHolder[0]/NotesHead[0]/NotesIcon[0]/Glyph_notes[0]", + "entityName" : "Glyph_notes", + "entityKind" : "SectionNode", + "parentPath" : "ObsidianInvoice[0]/InfoCardsRow[5]/NotesCard[0]/NotesHeadLayer[0]/NotesHeadHolder[0]/NotesHead[0]/NotesIcon[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 39.497, + "computedY" : 160.487, + "placementX" : 39.497, + "placementY" : 160.487, + "placementWidth" : 26.519, + "placementHeight" : 14.106, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 26.519, + "contentHeight" : 14.106, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ObsidianInvoice[0]/InfoCardsRow[5]/NotesCard[0]/NotesHeadLayer[0]/NotesHeadHolder[0]/NotesHead[0]/NotesIcon[0]/Glyph_notes[0]/Align[0]", + "entityName" : null, + "entityKind" : "Align", + "parentPath" : "ObsidianInvoice[0]/InfoCardsRow[5]/NotesCard[0]/NotesHeadLayer[0]/NotesHeadHolder[0]/NotesHead[0]/NotesIcon[0]/Glyph_notes[0]", + "childIndex" : 0, + "depth" : 9, + "layer" : 9, + "computedX" : 39.497, + "computedY" : 160.487, + "placementX" : 39.497, + "placementY" : 160.487, + "placementWidth" : 26.519, + "placementHeight" : 14.106, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 26.519, + "contentHeight" : 14.106, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ObsidianInvoice[0]/InfoCardsRow[5]/NotesCard[0]/NotesHeadLayer[0]/NotesHeadHolder[0]/NotesHead[0]/NotesIcon[0]/Glyph_notes[0]/Align[0]/SvgIcon[0]", + "entityName" : "SvgIcon", + "entityKind" : "LayerStackNode", + "parentPath" : "ObsidianInvoice[0]/InfoCardsRow[5]/NotesCard[0]/NotesHeadLayer[0]/NotesHeadHolder[0]/NotesHead[0]/NotesIcon[0]/Glyph_notes[0]/Align[0]", + "childIndex" : 0, + "depth" : 10, + "layer" : 10, + "computedX" : 39.497, + "computedY" : 160.487, + "placementX" : 39.497, + "placementY" : 160.487, + "placementWidth" : 14.106, + "placementHeight" : 14.106, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 14.106, + "contentHeight" : 14.106, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ObsidianInvoice[0]/InfoCardsRow[5]/NotesCard[0]/NotesHeadLayer[0]/NotesHeadHolder[0]/NotesHead[0]/NotesIcon[0]/Glyph_notes[0]/Align[0]/SvgIcon[0]/SvgLayer0[0]", + "entityName" : "SvgLayer0", + "entityKind" : "Path", + "parentPath" : "ObsidianInvoice[0]/InfoCardsRow[5]/NotesCard[0]/NotesHeadLayer[0]/NotesHeadHolder[0]/NotesHead[0]/NotesIcon[0]/Glyph_notes[0]/Align[0]/SvgIcon[0]", + "childIndex" : 0, + "depth" : 11, + "layer" : 11, + "computedX" : 39.497, + "computedY" : 160.487, + "placementX" : 39.497, + "placementY" : 160.487, + "placementWidth" : 14.106, + "placementHeight" : 14.106, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 14.106, + "contentHeight" : 14.106, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ObsidianInvoice[0]/InfoCardsRow[5]/NotesCard[0]/NotesHeadLayer[0]/NotesHeadHolder[0]/NotesHead[0]/NotesHeading[1]", + "entityName" : "NotesHeading", + "entityKind" : "ParagraphNode", + "parentPath" : "ObsidianInvoice[0]/InfoCardsRow[5]/NotesCard[0]/NotesHeadLayer[0]/NotesHeadHolder[0]/NotesHead[0]", + "childIndex" : 1, + "depth" : 7, + "layer" : 7, + "computedX" : 66.016, + "computedY" : 163.365, + "placementX" : 66.016, + "placementY" : 163.365, + "placementWidth" : 24.421, + "placementHeight" : 8.626, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 24.421, + "contentHeight" : 8.626, + "margin" : { + "top" : 2.602, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ObsidianInvoice[0]/InfoCardsRow[5]/NotesCard[0]/NotesBody[1]", + "entityName" : "NotesBody", + "entityKind" : "SectionNode", + "parentPath" : "ObsidianInvoice[0]/InfoCardsRow[5]/NotesCard[0]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 39.497, + "computedY" : 82.957, + "placementX" : 39.497, + "placementY" : 82.957, + "placementWidth" : 216.942, + "placementHeight" : 77.53, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 216.942, + "contentHeight" : 77.53, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 26.519 + } + }, { + "path" : "ObsidianInvoice[0]/InfoCardsRow[5]/NotesCard[0]/NotesBody[1]/NotesParagraph_0[0]", + "entityName" : "NotesParagraph_0", + "entityKind" : "ParagraphNode", + "parentPath" : "ObsidianInvoice[0]/InfoCardsRow[5]/NotesCard[0]/NotesBody[1]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 66.016, + "computedY" : 118.786, + "placementX" : 66.016, + "placementY" : 118.786, + "placementWidth" : 190.423, + "placementHeight" : 33.796, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 190.423, + "contentHeight" : 33.796, + "margin" : { + "top" : 7.905, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ObsidianInvoice[0]/InfoCardsRow[5]/NotesCard[0]/NotesBody[1]/NotesParagraph_1[1]", + "entityName" : "NotesParagraph_1", + "entityKind" : "ParagraphNode", + "parentPath" : "ObsidianInvoice[0]/InfoCardsRow[5]/NotesCard[0]/NotesBody[1]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 66.016, + "computedY" : 82.957, + "placementX" : 66.016, + "placementY" : 82.957, + "placementWidth" : 167.62, + "placementHeight" : 21.1, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 167.62, + "contentHeight" : 21.1, + "margin" : { + "top" : 14.729, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ObsidianInvoice[0]/InfoCardsRow[5]/PaymentCard[1]", + "entityName" : "PaymentCard", + "entityKind" : "SectionNode", + "parentPath" : "ObsidianInvoice[0]/InfoCardsRow[5]", + "childIndex" : 1, + "depth" : 3, + "layer" : 3, + "computedX" : 307.794, + "computedY" : 65.386, + "placementX" : 307.794, + "placementY" : 65.386, + "placementWidth" : 260.398, + "placementHeight" : 123.313, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 260.398, + "contentHeight" : 123.313, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 13.542, + "bottom" : 12.312, + "left" : 13.542 + } + }, { + "path" : "ObsidianInvoice[0]/InfoCardsRow[5]/PaymentCard[1]/PaymentHeadLayer[0]", + "entityName" : "PaymentHeadLayer", + "entityKind" : "LayerStackNode", + "parentPath" : "ObsidianInvoice[0]/InfoCardsRow[5]/PaymentCard[1]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 321.336, + "computedY" : 160.487, + "placementX" : 321.336, + "placementY" : 160.487, + "placementWidth" : 233.314, + "placementHeight" : 28.212, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 233.314, + "contentHeight" : 28.212, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ObsidianInvoice[0]/InfoCardsRow[5]/PaymentCard[1]/PaymentHeadLayer[0]/PaymentHeadHolder[0]", + "entityName" : "PaymentHeadHolder", + "entityKind" : "SectionNode", + "parentPath" : "ObsidianInvoice[0]/InfoCardsRow[5]/PaymentCard[1]/PaymentHeadLayer[0]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 321.336, + "computedY" : 160.487, + "placementX" : 321.336, + "placementY" : 160.487, + "placementWidth" : 233.314, + "placementHeight" : 28.212, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 233.314, + "contentHeight" : 28.212, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ObsidianInvoice[0]/InfoCardsRow[5]/PaymentCard[1]/PaymentHeadLayer[0]/PaymentHeadHolder[0]/PaymentHead[0]", + "entityName" : "PaymentHead", + "entityKind" : "RowNode", + "parentPath" : "ObsidianInvoice[0]/InfoCardsRow[5]/PaymentCard[1]/PaymentHeadLayer[0]/PaymentHeadHolder[0]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 321.336, + "computedY" : 160.487, + "placementX" : 321.336, + "placementY" : 160.487, + "placementWidth" : 233.314, + "placementHeight" : 14.106, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 233.314, + "contentHeight" : 14.106, + "margin" : { + "top" : 14.106, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ObsidianInvoice[0]/InfoCardsRow[5]/PaymentCard[1]/PaymentHeadLayer[0]/PaymentHeadHolder[0]/PaymentHead[0]/PaymentIcon[0]", + "entityName" : "PaymentIcon", + "entityKind" : "SectionNode", + "parentPath" : "ObsidianInvoice[0]/InfoCardsRow[5]/PaymentCard[1]/PaymentHeadLayer[0]/PaymentHeadHolder[0]/PaymentHead[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 321.336, + "computedY" : 160.487, + "placementX" : 321.336, + "placementY" : 160.487, + "placementWidth" : 26.519, + "placementHeight" : 14.106, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 26.519, + "contentHeight" : 14.106, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ObsidianInvoice[0]/InfoCardsRow[5]/PaymentCard[1]/PaymentHeadLayer[0]/PaymentHeadHolder[0]/PaymentHead[0]/PaymentIcon[0]/Glyph_payment[0]", + "entityName" : "Glyph_payment", + "entityKind" : "SectionNode", + "parentPath" : "ObsidianInvoice[0]/InfoCardsRow[5]/PaymentCard[1]/PaymentHeadLayer[0]/PaymentHeadHolder[0]/PaymentHead[0]/PaymentIcon[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 321.336, + "computedY" : 160.487, + "placementX" : 321.336, + "placementY" : 160.487, + "placementWidth" : 26.519, + "placementHeight" : 14.106, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 26.519, + "contentHeight" : 14.106, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ObsidianInvoice[0]/InfoCardsRow[5]/PaymentCard[1]/PaymentHeadLayer[0]/PaymentHeadHolder[0]/PaymentHead[0]/PaymentIcon[0]/Glyph_payment[0]/Align[0]", + "entityName" : null, + "entityKind" : "Align", + "parentPath" : "ObsidianInvoice[0]/InfoCardsRow[5]/PaymentCard[1]/PaymentHeadLayer[0]/PaymentHeadHolder[0]/PaymentHead[0]/PaymentIcon[0]/Glyph_payment[0]", + "childIndex" : 0, + "depth" : 9, + "layer" : 9, + "computedX" : 321.336, + "computedY" : 160.487, + "placementX" : 321.336, + "placementY" : 160.487, + "placementWidth" : 26.519, + "placementHeight" : 14.106, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 26.519, + "contentHeight" : 14.106, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ObsidianInvoice[0]/InfoCardsRow[5]/PaymentCard[1]/PaymentHeadLayer[0]/PaymentHeadHolder[0]/PaymentHead[0]/PaymentIcon[0]/Glyph_payment[0]/Align[0]/SvgIcon[0]", + "entityName" : "SvgIcon", + "entityKind" : "LayerStackNode", + "parentPath" : "ObsidianInvoice[0]/InfoCardsRow[5]/PaymentCard[1]/PaymentHeadLayer[0]/PaymentHeadHolder[0]/PaymentHead[0]/PaymentIcon[0]/Glyph_payment[0]/Align[0]", + "childIndex" : 0, + "depth" : 10, + "layer" : 10, + "computedX" : 321.336, + "computedY" : 160.487, + "placementX" : 321.336, + "placementY" : 160.487, + "placementWidth" : 14.106, + "placementHeight" : 14.106, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 14.106, + "contentHeight" : 14.106, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ObsidianInvoice[0]/InfoCardsRow[5]/PaymentCard[1]/PaymentHeadLayer[0]/PaymentHeadHolder[0]/PaymentHead[0]/PaymentIcon[0]/Glyph_payment[0]/Align[0]/SvgIcon[0]/SvgLayer0[0]", + "entityName" : "SvgLayer0", + "entityKind" : "Path", + "parentPath" : "ObsidianInvoice[0]/InfoCardsRow[5]/PaymentCard[1]/PaymentHeadLayer[0]/PaymentHeadHolder[0]/PaymentHead[0]/PaymentIcon[0]/Glyph_payment[0]/Align[0]/SvgIcon[0]", + "childIndex" : 0, + "depth" : 11, + "layer" : 11, + "computedX" : 321.336, + "computedY" : 160.487, + "placementX" : 321.336, + "placementY" : 160.487, + "placementWidth" : 14.106, + "placementHeight" : 14.106, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 14.106, + "contentHeight" : 14.106, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ObsidianInvoice[0]/InfoCardsRow[5]/PaymentCard[1]/PaymentHeadLayer[0]/PaymentHeadHolder[0]/PaymentHead[0]/PaymentHeading[1]", + "entityName" : "PaymentHeading", + "entityKind" : "ParagraphNode", + "parentPath" : "ObsidianInvoice[0]/InfoCardsRow[5]/PaymentCard[1]/PaymentHeadLayer[0]/PaymentHeadHolder[0]/PaymentHead[0]", + "childIndex" : 1, + "depth" : 7, + "layer" : 7, + "computedX" : 347.855, + "computedY" : 163.365, + "placementX" : 347.855, + "placementY" : 163.365, + "placementWidth" : 66.611, + "placementHeight" : 8.626, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 66.611, + "contentHeight" : 8.626, + "margin" : { + "top" : 2.602, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ObsidianInvoice[0]/InfoCardsRow[5]/PaymentCard[1]/PaymentIntro[1]", + "entityName" : "PaymentIntro", + "entityKind" : "ParagraphNode", + "parentPath" : "ObsidianInvoice[0]/InfoCardsRow[5]/PaymentCard[1]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 321.336, + "computedY" : 133.795, + "placementX" : 321.336, + "placementY" : 133.795, + "placementWidth" : 104.986, + "placementHeight" : 19.351, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 104.986, + "contentHeight" : 19.351, + "margin" : { + "top" : 7.34, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ObsidianInvoice[0]/InfoCardsRow[5]/PaymentCard[1]/PaymentFields[2]", + "entityName" : "PaymentFields", + "entityKind" : "SectionNode", + "parentPath" : "ObsidianInvoice[0]/InfoCardsRow[5]/PaymentCard[1]", + "childIndex" : 2, + "depth" : 4, + "layer" : 4, + "computedX" : 321.336, + "computedY" : 77.698, + "placementX" : 321.336, + "placementY" : 77.698, + "placementWidth" : 233.314, + "placementHeight" : 46.996, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 233.314, + "contentHeight" : 46.996, + "margin" : { + "top" : 9.101, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ObsidianInvoice[0]/InfoCardsRow[5]/PaymentCard[1]/PaymentFields[2]/PaymentField_0Layer[0]", + "entityName" : "PaymentField_0Layer", + "entityKind" : "LayerStackNode", + "parentPath" : "ObsidianInvoice[0]/InfoCardsRow[5]/PaymentCard[1]/PaymentFields[2]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 321.336, + "computedY" : 116.123, + "placementX" : 321.336, + "placementY" : 116.123, + "placementWidth" : 233.314, + "placementHeight" : 8.571, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 233.314, + "contentHeight" : 8.571, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ObsidianInvoice[0]/InfoCardsRow[5]/PaymentCard[1]/PaymentFields[2]/PaymentField_0Layer[0]/PaymentField_0Holder[0]", + "entityName" : "PaymentField_0Holder", + "entityKind" : "SectionNode", + "parentPath" : "ObsidianInvoice[0]/InfoCardsRow[5]/PaymentCard[1]/PaymentFields[2]/PaymentField_0Layer[0]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 321.336, + "computedY" : 116.123, + "placementX" : 321.336, + "placementY" : 116.123, + "placementWidth" : 233.314, + "placementHeight" : 8.571, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 233.314, + "contentHeight" : 8.571, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ObsidianInvoice[0]/InfoCardsRow[5]/PaymentCard[1]/PaymentFields[2]/PaymentField_0Layer[0]/PaymentField_0Holder[0]/PaymentField_0[0]", + "entityName" : "PaymentField_0", + "entityKind" : "RowNode", + "parentPath" : "ObsidianInvoice[0]/InfoCardsRow[5]/PaymentCard[1]/PaymentFields[2]/PaymentField_0Layer[0]/PaymentField_0Holder[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 321.336, + "computedY" : 116.123, + "placementX" : 321.336, + "placementY" : 116.123, + "placementWidth" : 233.314, + "placementHeight" : 8.571, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 233.314, + "contentHeight" : 8.571, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ObsidianInvoice[0]/InfoCardsRow[5]/PaymentCard[1]/PaymentFields[2]/PaymentField_0Layer[0]/PaymentField_0Holder[0]/PaymentField_0[0]/PaymentField_0Label[0]", + "entityName" : "PaymentField_0Label", + "entityKind" : "ParagraphNode", + "parentPath" : "ObsidianInvoice[0]/InfoCardsRow[5]/PaymentCard[1]/PaymentFields[2]/PaymentField_0Layer[0]/PaymentField_0Holder[0]/PaymentField_0[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 321.336, + "computedY" : 116.51, + "placementX" : 321.336, + "placementY" : 116.51, + "placementWidth" : 22.857, + "placementHeight" : 8.184, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 22.857, + "contentHeight" : 8.184, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ObsidianInvoice[0]/InfoCardsRow[5]/PaymentCard[1]/PaymentFields[2]/PaymentField_0Layer[0]/PaymentField_0Holder[0]/PaymentField_0[0]/PaymentField_0Value[1]", + "entityName" : "PaymentField_0Value", + "entityKind" : "ParagraphNode", + "parentPath" : "ObsidianInvoice[0]/InfoCardsRow[5]/PaymentCard[1]/PaymentFields[2]/PaymentField_0Layer[0]/PaymentField_0Holder[0]/PaymentField_0[0]", + "childIndex" : 1, + "depth" : 8, + "layer" : 8, + "computedX" : 405.972, + "computedY" : 116.123, + "placementX" : 405.972, + "placementY" : 116.123, + "placementWidth" : 64.461, + "placementHeight" : 8.571, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 64.461, + "contentHeight" : 8.571, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ObsidianInvoice[0]/InfoCardsRow[5]/PaymentCard[1]/PaymentFields[2]/PaymentField_1Layer[1]", + "entityName" : "PaymentField_1Layer", + "entityKind" : "LayerStackNode", + "parentPath" : "ObsidianInvoice[0]/InfoCardsRow[5]/PaymentCard[1]/PaymentFields[2]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 321.336, + "computedY" : 103.315, + "placementX" : 321.336, + "placementY" : 103.315, + "placementWidth" : 233.314, + "placementHeight" : 8.571, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 233.314, + "contentHeight" : 8.571, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ObsidianInvoice[0]/InfoCardsRow[5]/PaymentCard[1]/PaymentFields[2]/PaymentField_1Layer[1]/PaymentField_1Holder[0]", + "entityName" : "PaymentField_1Holder", + "entityKind" : "SectionNode", + "parentPath" : "ObsidianInvoice[0]/InfoCardsRow[5]/PaymentCard[1]/PaymentFields[2]/PaymentField_1Layer[1]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 321.336, + "computedY" : 103.315, + "placementX" : 321.336, + "placementY" : 103.315, + "placementWidth" : 233.314, + "placementHeight" : 8.571, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 233.314, + "contentHeight" : 8.571, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ObsidianInvoice[0]/InfoCardsRow[5]/PaymentCard[1]/PaymentFields[2]/PaymentField_1Layer[1]/PaymentField_1Holder[0]/PaymentField_1[0]", + "entityName" : "PaymentField_1", + "entityKind" : "RowNode", + "parentPath" : "ObsidianInvoice[0]/InfoCardsRow[5]/PaymentCard[1]/PaymentFields[2]/PaymentField_1Layer[1]/PaymentField_1Holder[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 321.336, + "computedY" : 103.315, + "placementX" : 321.336, + "placementY" : 103.315, + "placementWidth" : 233.314, + "placementHeight" : 8.571, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 233.314, + "contentHeight" : 8.571, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ObsidianInvoice[0]/InfoCardsRow[5]/PaymentCard[1]/PaymentFields[2]/PaymentField_1Layer[1]/PaymentField_1Holder[0]/PaymentField_1[0]/PaymentField_1Label[0]", + "entityName" : "PaymentField_1Label", + "entityKind" : "ParagraphNode", + "parentPath" : "ObsidianInvoice[0]/InfoCardsRow[5]/PaymentCard[1]/PaymentFields[2]/PaymentField_1Layer[1]/PaymentField_1Holder[0]/PaymentField_1[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 321.336, + "computedY" : 103.702, + "placementX" : 321.336, + "placementY" : 103.702, + "placementWidth" : 35.411, + "placementHeight" : 8.184, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 35.411, + "contentHeight" : 8.184, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ObsidianInvoice[0]/InfoCardsRow[5]/PaymentCard[1]/PaymentFields[2]/PaymentField_1Layer[1]/PaymentField_1Holder[0]/PaymentField_1[0]/PaymentField_1Value[1]", + "entityName" : "PaymentField_1Value", + "entityKind" : "ParagraphNode", + "parentPath" : "ObsidianInvoice[0]/InfoCardsRow[5]/PaymentCard[1]/PaymentFields[2]/PaymentField_1Layer[1]/PaymentField_1Holder[0]/PaymentField_1[0]", + "childIndex" : 1, + "depth" : 8, + "layer" : 8, + "computedX" : 405.972, + "computedY" : 103.315, + "placementX" : 405.972, + "placementY" : 103.315, + "placementWidth" : 38.123, + "placementHeight" : 8.571, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 38.123, + "contentHeight" : 8.571, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ObsidianInvoice[0]/InfoCardsRow[5]/PaymentCard[1]/PaymentFields[2]/PaymentField_2Layer[2]", + "entityName" : "PaymentField_2Layer", + "entityKind" : "LayerStackNode", + "parentPath" : "ObsidianInvoice[0]/InfoCardsRow[5]/PaymentCard[1]/PaymentFields[2]", + "childIndex" : 2, + "depth" : 5, + "layer" : 5, + "computedX" : 321.336, + "computedY" : 90.506, + "placementX" : 321.336, + "placementY" : 90.506, + "placementWidth" : 233.314, + "placementHeight" : 8.571, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 233.314, + "contentHeight" : 8.571, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ObsidianInvoice[0]/InfoCardsRow[5]/PaymentCard[1]/PaymentFields[2]/PaymentField_2Layer[2]/PaymentField_2Holder[0]", + "entityName" : "PaymentField_2Holder", + "entityKind" : "SectionNode", + "parentPath" : "ObsidianInvoice[0]/InfoCardsRow[5]/PaymentCard[1]/PaymentFields[2]/PaymentField_2Layer[2]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 321.336, + "computedY" : 90.506, + "placementX" : 321.336, + "placementY" : 90.506, + "placementWidth" : 233.314, + "placementHeight" : 8.571, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 233.314, + "contentHeight" : 8.571, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ObsidianInvoice[0]/InfoCardsRow[5]/PaymentCard[1]/PaymentFields[2]/PaymentField_2Layer[2]/PaymentField_2Holder[0]/PaymentField_2[0]", + "entityName" : "PaymentField_2", + "entityKind" : "RowNode", + "parentPath" : "ObsidianInvoice[0]/InfoCardsRow[5]/PaymentCard[1]/PaymentFields[2]/PaymentField_2Layer[2]/PaymentField_2Holder[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 321.336, + "computedY" : 90.506, + "placementX" : 321.336, + "placementY" : 90.506, + "placementWidth" : 233.314, + "placementHeight" : 8.571, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 233.314, + "contentHeight" : 8.571, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ObsidianInvoice[0]/InfoCardsRow[5]/PaymentCard[1]/PaymentFields[2]/PaymentField_2Layer[2]/PaymentField_2Holder[0]/PaymentField_2[0]/PaymentField_2Label[0]", + "entityName" : "PaymentField_2Label", + "entityKind" : "ParagraphNode", + "parentPath" : "ObsidianInvoice[0]/InfoCardsRow[5]/PaymentCard[1]/PaymentFields[2]/PaymentField_2Layer[2]/PaymentField_2Holder[0]/PaymentField_2[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 321.336, + "computedY" : 90.893, + "placementX" : 321.336, + "placementY" : 90.893, + "placementWidth" : 60.29, + "placementHeight" : 8.184, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 60.29, + "contentHeight" : 8.184, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ObsidianInvoice[0]/InfoCardsRow[5]/PaymentCard[1]/PaymentFields[2]/PaymentField_2Layer[2]/PaymentField_2Holder[0]/PaymentField_2[0]/PaymentField_2Value[1]", + "entityName" : "PaymentField_2Value", + "entityKind" : "ParagraphNode", + "parentPath" : "ObsidianInvoice[0]/InfoCardsRow[5]/PaymentCard[1]/PaymentFields[2]/PaymentField_2Layer[2]/PaymentField_2Holder[0]/PaymentField_2[0]", + "childIndex" : 1, + "depth" : 8, + "layer" : 8, + "computedX" : 405.972, + "computedY" : 90.506, + "placementX" : 405.972, + "placementY" : 90.506, + "placementWidth" : 38.123, + "placementHeight" : 8.571, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 38.123, + "contentHeight" : 8.571, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ObsidianInvoice[0]/InfoCardsRow[5]/PaymentCard[1]/PaymentFields[2]/PaymentField_3Layer[3]", + "entityName" : "PaymentField_3Layer", + "entityKind" : "LayerStackNode", + "parentPath" : "ObsidianInvoice[0]/InfoCardsRow[5]/PaymentCard[1]/PaymentFields[2]", + "childIndex" : 3, + "depth" : 5, + "layer" : 5, + "computedX" : 321.336, + "computedY" : 77.698, + "placementX" : 321.336, + "placementY" : 77.698, + "placementWidth" : 233.314, + "placementHeight" : 8.571, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 233.314, + "contentHeight" : 8.571, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ObsidianInvoice[0]/InfoCardsRow[5]/PaymentCard[1]/PaymentFields[2]/PaymentField_3Layer[3]/PaymentField_3Holder[0]", + "entityName" : "PaymentField_3Holder", + "entityKind" : "SectionNode", + "parentPath" : "ObsidianInvoice[0]/InfoCardsRow[5]/PaymentCard[1]/PaymentFields[2]/PaymentField_3Layer[3]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 321.336, + "computedY" : 77.698, + "placementX" : 321.336, + "placementY" : 77.698, + "placementWidth" : 233.314, + "placementHeight" : 8.571, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 233.314, + "contentHeight" : 8.571, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ObsidianInvoice[0]/InfoCardsRow[5]/PaymentCard[1]/PaymentFields[2]/PaymentField_3Layer[3]/PaymentField_3Holder[0]/PaymentField_3[0]", + "entityName" : "PaymentField_3", + "entityKind" : "RowNode", + "parentPath" : "ObsidianInvoice[0]/InfoCardsRow[5]/PaymentCard[1]/PaymentFields[2]/PaymentField_3Layer[3]/PaymentField_3Holder[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 321.336, + "computedY" : 77.698, + "placementX" : 321.336, + "placementY" : 77.698, + "placementWidth" : 233.314, + "placementHeight" : 8.571, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 233.314, + "contentHeight" : 8.571, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ObsidianInvoice[0]/InfoCardsRow[5]/PaymentCard[1]/PaymentFields[2]/PaymentField_3Layer[3]/PaymentField_3Holder[0]/PaymentField_3[0]/PaymentField_3Label[0]", + "entityName" : "PaymentField_3Label", + "entityKind" : "ParagraphNode", + "parentPath" : "ObsidianInvoice[0]/InfoCardsRow[5]/PaymentCard[1]/PaymentFields[2]/PaymentField_3Layer[3]/PaymentField_3Holder[0]/PaymentField_3[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 321.336, + "computedY" : 78.085, + "placementX" : 321.336, + "placementY" : 78.085, + "placementWidth" : 37.932, + "placementHeight" : 8.184, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 37.932, + "contentHeight" : 8.184, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ObsidianInvoice[0]/InfoCardsRow[5]/PaymentCard[1]/PaymentFields[2]/PaymentField_3Layer[3]/PaymentField_3Holder[0]/PaymentField_3[0]/PaymentField_3Value[1]", + "entityName" : "PaymentField_3Value", + "entityKind" : "ParagraphNode", + "parentPath" : "ObsidianInvoice[0]/InfoCardsRow[5]/PaymentCard[1]/PaymentFields[2]/PaymentField_3Layer[3]/PaymentField_3Holder[0]/PaymentField_3[0]", + "childIndex" : 1, + "depth" : 8, + "layer" : 8, + "computedX" : 405.972, + "computedY" : 77.698, + "placementX" : 405.972, + "placementY" : 77.698, + "placementWidth" : 61.247, + "placementHeight" : 8.571, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 61.247, + "contentHeight" : 8.571, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ObsidianInvoice[0]/FooterDivider[6]", + "entityName" : "FooterDivider", + "entityKind" : "LineNode", + "parentPath" : "ObsidianInvoice[0]", + "childIndex" : 6, + "depth" : 2, + "layer" : 2, + "computedX" : 25.955, + "computedY" : 51.409, + "placementX" : 25.955, + "placementY" : 51.409, + "placementWidth" : 542.237, + "placementHeight" : 1.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 542.237, + "contentHeight" : 1.0, + "margin" : { + "top" : 12.978, + "right" : 0.0, + "bottom" : 9.438, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ObsidianInvoice[0]/ClosingBand[7]", + "entityName" : "ClosingBand", + "entityKind" : "RowNode", + "parentPath" : "ObsidianInvoice[0]", + "childIndex" : 7, + "depth" : 2, + "layer" : 2, + "computedX" : 25.955, + "computedY" : 16.015, + "placementX" : 25.955, + "placementY" : 16.015, + "placementWidth" : 542.237, + "placementHeight" : 25.955, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 542.237, + "contentHeight" : 25.955, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 3.385, + "bottom" : 0.0, + "left" : 2.821 + } + }, { + "path" : "ObsidianInvoice[0]/ClosingBand[7]/ClosingDisc[0]", + "entityName" : "ClosingDisc", + "entityKind" : "SectionNode", + "parentPath" : "ObsidianInvoice[0]/ClosingBand[7]", + "childIndex" : 0, + "depth" : 3, + "layer" : 3, + "computedX" : 28.776, + "computedY" : 16.015, + "placementX" : 28.776, + "placementY" : 16.015, + "placementWidth" : 25.955, + "placementHeight" : 25.955, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 25.955, + "contentHeight" : 25.955, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ObsidianInvoice[0]/ClosingBand[7]/ClosingDisc[0]/ClosingDiscShape[0]", + "entityName" : "ClosingDiscShape", + "entityKind" : "ShapeContainerNode", + "parentPath" : "ObsidianInvoice[0]/ClosingBand[7]/ClosingDisc[0]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 28.776, + "computedY" : 16.015, + "placementX" : 28.776, + "placementY" : 16.015, + "placementWidth" : 25.955, + "placementHeight" : 25.955, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 25.955, + "contentHeight" : 25.955, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ObsidianInvoice[0]/ClosingBand[7]/ClosingDisc[0]/ClosingDiscShape[0]/ClosingDiscInitials[0]", + "entityName" : "ClosingDiscInitials", + "entityKind" : "ParagraphNode", + "parentPath" : "ObsidianInvoice[0]/ClosingBand[7]/ClosingDisc[0]/ClosingDiscShape[0]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 28.776, + "computedY" : 24.79, + "placementX" : 28.776, + "placementY" : 24.79, + "placementWidth" : 25.955, + "placementHeight" : 8.405, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 25.955, + "contentHeight" : 8.405, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ObsidianInvoice[0]/ClosingBand[7]/ClosingText[1]", + "entityName" : "ClosingText", + "entityKind" : "SectionNode", + "parentPath" : "ObsidianInvoice[0]/ClosingBand[7]", + "childIndex" : 1, + "depth" : 3, + "layer" : 3, + "computedX" : 69.966, + "computedY" : 16.015, + "placementX" : 69.966, + "placementY" : 16.015, + "placementWidth" : 152.247, + "placementHeight" : 22.511, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 152.247, + "contentHeight" : 22.511, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ObsidianInvoice[0]/ClosingBand[7]/ClosingText[1]/ClosingSignOff[0]", + "entityName" : "ClosingSignOff", + "entityKind" : "ParagraphNode", + "parentPath" : "ObsidianInvoice[0]/ClosingBand[7]/ClosingText[1]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 69.966, + "computedY" : 30.121, + "placementX" : 69.966, + "placementY" : 30.121, + "placementWidth" : 114.778, + "placementHeight" : 8.405, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 114.778, + "contentHeight" : 8.405, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ObsidianInvoice[0]/ClosingBand[7]/ClosingText[1]/ClosingDue[1]", + "entityName" : "ClosingDue", + "entityKind" : "ParagraphNode", + "parentPath" : "ObsidianInvoice[0]/ClosingBand[7]/ClosingText[1]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 69.966, + "computedY" : 16.015, + "placementX" : 69.966, + "placementY" : 16.015, + "placementWidth" : 152.247, + "placementHeight" : 8.405, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 152.247, + "contentHeight" : 8.405, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ObsidianInvoice[0]/ClosingBand[7]/ClosingContact[2]", + "entityName" : "ClosingContact", + "entityKind" : "SectionNode", + "parentPath" : "ObsidianInvoice[0]/ClosingBand[7]", + "childIndex" : 2, + "depth" : 3, + "layer" : 3, + "computedX" : 474.528, + "computedY" : 16.015, + "placementX" : 474.528, + "placementY" : 16.015, + "placementWidth" : 90.279, + "placementHeight" : 23.009, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 90.279, + "contentHeight" : 23.009, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ObsidianInvoice[0]/ClosingBand[7]/ClosingContact[2]/ClosingName[0]", + "entityName" : "ClosingName", + "entityKind" : "ParagraphNode", + "parentPath" : "ObsidianInvoice[0]/ClosingBand[7]/ClosingContact[2]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 474.528, + "computedY" : 30.121, + "placementX" : 474.528, + "placementY" : 30.121, + "placementWidth" : 90.279, + "placementHeight" : 8.903, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 90.279, + "contentHeight" : 8.903, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ObsidianInvoice[0]/ClosingBand[7]/ClosingContact[2]/ClosingContact[1]", + "entityName" : "ClosingContact", + "entityKind" : "ParagraphNode", + "parentPath" : "ObsidianInvoice[0]/ClosingBand[7]/ClosingContact[2]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 474.528, + "computedY" : 16.015, + "placementX" : 474.528, + "placementY" : 16.015, + "placementWidth" : 90.279, + "placementHeight" : 8.903, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 90.279, + "contentHeight" : 8.903, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + } ] +} diff --git a/qa/src/test/resources/layout-snapshots/canonical-templates/invoice/payments_invoice_layout.json b/qa/src/test/resources/layout-snapshots/canonical-templates/invoice/payments_invoice_layout.json new file mode 100644 index 000000000..0e7557f0a --- /dev/null +++ b/qa/src/test/resources/layout-snapshots/canonical-templates/invoice/payments_invoice_layout.json @@ -0,0 +1,5357 @@ +{ + "formatVersion" : "2.0", + "canvas" : { + "pageWidth" : 595.276, + "pageHeight" : 841.89, + "innerWidth" : 537.159, + "innerHeight" : 808.035, + "margin" : { + "top" : 15.799, + "right" : 29.341, + "bottom" : 18.056, + "left" : 28.776 + } + }, + "totalPages" : 1, + "nodes" : [ { + "path" : "PaymentsInvoice[0]", + "entityName" : "PaymentsInvoice", + "entityKind" : "ContainerNode", + "parentPath" : null, + "childIndex" : 0, + "depth" : 1, + "layer" : 1, + "computedX" : 28.776, + "computedY" : 34.229, + "placementX" : 28.776, + "placementY" : 34.229, + "placementWidth" : 537.159, + "placementHeight" : 791.862, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 537.159, + "contentHeight" : 791.862, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/MastheadStack[0]", + "entityName" : "MastheadStack", + "entityKind" : "LayerStackNode", + "parentPath" : "PaymentsInvoice[0]", + "childIndex" : 0, + "depth" : 2, + "layer" : 2, + "computedX" : 28.776, + "computedY" : 622.964, + "placementX" : 28.776, + "placementY" : 622.964, + "placementWidth" : 537.159, + "placementHeight" : 203.127, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 537.159, + "contentHeight" : 203.127, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/MastheadStack[0]/HeaderBand[0]", + "entityName" : "HeaderBand", + "entityKind" : "SectionNode", + "parentPath" : "PaymentsInvoice[0]/MastheadStack[0]", + "childIndex" : 0, + "depth" : 3, + "layer" : 3, + "computedX" : 217.459, + "computedY" : 638.763, + "placementX" : 217.459, + "placementY" : 638.763, + "placementWidth" : 96.26, + "placementHeight" : 203.127, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 96.26, + "contentHeight" : 203.127, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/MastheadStack[0]/HeaderBand[0]/HeaderBandLayers[0]", + "entityName" : "HeaderBandLayers", + "entityKind" : "LayerStackNode", + "parentPath" : "PaymentsInvoice[0]/MastheadStack[0]/HeaderBand[0]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 217.459, + "computedY" : 638.763, + "placementX" : 217.459, + "placementY" : 638.763, + "placementWidth" : 96.26, + "placementHeight" : 203.127, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 96.26, + "contentHeight" : 203.127, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/MastheadStack[0]/HeaderBand[0]/HeaderBandLayers[0]/BandLavender[0]", + "entityName" : "BandLavender", + "entityKind" : "Path", + "parentPath" : "PaymentsInvoice[0]/MastheadStack[0]/HeaderBand[0]/HeaderBandLayers[0]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 217.459, + "computedY" : 638.763, + "placementX" : 217.459, + "placementY" : 638.763, + "placementWidth" : 96.26, + "placementHeight" : 203.127, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 96.26, + "contentHeight" : 203.127, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/MastheadStack[0]/HeaderBand[0]/HeaderBandLayers[0]/BandNavy[1]", + "entityName" : "BandNavy", + "entityKind" : "Path", + "parentPath" : "PaymentsInvoice[0]/MastheadStack[0]/HeaderBand[0]/HeaderBandLayers[0]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 217.459, + "computedY" : 638.763, + "placementX" : 217.459, + "placementY" : 638.763, + "placementWidth" : 96.26, + "placementHeight" : 203.127, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 96.26, + "contentHeight" : 203.127, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/MastheadStack[0]/AccentBarHolder[1]", + "entityName" : "AccentBarHolder", + "entityKind" : "SectionNode", + "parentPath" : "PaymentsInvoice[0]/MastheadStack[0]", + "childIndex" : 1, + "depth" : 3, + "layer" : 3, + "computedX" : 16.363, + "computedY" : 651.176, + "placementX" : 16.363, + "placementY" : 651.176, + "placementWidth" : 1.128, + "placementHeight" : 174.915, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 1.128, + "contentHeight" : 174.915, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/MastheadStack[0]/AccentBarHolder[1]/HeaderAccentBar[0]", + "entityName" : "HeaderAccentBar", + "entityKind" : "LineNode", + "parentPath" : "PaymentsInvoice[0]/MastheadStack[0]/AccentBarHolder[1]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 16.363, + "computedY" : 651.176, + "placementX" : 16.363, + "placementY" : 651.176, + "placementWidth" : 1.128, + "placementHeight" : 174.915, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 1.128, + "contentHeight" : 174.915, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/MastheadStack[0]/HeaderContent[2]", + "entityName" : "HeaderContent", + "entityKind" : "SectionNode", + "parentPath" : "PaymentsInvoice[0]/MastheadStack[0]", + "childIndex" : 2, + "depth" : 3, + "layer" : 3, + "computedX" : 28.776, + "computedY" : 646.402, + "placementX" : 28.776, + "placementY" : 646.402, + "placementWidth" : 537.159, + "placementHeight" : 179.689, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 537.159, + "contentHeight" : 179.689, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/MastheadStack[0]/HeaderContent[2]/Masthead[0]", + "entityName" : "Masthead", + "entityKind" : "RowNode", + "parentPath" : "PaymentsInvoice[0]/MastheadStack[0]/HeaderContent[2]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 28.776, + "computedY" : 770.962, + "placementX" : 28.776, + "placementY" : 770.962, + "placementWidth" : 537.159, + "placementHeight" : 44.973, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 537.159, + "contentHeight" : 44.973, + "margin" : { + "top" : 10.156, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/MastheadStack[0]/HeaderContent[2]/Masthead[0]/BrandLockup[0]", + "entityName" : "BrandLockup", + "entityKind" : "SectionNode", + "parentPath" : "PaymentsInvoice[0]/MastheadStack[0]/HeaderContent[2]/Masthead[0]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 28.776, + "computedY" : 789.713, + "placementX" : 28.776, + "placementY" : 789.713, + "placementWidth" : 94.178, + "placementHeight" : 26.222, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 94.178, + "contentHeight" : 26.222, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 6.771 + } + }, { + "path" : "PaymentsInvoice[0]/MastheadStack[0]/HeaderContent[2]/Masthead[0]/BrandLockup[0]/BrandWordmark[0]", + "entityName" : "BrandWordmark", + "entityKind" : "ParagraphNode", + "parentPath" : "PaymentsInvoice[0]/MastheadStack[0]/HeaderContent[2]/Masthead[0]/BrandLockup[0]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 35.547, + "computedY" : 789.713, + "placementX" : 35.547, + "placementY" : 789.713, + "placementWidth" : 87.407, + "placementHeight" : 26.222, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 87.407, + "contentHeight" : 26.222, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/MastheadStack[0]/HeaderContent[2]/Masthead[0]/InvoiceTitle[1]", + "entityName" : "InvoiceTitle", + "entityKind" : "ParagraphNode", + "parentPath" : "PaymentsInvoice[0]/MastheadStack[0]/HeaderContent[2]/Masthead[0]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 360.551, + "computedY" : 770.962, + "placementX" : 360.551, + "placementY" : 770.962, + "placementWidth" : 149.064, + "placementHeight" : 43.575, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 149.064, + "contentHeight" : 43.575, + "margin" : { + "top" : 1.397, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/MastheadStack[0]/HeaderContent[2]/HeaderSplit[1]", + "entityName" : "HeaderSplit", + "entityKind" : "RowNode", + "parentPath" : "PaymentsInvoice[0]/MastheadStack[0]/HeaderContent[2]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 28.776, + "computedY" : 646.402, + "placementX" : 28.776, + "placementY" : 646.402, + "placementWidth" : 537.159, + "placementHeight" : 119.2, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 537.159, + "contentHeight" : 119.2, + "margin" : { + "top" : 5.36, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/MastheadStack[0]/HeaderContent[2]/HeaderSplit[1]/IssuerBlock[0]", + "entityName" : "IssuerBlock", + "entityKind" : "SectionNode", + "parentPath" : "PaymentsInvoice[0]/MastheadStack[0]/HeaderContent[2]/HeaderSplit[1]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 28.776, + "computedY" : 664.414, + "placementX" : 28.776, + "placementY" : 664.414, + "placementWidth" : 127.249, + "placementHeight" : 101.188, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 127.249, + "contentHeight" : 101.188, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 6.207 + } + }, { + "path" : "PaymentsInvoice[0]/MastheadStack[0]/HeaderContent[2]/HeaderSplit[1]/IssuerBlock[0]/IssuerName[0]", + "entityName" : "IssuerName", + "entityKind" : "ParagraphNode", + "parentPath" : "PaymentsInvoice[0]/MastheadStack[0]/HeaderContent[2]/HeaderSplit[1]/IssuerBlock[0]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 34.983, + "computedY" : 751.882, + "placementX" : 34.983, + "placementY" : 751.882, + "placementWidth" : 121.042, + "placementHeight" : 13.72, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 121.042, + "contentHeight" : 13.72, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/MastheadStack[0]/HeaderContent[2]/HeaderSplit[1]/IssuerBlock[0]/IssuerAddress[1]", + "entityName" : "IssuerAddress", + "entityKind" : "SectionNode", + "parentPath" : "PaymentsInvoice[0]/MastheadStack[0]/HeaderContent[2]/HeaderSplit[1]/IssuerBlock[0]", + "childIndex" : 1, + "depth" : 6, + "layer" : 6, + "computedX" : 34.983, + "computedY" : 700.333, + "placementX" : 34.983, + "placementY" : 700.333, + "placementWidth" : 78.364, + "placementHeight" : 46.576, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 78.364, + "contentHeight" : 46.576, + "margin" : { + "top" : 4.972, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/MastheadStack[0]/HeaderContent[2]/HeaderSplit[1]/IssuerBlock[0]/IssuerAddress[1]/IssuerAddressLine1[0]", + "entityName" : "IssuerAddressLine1", + "entityKind" : "ParagraphNode", + "parentPath" : "PaymentsInvoice[0]/MastheadStack[0]/HeaderContent[2]/HeaderSplit[1]/IssuerBlock[0]/IssuerAddress[1]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 34.983, + "computedY" : 736.439, + "placementX" : 34.983, + "placementY" : 736.439, + "placementWidth" : 78.364, + "placementHeight" : 10.471, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 78.364, + "contentHeight" : 10.471, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/MastheadStack[0]/HeaderContent[2]/HeaderSplit[1]/IssuerBlock[0]/IssuerAddress[1]/IssuerAddressLine2[1]", + "entityName" : "IssuerAddressLine2", + "entityKind" : "ParagraphNode", + "parentPath" : "PaymentsInvoice[0]/MastheadStack[0]/HeaderContent[2]/HeaderSplit[1]/IssuerBlock[0]/IssuerAddress[1]", + "childIndex" : 1, + "depth" : 7, + "layer" : 7, + "computedX" : 34.983, + "computedY" : 724.404, + "placementX" : 34.983, + "placementY" : 724.404, + "placementWidth" : 53.766, + "placementHeight" : 10.471, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 53.766, + "contentHeight" : 10.471, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/MastheadStack[0]/HeaderContent[2]/HeaderSplit[1]/IssuerBlock[0]/IssuerAddress[1]/IssuerAddressLine3[2]", + "entityName" : "IssuerAddressLine3", + "entityKind" : "ParagraphNode", + "parentPath" : "PaymentsInvoice[0]/MastheadStack[0]/HeaderContent[2]/HeaderSplit[1]/IssuerBlock[0]/IssuerAddress[1]", + "childIndex" : 2, + "depth" : 7, + "layer" : 7, + "computedX" : 34.983, + "computedY" : 712.369, + "placementX" : 34.983, + "placementY" : 712.369, + "placementWidth" : 65.118, + "placementHeight" : 10.471, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 65.118, + "contentHeight" : 10.471, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/MastheadStack[0]/HeaderContent[2]/HeaderSplit[1]/IssuerBlock[0]/IssuerAddress[1]/IssuerAddressLine4[3]", + "entityName" : "IssuerAddressLine4", + "entityKind" : "ParagraphNode", + "parentPath" : "PaymentsInvoice[0]/MastheadStack[0]/HeaderContent[2]/HeaderSplit[1]/IssuerBlock[0]/IssuerAddress[1]", + "childIndex" : 3, + "depth" : 7, + "layer" : 7, + "computedX" : 34.983, + "computedY" : 700.333, + "placementX" : 34.983, + "placementY" : 700.333, + "placementWidth" : 62.544, + "placementHeight" : 10.471, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 62.544, + "contentHeight" : 10.471, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/MastheadStack[0]/HeaderContent[2]/HeaderSplit[1]/IssuerBlock[0]/IssuerRegistration[2]", + "entityName" : "IssuerRegistration", + "entityKind" : "SectionNode", + "parentPath" : "PaymentsInvoice[0]/MastheadStack[0]/HeaderContent[2]/HeaderSplit[1]/IssuerBlock[0]", + "childIndex" : 2, + "depth" : 6, + "layer" : 6, + "computedX" : 34.983, + "computedY" : 664.414, + "placementX" : 34.983, + "placementY" : 664.414, + "placementWidth" : 99.383, + "placementHeight" : 22.32, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 99.383, + "contentHeight" : 22.32, + "margin" : { + "top" : 13.6, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/MastheadStack[0]/HeaderContent[2]/HeaderSplit[1]/IssuerBlock[0]/IssuerRegistration[2]/IssuerRegistrationLine1[0]", + "entityName" : "IssuerRegistrationLine1", + "entityKind" : "ParagraphNode", + "parentPath" : "PaymentsInvoice[0]/MastheadStack[0]/HeaderContent[2]/HeaderSplit[1]/IssuerBlock[0]/IssuerRegistration[2]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 34.983, + "computedY" : 676.263, + "placementX" : 34.983, + "placementY" : 676.263, + "placementWidth" : 95.186, + "placementHeight" : 10.471, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 95.186, + "contentHeight" : 10.471, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/MastheadStack[0]/HeaderContent[2]/HeaderSplit[1]/IssuerBlock[0]/IssuerRegistration[2]/IssuerRegistrationLine2[1]", + "entityName" : "IssuerRegistrationLine2", + "entityKind" : "ParagraphNode", + "parentPath" : "PaymentsInvoice[0]/MastheadStack[0]/HeaderContent[2]/HeaderSplit[1]/IssuerBlock[0]/IssuerRegistration[2]", + "childIndex" : 1, + "depth" : 7, + "layer" : 7, + "computedX" : 34.983, + "computedY" : 664.414, + "placementX" : 34.983, + "placementY" : 664.414, + "placementWidth" : 99.383, + "placementHeight" : 10.471, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 99.383, + "contentHeight" : 10.471, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/MastheadStack[0]/HeaderContent[2]/HeaderSplit[1]/InvoiceMeta[1]", + "entityName" : "InvoiceMeta", + "entityKind" : "SectionNode", + "parentPath" : "PaymentsInvoice[0]/MastheadStack[0]/HeaderContent[2]/HeaderSplit[1]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 360.551, + "computedY" : 646.402, + "placementX" : 360.551, + "placementY" : 646.402, + "placementWidth" : 205.384, + "placementHeight" : 119.2, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 205.384, + "contentHeight" : 119.2, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 6.207, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/MastheadStack[0]/HeaderContent[2]/HeaderSplit[1]/InvoiceMeta[1]/MetaGridLayer[0]", + "entityName" : "MetaGridLayer", + "entityKind" : "LayerStackNode", + "parentPath" : "PaymentsInvoice[0]/MastheadStack[0]/HeaderContent[2]/HeaderSplit[1]/InvoiceMeta[1]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 360.551, + "computedY" : 646.402, + "placementX" : 360.551, + "placementY" : 646.402, + "placementWidth" : 205.384, + "placementHeight" : 112.993, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 205.384, + "contentHeight" : 112.993, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/MastheadStack[0]/HeaderContent[2]/HeaderSplit[1]/InvoiceMeta[1]/MetaGridLayer[0]/MetaGridHolder[0]", + "entityName" : "MetaGridHolder", + "entityKind" : "SectionNode", + "parentPath" : "PaymentsInvoice[0]/MastheadStack[0]/HeaderContent[2]/HeaderSplit[1]/InvoiceMeta[1]/MetaGridLayer[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 360.551, + "computedY" : 646.402, + "placementX" : 360.551, + "placementY" : 646.402, + "placementWidth" : 205.384, + "placementHeight" : 112.993, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 205.384, + "contentHeight" : 112.993, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/MastheadStack[0]/HeaderContent[2]/HeaderSplit[1]/InvoiceMeta[1]/MetaGridLayer[0]/MetaGridHolder[0]/MetaGrid[0]", + "entityName" : "MetaGrid", + "entityKind" : "RowNode", + "parentPath" : "PaymentsInvoice[0]/MastheadStack[0]/HeaderContent[2]/HeaderSplit[1]/InvoiceMeta[1]/MetaGridLayer[0]/MetaGridHolder[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 360.551, + "computedY" : 646.402, + "placementX" : 360.551, + "placementY" : 646.402, + "placementWidth" : 205.384, + "placementHeight" : 112.993, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 205.384, + "contentHeight" : 112.993, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/MastheadStack[0]/HeaderContent[2]/HeaderSplit[1]/InvoiceMeta[1]/MetaGridLayer[0]/MetaGridHolder[0]/MetaGrid[0]/MetaLabels[0]", + "entityName" : "MetaLabels", + "entityKind" : "SectionNode", + "parentPath" : "PaymentsInvoice[0]/MastheadStack[0]/HeaderContent[2]/HeaderSplit[1]/InvoiceMeta[1]/MetaGridLayer[0]/MetaGridHolder[0]/MetaGrid[0]", + "childIndex" : 0, + "depth" : 9, + "layer" : 9, + "computedX" : 360.551, + "computedY" : 646.402, + "placementX" : 360.551, + "placementY" : 646.402, + "placementWidth" : 64.446, + "placementHeight" : 112.993, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 64.446, + "contentHeight" : 112.993, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/MastheadStack[0]/HeaderContent[2]/HeaderSplit[1]/InvoiceMeta[1]/MetaGridLayer[0]/MetaGridHolder[0]/MetaGrid[0]/MetaLabels[0]/MetaLabel1[0]", + "entityName" : "MetaLabel1", + "entityKind" : "ParagraphNode", + "parentPath" : "PaymentsInvoice[0]/MastheadStack[0]/HeaderContent[2]/HeaderSplit[1]/InvoiceMeta[1]/MetaGridLayer[0]/MetaGridHolder[0]/MetaGrid[0]/MetaLabels[0]", + "childIndex" : 0, + "depth" : 10, + "layer" : 10, + "computedX" : 360.551, + "computedY" : 748.541, + "placementX" : 360.551, + "placementY" : 748.541, + "placementWidth" : 64.446, + "placementHeight" : 10.854, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 64.446, + "contentHeight" : 10.854, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/MastheadStack[0]/HeaderContent[2]/HeaderSplit[1]/InvoiceMeta[1]/MetaGridLayer[0]/MetaGridHolder[0]/MetaGrid[0]/MetaLabels[0]/MetaLabel2[1]", + "entityName" : "MetaLabel2", + "entityKind" : "ParagraphNode", + "parentPath" : "PaymentsInvoice[0]/MastheadStack[0]/HeaderContent[2]/HeaderSplit[1]/InvoiceMeta[1]/MetaGridLayer[0]/MetaGridHolder[0]/MetaGrid[0]/MetaLabels[0]", + "childIndex" : 1, + "depth" : 10, + "layer" : 10, + "computedX" : 360.551, + "computedY" : 731.518, + "placementX" : 360.551, + "placementY" : 731.518, + "placementWidth" : 51.123, + "placementHeight" : 10.854, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 51.123, + "contentHeight" : 10.854, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/MastheadStack[0]/HeaderContent[2]/HeaderSplit[1]/InvoiceMeta[1]/MetaGridLayer[0]/MetaGridHolder[0]/MetaGrid[0]/MetaLabels[0]/MetaLabel3[2]", + "entityName" : "MetaLabel3", + "entityKind" : "ParagraphNode", + "parentPath" : "PaymentsInvoice[0]/MastheadStack[0]/HeaderContent[2]/HeaderSplit[1]/InvoiceMeta[1]/MetaGridLayer[0]/MetaGridHolder[0]/MetaGrid[0]/MetaLabels[0]", + "childIndex" : 2, + "depth" : 10, + "layer" : 10, + "computedX" : 360.551, + "computedY" : 714.495, + "placementX" : 360.551, + "placementY" : 714.495, + "placementWidth" : 53.574, + "placementHeight" : 10.854, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 53.574, + "contentHeight" : 10.854, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/MastheadStack[0]/HeaderContent[2]/HeaderSplit[1]/InvoiceMeta[1]/MetaGridLayer[0]/MetaGridHolder[0]/MetaGrid[0]/MetaLabels[0]/MetaLabel4[3]", + "entityName" : "MetaLabel4", + "entityKind" : "ParagraphNode", + "parentPath" : "PaymentsInvoice[0]/MastheadStack[0]/HeaderContent[2]/HeaderSplit[1]/InvoiceMeta[1]/MetaGridLayer[0]/MetaGridHolder[0]/MetaGrid[0]/MetaLabels[0]", + "childIndex" : 3, + "depth" : 10, + "layer" : 10, + "computedX" : 360.551, + "computedY" : 697.471, + "placementX" : 360.551, + "placementY" : 697.471, + "placementWidth" : 45.542, + "placementHeight" : 10.854, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 45.542, + "contentHeight" : 10.854, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/MastheadStack[0]/HeaderContent[2]/HeaderSplit[1]/InvoiceMeta[1]/MetaGridLayer[0]/MetaGridHolder[0]/MetaGrid[0]/MetaLabels[0]/MetaLabel5[4]", + "entityName" : "MetaLabel5", + "entityKind" : "ParagraphNode", + "parentPath" : "PaymentsInvoice[0]/MastheadStack[0]/HeaderContent[2]/HeaderSplit[1]/InvoiceMeta[1]/MetaGridLayer[0]/MetaGridHolder[0]/MetaGrid[0]/MetaLabels[0]", + "childIndex" : 4, + "depth" : 10, + "layer" : 10, + "computedX" : 360.551, + "computedY" : 680.448, + "placementX" : 360.551, + "placementY" : 680.448, + "placementWidth" : 63.234, + "placementHeight" : 10.854, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 63.234, + "contentHeight" : 10.854, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/MastheadStack[0]/HeaderContent[2]/HeaderSplit[1]/InvoiceMeta[1]/MetaGridLayer[0]/MetaGridHolder[0]/MetaGrid[0]/MetaLabels[0]/MetaLabel6[5]", + "entityName" : "MetaLabel6", + "entityKind" : "ParagraphNode", + "parentPath" : "PaymentsInvoice[0]/MastheadStack[0]/HeaderContent[2]/HeaderSplit[1]/InvoiceMeta[1]/MetaGridLayer[0]/MetaGridHolder[0]/MetaGrid[0]/MetaLabels[0]", + "childIndex" : 5, + "depth" : 10, + "layer" : 10, + "computedX" : 360.551, + "computedY" : 663.425, + "placementX" : 360.551, + "placementY" : 663.425, + "placementWidth" : 38.686, + "placementHeight" : 10.854, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 38.686, + "contentHeight" : 10.854, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/MastheadStack[0]/HeaderContent[2]/HeaderSplit[1]/InvoiceMeta[1]/MetaGridLayer[0]/MetaGridHolder[0]/MetaGrid[0]/MetaLabels[0]/MetaLabel7[6]", + "entityName" : "MetaLabel7", + "entityKind" : "ParagraphNode", + "parentPath" : "PaymentsInvoice[0]/MastheadStack[0]/HeaderContent[2]/HeaderSplit[1]/InvoiceMeta[1]/MetaGridLayer[0]/MetaGridHolder[0]/MetaGrid[0]/MetaLabels[0]", + "childIndex" : 6, + "depth" : 10, + "layer" : 10, + "computedX" : 360.551, + "computedY" : 646.402, + "placementX" : 360.551, + "placementY" : 646.402, + "placementWidth" : 25.371, + "placementHeight" : 10.854, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 25.371, + "contentHeight" : 10.854, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/MastheadStack[0]/HeaderContent[2]/HeaderSplit[1]/InvoiceMeta[1]/MetaGridLayer[0]/MetaGridHolder[0]/MetaGrid[0]/MetaValues[1]", + "entityName" : "MetaValues", + "entityKind" : "SectionNode", + "parentPath" : "PaymentsInvoice[0]/MastheadStack[0]/HeaderContent[2]/HeaderSplit[1]/InvoiceMeta[1]/MetaGridLayer[0]/MetaGridHolder[0]/MetaGrid[0]", + "childIndex" : 1, + "depth" : 9, + "layer" : 9, + "computedX" : 440.955, + "computedY" : 646.402, + "placementX" : 440.955, + "placementY" : 646.402, + "placementWidth" : 88.384, + "placementHeight" : 112.993, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 88.384, + "contentHeight" : 112.993, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 13.26 + } + }, { + "path" : "PaymentsInvoice[0]/MastheadStack[0]/HeaderContent[2]/HeaderSplit[1]/InvoiceMeta[1]/MetaGridLayer[0]/MetaGridHolder[0]/MetaGrid[0]/MetaValues[1]/MetaValue1[0]", + "entityName" : "MetaValue1", + "entityKind" : "ParagraphNode", + "parentPath" : "PaymentsInvoice[0]/MastheadStack[0]/HeaderContent[2]/HeaderSplit[1]/InvoiceMeta[1]/MetaGridLayer[0]/MetaGridHolder[0]/MetaGrid[0]/MetaValues[1]", + "childIndex" : 0, + "depth" : 10, + "layer" : 10, + "computedX" : 454.215, + "computedY" : 749.614, + "placementX" : 454.215, + "placementY" : 749.614, + "placementWidth" : 75.124, + "placementHeight" : 9.781, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 75.124, + "contentHeight" : 9.781, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/MastheadStack[0]/HeaderContent[2]/HeaderSplit[1]/InvoiceMeta[1]/MetaGridLayer[0]/MetaGridHolder[0]/MetaGrid[0]/MetaValues[1]/MetaValue2[1]", + "entityName" : "MetaValue2", + "entityKind" : "ParagraphNode", + "parentPath" : "PaymentsInvoice[0]/MastheadStack[0]/HeaderContent[2]/HeaderSplit[1]/InvoiceMeta[1]/MetaGridLayer[0]/MetaGridHolder[0]/MetaGrid[0]/MetaValues[1]", + "childIndex" : 1, + "depth" : 10, + "layer" : 10, + "computedX" : 454.215, + "computedY" : 732.591, + "placementX" : 454.215, + "placementY" : 732.591, + "placementWidth" : 48.366, + "placementHeight" : 9.781, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 48.366, + "contentHeight" : 9.781, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/MastheadStack[0]/HeaderContent[2]/HeaderSplit[1]/InvoiceMeta[1]/MetaGridLayer[0]/MetaGridHolder[0]/MetaGrid[0]/MetaValues[1]/MetaValue3[2]", + "entityName" : "MetaValue3", + "entityKind" : "ParagraphNode", + "parentPath" : "PaymentsInvoice[0]/MastheadStack[0]/HeaderContent[2]/HeaderSplit[1]/InvoiceMeta[1]/MetaGridLayer[0]/MetaGridHolder[0]/MetaGrid[0]/MetaValues[1]", + "childIndex" : 2, + "depth" : 10, + "layer" : 10, + "computedX" : 454.215, + "computedY" : 715.568, + "placementX" : 454.215, + "placementY" : 715.568, + "placementWidth" : 61.407, + "placementHeight" : 9.781, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 61.407, + "contentHeight" : 9.781, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/MastheadStack[0]/HeaderContent[2]/HeaderSplit[1]/InvoiceMeta[1]/MetaGridLayer[0]/MetaGridHolder[0]/MetaGrid[0]/MetaValues[1]/MetaValue4[3]", + "entityName" : "MetaValue4", + "entityKind" : "ParagraphNode", + "parentPath" : "PaymentsInvoice[0]/MastheadStack[0]/HeaderContent[2]/HeaderSplit[1]/InvoiceMeta[1]/MetaGridLayer[0]/MetaGridHolder[0]/MetaGrid[0]/MetaValues[1]", + "childIndex" : 3, + "depth" : 10, + "layer" : 10, + "computedX" : 454.215, + "computedY" : 698.545, + "placementX" : 454.215, + "placementY" : 698.545, + "placementWidth" : 69.345, + "placementHeight" : 9.781, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 69.345, + "contentHeight" : 9.781, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/MastheadStack[0]/HeaderContent[2]/HeaderSplit[1]/InvoiceMeta[1]/MetaGridLayer[0]/MetaGridHolder[0]/MetaGrid[0]/MetaValues[1]/MetaValue5[4]", + "entityName" : "MetaValue5", + "entityKind" : "ParagraphNode", + "parentPath" : "PaymentsInvoice[0]/MastheadStack[0]/HeaderContent[2]/HeaderSplit[1]/InvoiceMeta[1]/MetaGridLayer[0]/MetaGridHolder[0]/MetaGrid[0]/MetaValues[1]", + "childIndex" : 4, + "depth" : 10, + "layer" : 10, + "computedX" : 454.215, + "computedY" : 681.522, + "placementX" : 454.215, + "placementY" : 681.522, + "placementWidth" : 24.998, + "placementHeight" : 9.781, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 24.998, + "contentHeight" : 9.781, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/MastheadStack[0]/HeaderContent[2]/HeaderSplit[1]/InvoiceMeta[1]/MetaGridLayer[0]/MetaGridHolder[0]/MetaGrid[0]/MetaValues[1]/MetaValue6[5]", + "entityName" : "MetaValue6", + "entityKind" : "ParagraphNode", + "parentPath" : "PaymentsInvoice[0]/MastheadStack[0]/HeaderContent[2]/HeaderSplit[1]/InvoiceMeta[1]/MetaGridLayer[0]/MetaGridHolder[0]/MetaGrid[0]/MetaValues[1]", + "childIndex" : 5, + "depth" : 10, + "layer" : 10, + "computedX" : 454.215, + "computedY" : 664.498, + "placementX" : 454.215, + "placementY" : 664.498, + "placementWidth" : 49.385, + "placementHeight" : 9.781, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 49.385, + "contentHeight" : 9.781, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/MastheadStack[0]/HeaderContent[2]/HeaderSplit[1]/InvoiceMeta[1]/MetaGridLayer[0]/MetaGridHolder[0]/MetaGrid[0]/MetaValues[1]/MetaValue7[6]", + "entityName" : "MetaValue7", + "entityKind" : "ParagraphNode", + "parentPath" : "PaymentsInvoice[0]/MastheadStack[0]/HeaderContent[2]/HeaderSplit[1]/InvoiceMeta[1]/MetaGridLayer[0]/MetaGridHolder[0]/MetaGrid[0]/MetaValues[1]", + "childIndex" : 6, + "depth" : 10, + "layer" : 10, + "computedX" : 454.215, + "computedY" : 646.402, + "placementX" : 454.215, + "placementY" : 646.402, + "placementWidth" : 35.149, + "placementHeight" : 10.854, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 35.149, + "contentHeight" : 10.854, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/HeaderRule[1]", + "entityName" : "HeaderRule", + "entityKind" : "LineNode", + "parentPath" : "PaymentsInvoice[0]", + "childIndex" : 1, + "depth" : 2, + "layer" : 2, + "computedX" : 16.927, + "computedY" : 638.198, + "placementX" : 16.927, + "placementY" : 638.198, + "placementWidth" : 557.471, + "placementHeight" : 1.128, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 557.471, + "contentHeight" : 1.128, + "margin" : { + "top" : -16.363, + "right" : -8.464, + "bottom" : 9.592, + "left" : -11.849 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/PartiesRow[2]", + "entityName" : "PartiesRow", + "entityKind" : "RowNode", + "parentPath" : "PaymentsInvoice[0]", + "childIndex" : 2, + "depth" : 2, + "layer" : 2, + "computedX" : 28.776, + "computedY" : 522.368, + "placementX" : 28.776, + "placementY" : 522.368, + "placementWidth" : 537.159, + "placementHeight" : 106.238, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 537.159, + "contentHeight" : 106.238, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/PartiesRow[2]/Party1[0]", + "entityName" : "Party1", + "entityKind" : "SectionNode", + "parentPath" : "PaymentsInvoice[0]/PartiesRow[2]", + "childIndex" : 0, + "depth" : 3, + "layer" : 3, + "computedX" : 28.776, + "computedY" : 522.368, + "placementX" : 28.776, + "placementY" : 522.368, + "placementWidth" : 268.579, + "placementHeight" : 106.238, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 268.579, + "contentHeight" : 106.238, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 5.078 + } + }, { + "path" : "PaymentsInvoice[0]/PartiesRow[2]/Party1[0]/PartyHeadLayer[0]", + "entityName" : "PartyHeadLayer", + "entityKind" : "LayerStackNode", + "parentPath" : "PaymentsInvoice[0]/PartiesRow[2]/Party1[0]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 33.855, + "computedY" : 606.036, + "placementX" : 33.855, + "placementY" : 606.036, + "placementWidth" : 263.501, + "placementHeight" : 22.57, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 263.501, + "contentHeight" : 22.57, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/PartiesRow[2]/Party1[0]/PartyHeadLayer[0]/PartyHeadHolder[0]", + "entityName" : "PartyHeadHolder", + "entityKind" : "SectionNode", + "parentPath" : "PaymentsInvoice[0]/PartiesRow[2]/Party1[0]/PartyHeadLayer[0]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 33.855, + "computedY" : 606.036, + "placementX" : 33.855, + "placementY" : 606.036, + "placementWidth" : 263.501, + "placementHeight" : 22.57, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 263.501, + "contentHeight" : 22.57, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/PartiesRow[2]/Party1[0]/PartyHeadLayer[0]/PartyHeadHolder[0]/PartyHead[0]", + "entityName" : "PartyHead", + "entityKind" : "RowNode", + "parentPath" : "PaymentsInvoice[0]/PartiesRow[2]/Party1[0]/PartyHeadLayer[0]/PartyHeadHolder[0]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 33.855, + "computedY" : 606.036, + "placementX" : 33.855, + "placementY" : 606.036, + "placementWidth" : 263.501, + "placementHeight" : 22.57, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 263.501, + "contentHeight" : 22.57, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/PartiesRow[2]/Party1[0]/PartyHeadLayer[0]/PartyHeadHolder[0]/PartyHead[0]/PartyDisc[0]", + "entityName" : "PartyDisc", + "entityKind" : "SectionNode", + "parentPath" : "PaymentsInvoice[0]/PartiesRow[2]/Party1[0]/PartyHeadLayer[0]/PartyHeadHolder[0]/PartyHead[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 33.855, + "computedY" : 606.036, + "placementX" : 33.855, + "placementY" : 606.036, + "placementWidth" : 22.57, + "placementHeight" : 22.57, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 22.57, + "contentHeight" : 22.57, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/PartiesRow[2]/Party1[0]/PartyHeadLayer[0]/PartyHeadHolder[0]/PartyHead[0]/PartyDisc[0]/Discbill-to[0]", + "entityName" : "Discbill-to", + "entityKind" : "ShapeContainerNode", + "parentPath" : "PaymentsInvoice[0]/PartiesRow[2]/Party1[0]/PartyHeadLayer[0]/PartyHeadHolder[0]/PartyHead[0]/PartyDisc[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 33.855, + "computedY" : 606.036, + "placementX" : 33.855, + "placementY" : 606.036, + "placementWidth" : 22.57, + "placementHeight" : 22.57, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 22.57, + "contentHeight" : 22.57, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/PartiesRow[2]/Party1[0]/PartyHeadLayer[0]/PartyHeadHolder[0]/PartyHead[0]/PartyDisc[0]/Discbill-to[0]/Glyphbill-to[0]", + "entityName" : "Glyphbill-to", + "entityKind" : "SectionNode", + "parentPath" : "PaymentsInvoice[0]/PartiesRow[2]/Party1[0]/PartyHeadLayer[0]/PartyHeadHolder[0]/PartyHead[0]/PartyDisc[0]/Discbill-to[0]", + "childIndex" : 0, + "depth" : 9, + "layer" : 9, + "computedX" : 33.855, + "computedY" : 609.422, + "placementX" : 33.855, + "placementY" : 609.422, + "placementWidth" : 22.57, + "placementHeight" : 15.799, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 22.57, + "contentHeight" : 15.799, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/PartiesRow[2]/Party1[0]/PartyHeadLayer[0]/PartyHeadHolder[0]/PartyHead[0]/PartyDisc[0]/Discbill-to[0]/Glyphbill-to[0]/Align[0]", + "entityName" : null, + "entityKind" : "Align", + "parentPath" : "PaymentsInvoice[0]/PartiesRow[2]/Party1[0]/PartyHeadLayer[0]/PartyHeadHolder[0]/PartyHead[0]/PartyDisc[0]/Discbill-to[0]/Glyphbill-to[0]", + "childIndex" : 0, + "depth" : 10, + "layer" : 10, + "computedX" : 33.855, + "computedY" : 609.422, + "placementX" : 33.855, + "placementY" : 609.422, + "placementWidth" : 22.57, + "placementHeight" : 15.799, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 22.57, + "contentHeight" : 15.799, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/PartiesRow[2]/Party1[0]/PartyHeadLayer[0]/PartyHeadHolder[0]/PartyHead[0]/PartyDisc[0]/Discbill-to[0]/Glyphbill-to[0]/Align[0]/SvgIcon[0]", + "entityName" : "SvgIcon", + "entityKind" : "LayerStackNode", + "parentPath" : "PaymentsInvoice[0]/PartiesRow[2]/Party1[0]/PartyHeadLayer[0]/PartyHeadHolder[0]/PartyHead[0]/PartyDisc[0]/Discbill-to[0]/Glyphbill-to[0]/Align[0]", + "childIndex" : 0, + "depth" : 11, + "layer" : 11, + "computedX" : 37.24, + "computedY" : 609.422, + "placementX" : 37.24, + "placementY" : 609.422, + "placementWidth" : 15.799, + "placementHeight" : 15.799, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 15.799, + "contentHeight" : 15.799, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/PartiesRow[2]/Party1[0]/PartyHeadLayer[0]/PartyHeadHolder[0]/PartyHead[0]/PartyDisc[0]/Discbill-to[0]/Glyphbill-to[0]/Align[0]/SvgIcon[0]/SvgLayer0[0]", + "entityName" : "SvgLayer0", + "entityKind" : "Path", + "parentPath" : "PaymentsInvoice[0]/PartiesRow[2]/Party1[0]/PartyHeadLayer[0]/PartyHeadHolder[0]/PartyHead[0]/PartyDisc[0]/Discbill-to[0]/Glyphbill-to[0]/Align[0]/SvgIcon[0]", + "childIndex" : 0, + "depth" : 12, + "layer" : 12, + "computedX" : 37.24, + "computedY" : 609.422, + "placementX" : 37.24, + "placementY" : 609.422, + "placementWidth" : 15.799, + "placementHeight" : 15.799, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 15.799, + "contentHeight" : 15.799, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/PartiesRow[2]/Party1[0]/PartyHeadLayer[0]/PartyHeadHolder[0]/PartyHead[0]/PartyLabel[1]", + "entityName" : "PartyLabel", + "entityKind" : "ParagraphNode", + "parentPath" : "PaymentsInvoice[0]/PartiesRow[2]/Party1[0]/PartyHeadLayer[0]/PartyHeadHolder[0]/PartyHead[0]", + "childIndex" : 1, + "depth" : 7, + "layer" : 7, + "computedX" : 66.581, + "computedY" : 613.055, + "placementX" : 66.581, + "placementY" : 613.055, + "placementWidth" : 31.052, + "placementHeight" : 10.217, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 31.052, + "contentHeight" : 10.217, + "margin" : { + "top" : 5.334, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/PartiesRow[2]/Party1[0]/PartyBody[1]", + "entityName" : "PartyBody", + "entityKind" : "SectionNode", + "parentPath" : "PaymentsInvoice[0]/PartiesRow[2]/Party1[0]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 33.855, + "computedY" : 522.368, + "placementX" : 33.855, + "placementY" : 522.368, + "placementWidth" : 132.109, + "placementHeight" : 83.668, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 132.109, + "contentHeight" : 83.668, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 32.726 + } + }, { + "path" : "PaymentsInvoice[0]/PartiesRow[2]/Party1[0]/PartyBody[1]/PartyName[0]", + "entityName" : "PartyName", + "entityKind" : "ParagraphNode", + "parentPath" : "PaymentsInvoice[0]/PartiesRow[2]/Party1[0]/PartyBody[1]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 66.581, + "computedY" : 593.759, + "placementX" : 66.581, + "placementY" : 593.759, + "placementWidth" : 69.419, + "placementHeight" : 12.278, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 69.419, + "contentHeight" : 12.278, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/PartiesRow[2]/Party1[0]/PartyBody[1]/PartyAddress[1]", + "entityName" : "PartyAddress", + "entityKind" : "SectionNode", + "parentPath" : "PaymentsInvoice[0]/PartiesRow[2]/Party1[0]/PartyBody[1]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 66.581, + "computedY" : 544.374, + "placementX" : 66.581, + "placementY" : 544.374, + "placementWidth" : 94.506, + "placementHeight" : 45.51, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 94.506, + "contentHeight" : 45.51, + "margin" : { + "top" : 3.875, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/PartiesRow[2]/Party1[0]/PartyBody[1]/PartyAddress[1]/PartyAddressLine1[0]", + "entityName" : "PartyAddressLine1", + "entityKind" : "ParagraphNode", + "parentPath" : "PaymentsInvoice[0]/PartiesRow[2]/Party1[0]/PartyBody[1]/PartyAddress[1]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 66.581, + "computedY" : 579.413, + "placementX" : 66.581, + "placementY" : 579.413, + "placementWidth" : 94.506, + "placementHeight" : 10.471, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 94.506, + "contentHeight" : 10.471, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/PartiesRow[2]/Party1[0]/PartyBody[1]/PartyAddress[1]/PartyAddressLine2[1]", + "entityName" : "PartyAddressLine2", + "entityKind" : "ParagraphNode", + "parentPath" : "PaymentsInvoice[0]/PartiesRow[2]/Party1[0]/PartyBody[1]/PartyAddress[1]", + "childIndex" : 1, + "depth" : 6, + "layer" : 6, + "computedX" : 66.581, + "computedY" : 567.733, + "placementX" : 66.581, + "placementY" : 567.733, + "placementWidth" : 85.039, + "placementHeight" : 10.471, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 85.039, + "contentHeight" : 10.471, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/PartiesRow[2]/Party1[0]/PartyBody[1]/PartyAddress[1]/PartyAddressLine3[2]", + "entityName" : "PartyAddressLine3", + "entityKind" : "ParagraphNode", + "parentPath" : "PaymentsInvoice[0]/PartiesRow[2]/Party1[0]/PartyBody[1]/PartyAddress[1]", + "childIndex" : 2, + "depth" : 6, + "layer" : 6, + "computedX" : 66.581, + "computedY" : 556.054, + "placementX" : 66.581, + "placementY" : 556.054, + "placementWidth" : 85.553, + "placementHeight" : 10.471, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 85.553, + "contentHeight" : 10.471, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/PartiesRow[2]/Party1[0]/PartyBody[1]/PartyAddress[1]/PartyAddressLine4[3]", + "entityName" : "PartyAddressLine4", + "entityKind" : "ParagraphNode", + "parentPath" : "PaymentsInvoice[0]/PartiesRow[2]/Party1[0]/PartyBody[1]/PartyAddress[1]", + "childIndex" : 3, + "depth" : 6, + "layer" : 6, + "computedX" : 66.581, + "computedY" : 544.374, + "placementX" : 66.581, + "placementY" : 544.374, + "placementWidth" : 62.544, + "placementHeight" : 10.471, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 62.544, + "contentHeight" : 10.471, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/PartiesRow[2]/Party1[0]/PartyBody[1]/PartySubline[2]", + "entityName" : "PartySubline", + "entityKind" : "ParagraphNode", + "parentPath" : "PaymentsInvoice[0]/PartiesRow[2]/Party1[0]/PartyBody[1]", + "childIndex" : 2, + "depth" : 5, + "layer" : 5, + "computedX" : 66.581, + "computedY" : 522.368, + "placementX" : 66.581, + "placementY" : 522.368, + "placementWidth" : 99.383, + "placementHeight" : 10.471, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 99.383, + "contentHeight" : 10.471, + "margin" : { + "top" : 11.535, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/PartiesRow[2]/Party2[1]", + "entityName" : "Party2", + "entityKind" : "SectionNode", + "parentPath" : "PaymentsInvoice[0]/PartiesRow[2]", + "childIndex" : 1, + "depth" : 3, + "layer" : 3, + "computedX" : 297.356, + "computedY" : 544.374, + "placementX" : 297.356, + "placementY" : 544.374, + "placementWidth" : 268.579, + "placementHeight" : 84.232, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 268.579, + "contentHeight" : 84.232, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 22.57 + } + }, { + "path" : "PaymentsInvoice[0]/PartiesRow[2]/Party2[1]/PartyHeadLayer[0]", + "entityName" : "PartyHeadLayer", + "entityKind" : "LayerStackNode", + "parentPath" : "PaymentsInvoice[0]/PartiesRow[2]/Party2[1]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 319.925, + "computedY" : 606.036, + "placementX" : 319.925, + "placementY" : 606.036, + "placementWidth" : 246.01, + "placementHeight" : 22.57, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 246.01, + "contentHeight" : 22.57, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/PartiesRow[2]/Party2[1]/PartyHeadLayer[0]/PartyHeadHolder[0]", + "entityName" : "PartyHeadHolder", + "entityKind" : "SectionNode", + "parentPath" : "PaymentsInvoice[0]/PartiesRow[2]/Party2[1]/PartyHeadLayer[0]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 319.925, + "computedY" : 606.036, + "placementX" : 319.925, + "placementY" : 606.036, + "placementWidth" : 246.01, + "placementHeight" : 22.57, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 246.01, + "contentHeight" : 22.57, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/PartiesRow[2]/Party2[1]/PartyHeadLayer[0]/PartyHeadHolder[0]/PartyHead[0]", + "entityName" : "PartyHead", + "entityKind" : "RowNode", + "parentPath" : "PaymentsInvoice[0]/PartiesRow[2]/Party2[1]/PartyHeadLayer[0]/PartyHeadHolder[0]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 319.925, + "computedY" : 606.036, + "placementX" : 319.925, + "placementY" : 606.036, + "placementWidth" : 246.01, + "placementHeight" : 22.57, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 246.01, + "contentHeight" : 22.57, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/PartiesRow[2]/Party2[1]/PartyHeadLayer[0]/PartyHeadHolder[0]/PartyHead[0]/PartyDisc[0]", + "entityName" : "PartyDisc", + "entityKind" : "SectionNode", + "parentPath" : "PaymentsInvoice[0]/PartiesRow[2]/Party2[1]/PartyHeadLayer[0]/PartyHeadHolder[0]/PartyHead[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 319.925, + "computedY" : 606.036, + "placementX" : 319.925, + "placementY" : 606.036, + "placementWidth" : 22.57, + "placementHeight" : 22.57, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 22.57, + "contentHeight" : 22.57, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/PartiesRow[2]/Party2[1]/PartyHeadLayer[0]/PartyHeadHolder[0]/PartyHead[0]/PartyDisc[0]/Discship-to[0]", + "entityName" : "Discship-to", + "entityKind" : "ShapeContainerNode", + "parentPath" : "PaymentsInvoice[0]/PartiesRow[2]/Party2[1]/PartyHeadLayer[0]/PartyHeadHolder[0]/PartyHead[0]/PartyDisc[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 319.925, + "computedY" : 606.036, + "placementX" : 319.925, + "placementY" : 606.036, + "placementWidth" : 22.57, + "placementHeight" : 22.57, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 22.57, + "contentHeight" : 22.57, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/PartiesRow[2]/Party2[1]/PartyHeadLayer[0]/PartyHeadHolder[0]/PartyHead[0]/PartyDisc[0]/Discship-to[0]/Glyphship-to[0]", + "entityName" : "Glyphship-to", + "entityKind" : "SectionNode", + "parentPath" : "PaymentsInvoice[0]/PartiesRow[2]/Party2[1]/PartyHeadLayer[0]/PartyHeadHolder[0]/PartyHead[0]/PartyDisc[0]/Discship-to[0]", + "childIndex" : 0, + "depth" : 9, + "layer" : 9, + "computedX" : 319.925, + "computedY" : 609.422, + "placementX" : 319.925, + "placementY" : 609.422, + "placementWidth" : 22.57, + "placementHeight" : 15.799, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 22.57, + "contentHeight" : 15.799, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/PartiesRow[2]/Party2[1]/PartyHeadLayer[0]/PartyHeadHolder[0]/PartyHead[0]/PartyDisc[0]/Discship-to[0]/Glyphship-to[0]/Align[0]", + "entityName" : null, + "entityKind" : "Align", + "parentPath" : "PaymentsInvoice[0]/PartiesRow[2]/Party2[1]/PartyHeadLayer[0]/PartyHeadHolder[0]/PartyHead[0]/PartyDisc[0]/Discship-to[0]/Glyphship-to[0]", + "childIndex" : 0, + "depth" : 10, + "layer" : 10, + "computedX" : 319.925, + "computedY" : 609.422, + "placementX" : 319.925, + "placementY" : 609.422, + "placementWidth" : 22.57, + "placementHeight" : 15.799, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 22.57, + "contentHeight" : 15.799, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/PartiesRow[2]/Party2[1]/PartyHeadLayer[0]/PartyHeadHolder[0]/PartyHead[0]/PartyDisc[0]/Discship-to[0]/Glyphship-to[0]/Align[0]/SvgIcon[0]", + "entityName" : "SvgIcon", + "entityKind" : "LayerStackNode", + "parentPath" : "PaymentsInvoice[0]/PartiesRow[2]/Party2[1]/PartyHeadLayer[0]/PartyHeadHolder[0]/PartyHead[0]/PartyDisc[0]/Discship-to[0]/Glyphship-to[0]/Align[0]", + "childIndex" : 0, + "depth" : 11, + "layer" : 11, + "computedX" : 323.311, + "computedY" : 609.422, + "placementX" : 323.311, + "placementY" : 609.422, + "placementWidth" : 15.799, + "placementHeight" : 15.799, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 15.799, + "contentHeight" : 15.799, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/PartiesRow[2]/Party2[1]/PartyHeadLayer[0]/PartyHeadHolder[0]/PartyHead[0]/PartyDisc[0]/Discship-to[0]/Glyphship-to[0]/Align[0]/SvgIcon[0]/SvgLayer0[0]", + "entityName" : "SvgLayer0", + "entityKind" : "Path", + "parentPath" : "PaymentsInvoice[0]/PartiesRow[2]/Party2[1]/PartyHeadLayer[0]/PartyHeadHolder[0]/PartyHead[0]/PartyDisc[0]/Discship-to[0]/Glyphship-to[0]/Align[0]/SvgIcon[0]", + "childIndex" : 0, + "depth" : 12, + "layer" : 12, + "computedX" : 323.311, + "computedY" : 609.422, + "placementX" : 323.311, + "placementY" : 609.422, + "placementWidth" : 15.799, + "placementHeight" : 15.799, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 15.799, + "contentHeight" : 15.799, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/PartiesRow[2]/Party2[1]/PartyHeadLayer[0]/PartyHeadHolder[0]/PartyHead[0]/PartyLabel[1]", + "entityName" : "PartyLabel", + "entityKind" : "ParagraphNode", + "parentPath" : "PaymentsInvoice[0]/PartiesRow[2]/Party2[1]/PartyHeadLayer[0]/PartyHeadHolder[0]/PartyHead[0]", + "childIndex" : 1, + "depth" : 7, + "layer" : 7, + "computedX" : 352.651, + "computedY" : 613.055, + "placementX" : 352.651, + "placementY" : 613.055, + "placementWidth" : 33.095, + "placementHeight" : 10.217, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 33.095, + "contentHeight" : 10.217, + "margin" : { + "top" : 5.334, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/PartiesRow[2]/Party2[1]/PartyBody[1]", + "entityName" : "PartyBody", + "entityKind" : "SectionNode", + "parentPath" : "PaymentsInvoice[0]/PartiesRow[2]/Party2[1]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 319.925, + "computedY" : 544.374, + "placementX" : 319.925, + "placementY" : 544.374, + "placementWidth" : 104.004, + "placementHeight" : 61.663, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 104.004, + "contentHeight" : 61.663, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 32.726 + } + }, { + "path" : "PaymentsInvoice[0]/PartiesRow[2]/Party2[1]/PartyBody[1]/PartyName[0]", + "entityName" : "PartyName", + "entityKind" : "ParagraphNode", + "parentPath" : "PaymentsInvoice[0]/PartiesRow[2]/Party2[1]/PartyBody[1]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 352.651, + "computedY" : 593.759, + "placementX" : 352.651, + "placementY" : 593.759, + "placementWidth" : 69.419, + "placementHeight" : 12.278, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 69.419, + "contentHeight" : 12.278, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/PartiesRow[2]/Party2[1]/PartyBody[1]/PartyAddress[1]", + "entityName" : "PartyAddress", + "entityKind" : "SectionNode", + "parentPath" : "PaymentsInvoice[0]/PartiesRow[2]/Party2[1]/PartyBody[1]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 352.651, + "computedY" : 544.374, + "placementX" : 352.651, + "placementY" : 544.374, + "placementWidth" : 71.278, + "placementHeight" : 45.51, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 71.278, + "contentHeight" : 45.51, + "margin" : { + "top" : 3.875, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/PartiesRow[2]/Party2[1]/PartyBody[1]/PartyAddress[1]/PartyAddressLine1[0]", + "entityName" : "PartyAddressLine1", + "entityKind" : "ParagraphNode", + "parentPath" : "PaymentsInvoice[0]/PartiesRow[2]/Party2[1]/PartyBody[1]/PartyAddress[1]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 352.651, + "computedY" : 579.413, + "placementX" : 352.651, + "placementY" : 579.413, + "placementWidth" : 71.278, + "placementHeight" : 10.471, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 71.278, + "contentHeight" : 10.471, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/PartiesRow[2]/Party2[1]/PartyBody[1]/PartyAddress[1]/PartyAddressLine2[1]", + "entityName" : "PartyAddressLine2", + "entityKind" : "ParagraphNode", + "parentPath" : "PaymentsInvoice[0]/PartiesRow[2]/Party2[1]/PartyBody[1]/PartyAddress[1]", + "childIndex" : 1, + "depth" : 6, + "layer" : 6, + "computedX" : 352.651, + "computedY" : 567.733, + "placementX" : 352.651, + "placementY" : 567.733, + "placementWidth" : 63.478, + "placementHeight" : 10.471, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 63.478, + "contentHeight" : 10.471, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/PartiesRow[2]/Party2[1]/PartyBody[1]/PartyAddress[1]/PartyAddressLine3[2]", + "entityName" : "PartyAddressLine3", + "entityKind" : "ParagraphNode", + "parentPath" : "PaymentsInvoice[0]/PartiesRow[2]/Party2[1]/PartyBody[1]/PartyAddress[1]", + "childIndex" : 2, + "depth" : 6, + "layer" : 6, + "computedX" : 352.651, + "computedY" : 556.054, + "placementX" : 352.651, + "placementY" : 556.054, + "placementWidth" : 63.521, + "placementHeight" : 10.471, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 63.521, + "contentHeight" : 10.471, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/PartiesRow[2]/Party2[1]/PartyBody[1]/PartyAddress[1]/PartyAddressLine4[3]", + "entityName" : "PartyAddressLine4", + "entityKind" : "ParagraphNode", + "parentPath" : "PaymentsInvoice[0]/PartiesRow[2]/Party2[1]/PartyBody[1]/PartyAddress[1]", + "childIndex" : 3, + "depth" : 6, + "layer" : 6, + "computedX" : 352.651, + "computedY" : 544.374, + "placementX" : 352.651, + "placementY" : 544.374, + "placementWidth" : 62.544, + "placementHeight" : 10.471, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 62.544, + "contentHeight" : 10.471, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/LineItemsBox[3]", + "entityName" : "LineItemsBox", + "entityKind" : "SectionNode", + "parentPath" : "PaymentsInvoice[0]", + "childIndex" : 3, + "depth" : 2, + "layer" : 2, + "computedX" : 28.776, + "computedY" : 272.254, + "placementX" : 28.776, + "placementY" : 272.254, + "placementWidth" : 537.159, + "placementHeight" : 233.751, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 537.159, + "contentHeight" : 233.751, + "margin" : { + "top" : 16.363, + "right" : 0.0, + "bottom" : 10.156, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/LineItemsBox[3]/LineItems[0]", + "entityName" : "LineItems", + "entityKind" : "TableNode", + "parentPath" : "PaymentsInvoice[0]/LineItemsBox[3]", + "childIndex" : 0, + "depth" : 3, + "layer" : 3, + "computedX" : 28.776, + "computedY" : 272.254, + "placementX" : 28.776, + "placementY" : 272.254, + "placementWidth" : 537.159, + "placementHeight" : 233.751, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 537.159, + "contentHeight" : 233.751, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/SettlementRow[4]", + "entityName" : "SettlementRow", + "entityKind" : "RowNode", + "parentPath" : "PaymentsInvoice[0]", + "childIndex" : 4, + "depth" : 2, + "layer" : 2, + "computedX" : 28.776, + "computedY" : 153.463, + "placementX" : 28.776, + "placementY" : 153.463, + "placementWidth" : 537.159, + "placementHeight" : 108.635, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 537.159, + "contentHeight" : 108.635, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/SettlementRow[4]/PaymentDetails[0]", + "entityName" : "PaymentDetails", + "entityKind" : "SectionNode", + "parentPath" : "PaymentsInvoice[0]/SettlementRow[4]", + "childIndex" : 0, + "depth" : 3, + "layer" : 3, + "computedX" : 28.776, + "computedY" : 153.82, + "placementX" : 28.776, + "placementY" : 153.82, + "placementWidth" : 248.267, + "placementHeight" : 108.278, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 248.267, + "contentHeight" : 108.278, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 2.821, + "right" : 11.285, + "bottom" : 7.617, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/SettlementRow[4]/PaymentDetails[0]/CardHeadLayer[0]", + "entityName" : "CardHeadLayer", + "entityKind" : "LayerStackNode", + "parentPath" : "PaymentsInvoice[0]/SettlementRow[4]/PaymentDetails[0]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 28.776, + "computedY" : 237.835, + "placementX" : 28.776, + "placementY" : 237.835, + "placementWidth" : 236.982, + "placementHeight" : 21.441, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 236.982, + "contentHeight" : 21.441, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/SettlementRow[4]/PaymentDetails[0]/CardHeadLayer[0]/CardHeadHolder[0]", + "entityName" : "CardHeadHolder", + "entityKind" : "SectionNode", + "parentPath" : "PaymentsInvoice[0]/SettlementRow[4]/PaymentDetails[0]/CardHeadLayer[0]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 28.776, + "computedY" : 237.835, + "placementX" : 28.776, + "placementY" : 237.835, + "placementWidth" : 236.982, + "placementHeight" : 21.441, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 236.982, + "contentHeight" : 21.441, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/SettlementRow[4]/PaymentDetails[0]/CardHeadLayer[0]/CardHeadHolder[0]/CardHead[0]", + "entityName" : "CardHead", + "entityKind" : "RowNode", + "parentPath" : "PaymentsInvoice[0]/SettlementRow[4]/PaymentDetails[0]/CardHeadLayer[0]/CardHeadHolder[0]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 28.776, + "computedY" : 237.835, + "placementX" : 28.776, + "placementY" : 237.835, + "placementWidth" : 236.982, + "placementHeight" : 21.441, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 236.982, + "contentHeight" : 21.441, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/SettlementRow[4]/PaymentDetails[0]/CardHeadLayer[0]/CardHeadHolder[0]/CardHead[0]/CardDisc[0]", + "entityName" : "CardDisc", + "entityKind" : "SectionNode", + "parentPath" : "PaymentsInvoice[0]/SettlementRow[4]/PaymentDetails[0]/CardHeadLayer[0]/CardHeadHolder[0]/CardHead[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 28.776, + "computedY" : 237.835, + "placementX" : 28.776, + "placementY" : 237.835, + "placementWidth" : 28.494, + "placementHeight" : 21.441, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 28.494, + "contentHeight" : 21.441, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 7.053 + } + }, { + "path" : "PaymentsInvoice[0]/SettlementRow[4]/PaymentDetails[0]/CardHeadLayer[0]/CardHeadHolder[0]/CardHead[0]/CardDisc[0]/Discbank[0]", + "entityName" : "Discbank", + "entityKind" : "ShapeContainerNode", + "parentPath" : "PaymentsInvoice[0]/SettlementRow[4]/PaymentDetails[0]/CardHeadLayer[0]/CardHeadHolder[0]/CardHead[0]/CardDisc[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 35.829, + "computedY" : 237.835, + "placementX" : 35.829, + "placementY" : 237.835, + "placementWidth" : 21.441, + "placementHeight" : 21.441, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 21.441, + "contentHeight" : 21.441, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/SettlementRow[4]/PaymentDetails[0]/CardHeadLayer[0]/CardHeadHolder[0]/CardHead[0]/CardDisc[0]/Discbank[0]/Glyphbank[0]", + "entityName" : "Glyphbank", + "entityKind" : "SectionNode", + "parentPath" : "PaymentsInvoice[0]/SettlementRow[4]/PaymentDetails[0]/CardHeadLayer[0]/CardHeadHolder[0]/CardHead[0]/CardDisc[0]/Discbank[0]", + "childIndex" : 0, + "depth" : 9, + "layer" : 9, + "computedX" : 35.829, + "computedY" : 240.656, + "placementX" : 35.829, + "placementY" : 240.656, + "placementWidth" : 21.441, + "placementHeight" : 15.799, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 21.441, + "contentHeight" : 15.799, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/SettlementRow[4]/PaymentDetails[0]/CardHeadLayer[0]/CardHeadHolder[0]/CardHead[0]/CardDisc[0]/Discbank[0]/Glyphbank[0]/Align[0]", + "entityName" : null, + "entityKind" : "Align", + "parentPath" : "PaymentsInvoice[0]/SettlementRow[4]/PaymentDetails[0]/CardHeadLayer[0]/CardHeadHolder[0]/CardHead[0]/CardDisc[0]/Discbank[0]/Glyphbank[0]", + "childIndex" : 0, + "depth" : 10, + "layer" : 10, + "computedX" : 35.829, + "computedY" : 240.656, + "placementX" : 35.829, + "placementY" : 240.656, + "placementWidth" : 21.441, + "placementHeight" : 15.799, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 21.441, + "contentHeight" : 15.799, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/SettlementRow[4]/PaymentDetails[0]/CardHeadLayer[0]/CardHeadHolder[0]/CardHead[0]/CardDisc[0]/Discbank[0]/Glyphbank[0]/Align[0]/SvgIcon[0]", + "entityName" : "SvgIcon", + "entityKind" : "LayerStackNode", + "parentPath" : "PaymentsInvoice[0]/SettlementRow[4]/PaymentDetails[0]/CardHeadLayer[0]/CardHeadHolder[0]/CardHead[0]/CardDisc[0]/Discbank[0]/Glyphbank[0]/Align[0]", + "childIndex" : 0, + "depth" : 11, + "layer" : 11, + "computedX" : 38.651, + "computedY" : 240.656, + "placementX" : 38.651, + "placementY" : 240.656, + "placementWidth" : 15.799, + "placementHeight" : 15.799, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 15.799, + "contentHeight" : 15.799, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/SettlementRow[4]/PaymentDetails[0]/CardHeadLayer[0]/CardHeadHolder[0]/CardHead[0]/CardDisc[0]/Discbank[0]/Glyphbank[0]/Align[0]/SvgIcon[0]/SvgLayer0[0]", + "entityName" : "SvgLayer0", + "entityKind" : "Path", + "parentPath" : "PaymentsInvoice[0]/SettlementRow[4]/PaymentDetails[0]/CardHeadLayer[0]/CardHeadHolder[0]/CardHead[0]/CardDisc[0]/Discbank[0]/Glyphbank[0]/Align[0]/SvgIcon[0]", + "childIndex" : 0, + "depth" : 12, + "layer" : 12, + "computedX" : 38.651, + "computedY" : 240.656, + "placementX" : 38.651, + "placementY" : 240.656, + "placementWidth" : 15.799, + "placementHeight" : 15.799, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 15.799, + "contentHeight" : 15.799, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/SettlementRow[4]/PaymentDetails[0]/CardHeadLayer[0]/CardHeadHolder[0]/CardHead[0]/CardLabel[1]", + "entityName" : "CardLabel", + "entityKind" : "ParagraphNode", + "parentPath" : "PaymentsInvoice[0]/SettlementRow[4]/PaymentDetails[0]/CardHeadLayer[0]/CardHeadHolder[0]/CardHead[0]", + "childIndex" : 1, + "depth" : 7, + "layer" : 7, + "computedX" : 66.863, + "computedY" : 243.431, + "placementX" : 66.863, + "placementY" : 243.431, + "placementWidth" : 80.189, + "placementHeight" : 10.582, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 80.189, + "contentHeight" : 10.582, + "margin" : { + "top" : 5.263, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/SettlementRow[4]/PaymentDetails[0]/CardFieldsLayer[1]", + "entityName" : "CardFieldsLayer", + "entityKind" : "LayerStackNode", + "parentPath" : "PaymentsInvoice[0]/SettlementRow[4]/PaymentDetails[0]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 28.776, + "computedY" : 161.437, + "placementX" : 28.776, + "placementY" : 161.437, + "placementWidth" : 236.982, + "placementHeight" : 76.398, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 236.982, + "contentHeight" : 76.398, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/SettlementRow[4]/PaymentDetails[0]/CardFieldsLayer[1]/CardFieldsHolder[0]", + "entityName" : "CardFieldsHolder", + "entityKind" : "SectionNode", + "parentPath" : "PaymentsInvoice[0]/SettlementRow[4]/PaymentDetails[0]/CardFieldsLayer[1]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 28.776, + "computedY" : 161.437, + "placementX" : 28.776, + "placementY" : 161.437, + "placementWidth" : 236.982, + "placementHeight" : 76.398, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 236.982, + "contentHeight" : 76.398, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/SettlementRow[4]/PaymentDetails[0]/CardFieldsLayer[1]/CardFieldsHolder[0]/CardFields[0]", + "entityName" : "CardFields", + "entityKind" : "RowNode", + "parentPath" : "PaymentsInvoice[0]/SettlementRow[4]/PaymentDetails[0]/CardFieldsLayer[1]/CardFieldsHolder[0]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 65.452, + "computedY" : 161.437, + "placementX" : 65.452, + "placementY" : 161.437, + "placementWidth" : 200.306, + "placementHeight" : 74.18, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 200.306, + "contentHeight" : 74.18, + "margin" : { + "top" : 2.218, + "right" : 0.0, + "bottom" : 0.0, + "left" : 36.676 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/SettlementRow[4]/PaymentDetails[0]/CardFieldsLayer[1]/CardFieldsHolder[0]/CardFields[0]/CardFieldLabels[0]", + "entityName" : "CardFieldLabels", + "entityKind" : "SectionNode", + "parentPath" : "PaymentsInvoice[0]/SettlementRow[4]/PaymentDetails[0]/CardFieldsLayer[1]/CardFieldsHolder[0]/CardFields[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 65.452, + "computedY" : 161.437, + "placementX" : 65.452, + "placementY" : 161.437, + "placementWidth" : 56.174, + "placementHeight" : 74.18, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 56.174, + "contentHeight" : 74.18, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/SettlementRow[4]/PaymentDetails[0]/CardFieldsLayer[1]/CardFieldsHolder[0]/CardFields[0]/CardFieldLabels[0]/CardFieldLabel1[0]", + "entityName" : "CardFieldLabel1", + "entityKind" : "ParagraphNode", + "parentPath" : "PaymentsInvoice[0]/SettlementRow[4]/PaymentDetails[0]/CardFieldsLayer[1]/CardFieldsHolder[0]/CardFields[0]/CardFieldLabels[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 65.452, + "computedY" : 226.776, + "placementX" : 65.452, + "placementY" : 226.776, + "placementWidth" : 38.014, + "placementHeight" : 8.841, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 38.014, + "contentHeight" : 8.841, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/SettlementRow[4]/PaymentDetails[0]/CardFieldsLayer[1]/CardFieldsHolder[0]/CardFields[0]/CardFieldLabels[0]/CardFieldLabel2[1]", + "entityName" : "CardFieldLabel2", + "entityKind" : "ParagraphNode", + "parentPath" : "PaymentsInvoice[0]/SettlementRow[4]/PaymentDetails[0]/CardFieldsLayer[1]/CardFieldsHolder[0]/CardFields[0]/CardFieldLabels[0]", + "childIndex" : 1, + "depth" : 8, + "layer" : 8, + "computedX" : 65.452, + "computedY" : 215.886, + "placementX" : 65.452, + "placementY" : 215.886, + "placementWidth" : 48.829, + "placementHeight" : 8.841, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 48.829, + "contentHeight" : 8.841, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/SettlementRow[4]/PaymentDetails[0]/CardFieldsLayer[1]/CardFieldsHolder[0]/CardFields[0]/CardFieldLabels[0]/CardFieldLabel3[2]", + "entityName" : "CardFieldLabel3", + "entityKind" : "ParagraphNode", + "parentPath" : "PaymentsInvoice[0]/SettlementRow[4]/PaymentDetails[0]/CardFieldsLayer[1]/CardFieldsHolder[0]/CardFields[0]/CardFieldLabels[0]", + "childIndex" : 2, + "depth" : 8, + "layer" : 8, + "computedX" : 65.452, + "computedY" : 204.996, + "placementX" : 65.452, + "placementY" : 204.996, + "placementWidth" : 32.791, + "placementHeight" : 8.841, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 32.791, + "contentHeight" : 8.841, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/SettlementRow[4]/PaymentDetails[0]/CardFieldsLayer[1]/CardFieldsHolder[0]/CardFields[0]/CardFieldLabels[0]/CardFieldLabel4[3]", + "entityName" : "CardFieldLabel4", + "entityKind" : "ParagraphNode", + "parentPath" : "PaymentsInvoice[0]/SettlementRow[4]/PaymentDetails[0]/CardFieldsLayer[1]/CardFieldsHolder[0]/CardFields[0]/CardFieldLabels[0]", + "childIndex" : 3, + "depth" : 8, + "layer" : 8, + "computedX" : 65.452, + "computedY" : 194.107, + "placementX" : 65.452, + "placementY" : 194.107, + "placementWidth" : 56.174, + "placementHeight" : 8.841, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 56.174, + "contentHeight" : 8.841, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/SettlementRow[4]/PaymentDetails[0]/CardFieldsLayer[1]/CardFieldsHolder[0]/CardFields[0]/CardFieldLabels[0]/CardFieldLabel5[4]", + "entityName" : "CardFieldLabel5", + "entityKind" : "ParagraphNode", + "parentPath" : "PaymentsInvoice[0]/SettlementRow[4]/PaymentDetails[0]/CardFieldsLayer[1]/CardFieldsHolder[0]/CardFields[0]/CardFieldLabels[0]", + "childIndex" : 4, + "depth" : 8, + "layer" : 8, + "computedX" : 65.452, + "computedY" : 183.217, + "placementX" : 65.452, + "placementY" : 183.217, + "placementWidth" : 17.843, + "placementHeight" : 8.841, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 17.843, + "contentHeight" : 8.841, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/SettlementRow[4]/PaymentDetails[0]/CardFieldsLayer[1]/CardFieldsHolder[0]/CardFields[0]/CardFieldLabels[0]/CardFieldLabel6[5]", + "entityName" : "CardFieldLabel6", + "entityKind" : "ParagraphNode", + "parentPath" : "PaymentsInvoice[0]/SettlementRow[4]/PaymentDetails[0]/CardFieldsLayer[1]/CardFieldsHolder[0]/CardFields[0]/CardFieldLabels[0]", + "childIndex" : 5, + "depth" : 8, + "layer" : 8, + "computedX" : 65.452, + "computedY" : 172.327, + "placementX" : 65.452, + "placementY" : 172.327, + "placementWidth" : 41.499, + "placementHeight" : 8.841, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 41.499, + "contentHeight" : 8.841, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/SettlementRow[4]/PaymentDetails[0]/CardFieldsLayer[1]/CardFieldsHolder[0]/CardFields[0]/CardFieldLabels[0]/CardFieldLabel7[6]", + "entityName" : "CardFieldLabel7", + "entityKind" : "ParagraphNode", + "parentPath" : "PaymentsInvoice[0]/SettlementRow[4]/PaymentDetails[0]/CardFieldsLayer[1]/CardFieldsHolder[0]/CardFields[0]/CardFieldLabels[0]", + "childIndex" : 6, + "depth" : 8, + "layer" : 8, + "computedX" : 65.452, + "computedY" : 161.437, + "placementX" : 65.452, + "placementY" : 161.437, + "placementWidth" : 33.572, + "placementHeight" : 8.841, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 33.572, + "contentHeight" : 8.841, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/SettlementRow[4]/PaymentDetails[0]/CardFieldsLayer[1]/CardFieldsHolder[0]/CardFields[0]/CardFieldValues[1]", + "entityName" : "CardFieldValues", + "entityKind" : "SectionNode", + "parentPath" : "PaymentsInvoice[0]/SettlementRow[4]/PaymentDetails[0]/CardFieldsLayer[1]/CardFieldsHolder[0]/CardFields[0]", + "childIndex" : 1, + "depth" : 7, + "layer" : 7, + "computedX" : 144.446, + "computedY" : 161.461, + "placementX" : 144.446, + "placementY" : 161.461, + "placementWidth" : 107.558, + "placementHeight" : 74.156, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 107.558, + "contentHeight" : 74.156, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/SettlementRow[4]/PaymentDetails[0]/CardFieldsLayer[1]/CardFieldsHolder[0]/CardFields[0]/CardFieldValues[1]/CardFieldValue1[0]", + "entityName" : "CardFieldValue1", + "entityKind" : "ParagraphNode", + "parentPath" : "PaymentsInvoice[0]/SettlementRow[4]/PaymentDetails[0]/CardFieldsLayer[1]/CardFieldsHolder[0]/CardFields[0]/CardFieldValues[1]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 144.446, + "computedY" : 226.8, + "placementX" : 144.446, + "placementY" : 226.8, + "placementWidth" : 56.362, + "placementHeight" : 8.817, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 56.362, + "contentHeight" : 8.817, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/SettlementRow[4]/PaymentDetails[0]/CardFieldsLayer[1]/CardFieldsHolder[0]/CardFields[0]/CardFieldValues[1]/CardFieldValue2[1]", + "entityName" : "CardFieldValue2", + "entityKind" : "ParagraphNode", + "parentPath" : "PaymentsInvoice[0]/SettlementRow[4]/PaymentDetails[0]/CardFieldsLayer[1]/CardFieldsHolder[0]/CardFields[0]/CardFieldValues[1]", + "childIndex" : 1, + "depth" : 8, + "layer" : 8, + "computedX" : 144.446, + "computedY" : 215.91, + "placementX" : 144.446, + "placementY" : 215.91, + "placementWidth" : 76.501, + "placementHeight" : 8.817, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 76.501, + "contentHeight" : 8.817, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/SettlementRow[4]/PaymentDetails[0]/CardFieldsLayer[1]/CardFieldsHolder[0]/CardFields[0]/CardFieldValues[1]/CardFieldValue3[2]", + "entityName" : "CardFieldValue3", + "entityKind" : "ParagraphNode", + "parentPath" : "PaymentsInvoice[0]/SettlementRow[4]/PaymentDetails[0]/CardFieldsLayer[1]/CardFieldsHolder[0]/CardFields[0]/CardFieldValues[1]", + "childIndex" : 2, + "depth" : 8, + "layer" : 8, + "computedX" : 144.446, + "computedY" : 205.02, + "placementX" : 144.446, + "placementY" : 205.02, + "placementWidth" : 31.035, + "placementHeight" : 8.817, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 31.035, + "contentHeight" : 8.817, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/SettlementRow[4]/PaymentDetails[0]/CardFieldsLayer[1]/CardFieldsHolder[0]/CardFields[0]/CardFieldValues[1]/CardFieldValue4[3]", + "entityName" : "CardFieldValue4", + "entityKind" : "ParagraphNode", + "parentPath" : "PaymentsInvoice[0]/SettlementRow[4]/PaymentDetails[0]/CardFieldsLayer[1]/CardFieldsHolder[0]/CardFields[0]/CardFieldValues[1]", + "childIndex" : 3, + "depth" : 8, + "layer" : 8, + "computedX" : 144.446, + "computedY" : 194.13, + "placementX" : 144.446, + "placementY" : 194.13, + "placementWidth" : 34.092, + "placementHeight" : 8.817, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 34.092, + "contentHeight" : 8.817, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/SettlementRow[4]/PaymentDetails[0]/CardFieldsLayer[1]/CardFieldsHolder[0]/CardFields[0]/CardFieldValues[1]/CardFieldValue5[4]", + "entityName" : "CardFieldValue5", + "entityKind" : "ParagraphNode", + "parentPath" : "PaymentsInvoice[0]/SettlementRow[4]/PaymentDetails[0]/CardFieldsLayer[1]/CardFieldsHolder[0]/CardFields[0]/CardFieldValues[1]", + "childIndex" : 4, + "depth" : 8, + "layer" : 8, + "computedX" : 144.446, + "computedY" : 183.24, + "placementX" : 144.446, + "placementY" : 183.24, + "placementWidth" : 107.558, + "placementHeight" : 8.817, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 107.558, + "contentHeight" : 8.817, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/SettlementRow[4]/PaymentDetails[0]/CardFieldsLayer[1]/CardFieldsHolder[0]/CardFields[0]/CardFieldValues[1]/CardFieldValue6[5]", + "entityName" : "CardFieldValue6", + "entityKind" : "ParagraphNode", + "parentPath" : "PaymentsInvoice[0]/SettlementRow[4]/PaymentDetails[0]/CardFieldsLayer[1]/CardFieldsHolder[0]/CardFields[0]/CardFieldValues[1]", + "childIndex" : 5, + "depth" : 8, + "layer" : 8, + "computedX" : 144.446, + "computedY" : 172.351, + "placementX" : 144.446, + "placementY" : 172.351, + "placementWidth" : 38.008, + "placementHeight" : 8.817, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 38.008, + "contentHeight" : 8.817, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/SettlementRow[4]/PaymentDetails[0]/CardFieldsLayer[1]/CardFieldsHolder[0]/CardFields[0]/CardFieldValues[1]/CardFieldValue7[6]", + "entityName" : "CardFieldValue7", + "entityKind" : "ParagraphNode", + "parentPath" : "PaymentsInvoice[0]/SettlementRow[4]/PaymentDetails[0]/CardFieldsLayer[1]/CardFieldsHolder[0]/CardFields[0]/CardFieldValues[1]", + "childIndex" : 6, + "depth" : 8, + "layer" : 8, + "computedX" : 144.446, + "computedY" : 161.461, + "placementX" : 144.446, + "placementY" : 161.461, + "placementWidth" : 67.721, + "placementHeight" : 8.817, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 67.721, + "contentHeight" : 8.817, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/SettlementRow[4]/Settlement[1]", + "entityName" : "Settlement", + "entityKind" : "SectionNode", + "parentPath" : "PaymentsInvoice[0]/SettlementRow[4]", + "childIndex" : 1, + "depth" : 3, + "layer" : 3, + "computedX" : 297.638, + "computedY" : 153.463, + "placementX" : 297.638, + "placementY" : 153.463, + "placementWidth" : 268.297, + "placementHeight" : 108.635, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 268.297, + "contentHeight" : 108.635, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 11.003 + } + }, { + "path" : "PaymentsInvoice[0]/SettlementRow[4]/Settlement[1]/Totals[0]", + "entityName" : "Totals", + "entityKind" : "SectionNode", + "parentPath" : "PaymentsInvoice[0]/SettlementRow[4]/Settlement[1]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 308.641, + "computedY" : 219.153, + "placementX" : 308.641, + "placementY" : 219.153, + "placementWidth" : 257.294, + "placementHeight" : 32.745, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 257.294, + "contentHeight" : 32.745, + "margin" : { + "top" : 10.2, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 9.028, + "bottom" : 0.0, + "left" : 10.721 + } + }, { + "path" : "PaymentsInvoice[0]/SettlementRow[4]/Settlement[1]/Totals[0]/TotalsGridLayer[0]", + "entityName" : "TotalsGridLayer", + "entityKind" : "LayerStackNode", + "parentPath" : "PaymentsInvoice[0]/SettlementRow[4]/Settlement[1]/Totals[0]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 319.361, + "computedY" : 219.153, + "placementX" : 319.361, + "placementY" : 219.153, + "placementWidth" : 237.546, + "placementHeight" : 32.745, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 237.546, + "contentHeight" : 32.745, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/SettlementRow[4]/Settlement[1]/Totals[0]/TotalsGridLayer[0]/TotalsGridHolder[0]", + "entityName" : "TotalsGridHolder", + "entityKind" : "SectionNode", + "parentPath" : "PaymentsInvoice[0]/SettlementRow[4]/Settlement[1]/Totals[0]/TotalsGridLayer[0]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 319.361, + "computedY" : 219.153, + "placementX" : 319.361, + "placementY" : 219.153, + "placementWidth" : 237.546, + "placementHeight" : 32.745, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 237.546, + "contentHeight" : 32.745, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/SettlementRow[4]/Settlement[1]/Totals[0]/TotalsGridLayer[0]/TotalsGridHolder[0]/TotalsGrid[0]", + "entityName" : "TotalsGrid", + "entityKind" : "RowNode", + "parentPath" : "PaymentsInvoice[0]/SettlementRow[4]/Settlement[1]/Totals[0]/TotalsGridLayer[0]/TotalsGridHolder[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 319.361, + "computedY" : 219.153, + "placementX" : 319.361, + "placementY" : 219.153, + "placementWidth" : 237.546, + "placementHeight" : 32.745, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 237.546, + "contentHeight" : 32.745, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/SettlementRow[4]/Settlement[1]/Totals[0]/TotalsGridLayer[0]/TotalsGridHolder[0]/TotalsGrid[0]/TotalsLabels[0]", + "entityName" : "TotalsLabels", + "entityKind" : "SectionNode", + "parentPath" : "PaymentsInvoice[0]/SettlementRow[4]/Settlement[1]/Totals[0]/TotalsGridLayer[0]/TotalsGridHolder[0]/TotalsGrid[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 319.361, + "computedY" : 219.153, + "placementX" : 319.361, + "placementY" : 219.153, + "placementWidth" : 82.968, + "placementHeight" : 32.745, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 82.968, + "contentHeight" : 32.745, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/SettlementRow[4]/Settlement[1]/Totals[0]/TotalsGridLayer[0]/TotalsGridHolder[0]/TotalsGrid[0]/TotalsLabels[0]/TotalsLabel1[0]", + "entityName" : "TotalsLabel1", + "entityKind" : "ParagraphNode", + "parentPath" : "PaymentsInvoice[0]/SettlementRow[4]/Settlement[1]/Totals[0]/TotalsGridLayer[0]/TotalsGridHolder[0]/TotalsGrid[0]/TotalsLabels[0]", + "childIndex" : 0, + "depth" : 9, + "layer" : 9, + "computedX" : 319.361, + "computedY" : 240.594, + "placementX" : 319.361, + "placementY" : 240.594, + "placementWidth" : 82.968, + "placementHeight" : 11.304, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 82.968, + "contentHeight" : 11.304, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/SettlementRow[4]/Settlement[1]/Totals[0]/TotalsGridLayer[0]/TotalsGridHolder[0]/TotalsGrid[0]/TotalsLabels[0]/TotalsLabel2[1]", + "entityName" : "TotalsLabel2", + "entityKind" : "ParagraphNode", + "parentPath" : "PaymentsInvoice[0]/SettlementRow[4]/Settlement[1]/Totals[0]/TotalsGridLayer[0]/TotalsGridHolder[0]/TotalsGrid[0]/TotalsLabels[0]", + "childIndex" : 1, + "depth" : 9, + "layer" : 9, + "computedX" : 319.361, + "computedY" : 219.153, + "placementX" : 319.361, + "placementY" : 219.153, + "placementWidth" : 50.263, + "placementHeight" : 11.304, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 50.263, + "contentHeight" : 11.304, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/SettlementRow[4]/Settlement[1]/Totals[0]/TotalsGridLayer[0]/TotalsGridHolder[0]/TotalsGrid[0]/TotalsValues[1]", + "entityName" : "TotalsValues", + "entityKind" : "SectionNode", + "parentPath" : "PaymentsInvoice[0]/SettlementRow[4]/Settlement[1]/Totals[0]/TotalsGridLayer[0]/TotalsGridHolder[0]/TotalsGrid[0]", + "childIndex" : 1, + "depth" : 8, + "layer" : 8, + "computedX" : 438.134, + "computedY" : 219.381, + "placementX" : 438.134, + "placementY" : 219.381, + "placementWidth" : 118.773, + "placementHeight" : 32.517, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 118.773, + "contentHeight" : 32.517, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/SettlementRow[4]/Settlement[1]/Totals[0]/TotalsGridLayer[0]/TotalsGridHolder[0]/TotalsGrid[0]/TotalsValues[1]/TotalsValue1[0]", + "entityName" : "TotalsValue1", + "entityKind" : "ParagraphNode", + "parentPath" : "PaymentsInvoice[0]/SettlementRow[4]/Settlement[1]/Totals[0]/TotalsGridLayer[0]/TotalsGridHolder[0]/TotalsGrid[0]/TotalsValues[1]", + "childIndex" : 0, + "depth" : 9, + "layer" : 9, + "computedX" : 438.134, + "computedY" : 240.822, + "placementX" : 438.134, + "placementY" : 240.822, + "placementWidth" : 118.773, + "placementHeight" : 11.075, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 118.773, + "contentHeight" : 11.075, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/SettlementRow[4]/Settlement[1]/Totals[0]/TotalsGridLayer[0]/TotalsGridHolder[0]/TotalsGrid[0]/TotalsValues[1]/TotalsValue2[1]", + "entityName" : "TotalsValue2", + "entityKind" : "ParagraphNode", + "parentPath" : "PaymentsInvoice[0]/SettlementRow[4]/Settlement[1]/Totals[0]/TotalsGridLayer[0]/TotalsGridHolder[0]/TotalsGrid[0]/TotalsValues[1]", + "childIndex" : 1, + "depth" : 9, + "layer" : 9, + "computedX" : 438.134, + "computedY" : 219.381, + "placementX" : 438.134, + "placementY" : 219.381, + "placementWidth" : 118.773, + "placementHeight" : 11.075, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 118.773, + "contentHeight" : 11.075, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/SettlementRow[4]/Settlement[1]/TotalsRule[1]", + "entityName" : "TotalsRule", + "entityKind" : "LineNode", + "parentPath" : "PaymentsInvoice[0]/SettlementRow[4]/Settlement[1]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 308.641, + "computedY" : 207.93, + "placementX" : 308.641, + "placementY" : 207.93, + "placementWidth" : 256.73, + "placementHeight" : 1.128, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 256.73, + "contentHeight" : 1.128, + "margin" : { + "top" : 10.094, + "right" : 0.0, + "bottom" : 9.592, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/SettlementRow[4]/Settlement[1]/TotalDuePanel[2]", + "entityName" : "TotalDuePanel", + "entityKind" : "SectionNode", + "parentPath" : "PaymentsInvoice[0]/SettlementRow[4]/Settlement[1]", + "childIndex" : 2, + "depth" : 4, + "layer" : 4, + "computedX" : 308.641, + "computedY" : 153.463, + "placementX" : 308.641, + "placementY" : 153.463, + "placementWidth" : 257.294, + "placementHeight" : 44.875, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 257.294, + "contentHeight" : 44.875, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 9.458, + "right" : 10.156, + "bottom" : 9.028, + "left" : 10.721 + } + }, { + "path" : "PaymentsInvoice[0]/SettlementRow[4]/Settlement[1]/TotalDuePanel[2]/TotalDueRowLayer[0]", + "entityName" : "TotalDueRowLayer", + "entityKind" : "LayerStackNode", + "parentPath" : "PaymentsInvoice[0]/SettlementRow[4]/Settlement[1]/TotalDuePanel[2]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 319.361, + "computedY" : 162.491, + "placementX" : 319.361, + "placementY" : 162.491, + "placementWidth" : 236.418, + "placementHeight" : 26.39, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 236.418, + "contentHeight" : 26.39, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/SettlementRow[4]/Settlement[1]/TotalDuePanel[2]/TotalDueRowLayer[0]/TotalDueRowHolder[0]", + "entityName" : "TotalDueRowHolder", + "entityKind" : "SectionNode", + "parentPath" : "PaymentsInvoice[0]/SettlementRow[4]/Settlement[1]/TotalDuePanel[2]/TotalDueRowLayer[0]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 319.361, + "computedY" : 162.491, + "placementX" : 319.361, + "placementY" : 162.491, + "placementWidth" : 236.418, + "placementHeight" : 26.39, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 236.418, + "contentHeight" : 26.39, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/SettlementRow[4]/Settlement[1]/TotalDuePanel[2]/TotalDueRowLayer[0]/TotalDueRowHolder[0]/TotalDueRow[0]", + "entityName" : "TotalDueRow", + "entityKind" : "RowNode", + "parentPath" : "PaymentsInvoice[0]/SettlementRow[4]/Settlement[1]/TotalDuePanel[2]/TotalDueRowLayer[0]/TotalDueRowHolder[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 319.361, + "computedY" : 162.491, + "placementX" : 319.361, + "placementY" : 162.491, + "placementWidth" : 236.418, + "placementHeight" : 26.39, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 236.418, + "contentHeight" : 26.39, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/SettlementRow[4]/Settlement[1]/TotalDuePanel[2]/TotalDueRowLayer[0]/TotalDueRowHolder[0]/TotalDueRow[0]/TotalDueText[0]", + "entityName" : "TotalDueText", + "entityKind" : "SectionNode", + "parentPath" : "PaymentsInvoice[0]/SettlementRow[4]/Settlement[1]/TotalDuePanel[2]/TotalDueRowLayer[0]/TotalDueRowHolder[0]/TotalDueRow[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 319.361, + "computedY" : 162.491, + "placementX" : 319.361, + "placementY" : 162.491, + "placementWidth" : 80.671, + "placementHeight" : 26.39, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 80.671, + "contentHeight" : 26.39, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/SettlementRow[4]/Settlement[1]/TotalDuePanel[2]/TotalDueRowLayer[0]/TotalDueRowHolder[0]/TotalDueRow[0]/TotalDueText[0]/TotalDueLabel[0]", + "entityName" : "TotalDueLabel", + "entityKind" : "ParagraphNode", + "parentPath" : "PaymentsInvoice[0]/SettlementRow[4]/Settlement[1]/TotalDuePanel[2]/TotalDueRowLayer[0]/TotalDueRowHolder[0]/TotalDueRow[0]/TotalDueText[0]", + "childIndex" : 0, + "depth" : 9, + "layer" : 9, + "computedX" : 319.361, + "computedY" : 176.669, + "placementX" : 319.361, + "placementY" : 176.669, + "placementWidth" : 56.43, + "placementHeight" : 12.212, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 56.43, + "contentHeight" : 12.212, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/SettlementRow[4]/Settlement[1]/TotalDuePanel[2]/TotalDueRowLayer[0]/TotalDueRowHolder[0]/TotalDueRow[0]/TotalDueText[0]/TotalDueSubLabel[1]", + "entityName" : "TotalDueSubLabel", + "entityKind" : "ParagraphNode", + "parentPath" : "PaymentsInvoice[0]/SettlementRow[4]/Settlement[1]/TotalDuePanel[2]/TotalDueRowLayer[0]/TotalDueRowHolder[0]/TotalDueRow[0]/TotalDueText[0]", + "childIndex" : 1, + "depth" : 9, + "layer" : 9, + "computedX" : 319.361, + "computedY" : 162.491, + "placementX" : 319.361, + "placementY" : 162.491, + "placementWidth" : 80.671, + "placementHeight" : 10.197, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 80.671, + "contentHeight" : 10.197, + "margin" : { + "top" : 3.981, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/SettlementRow[4]/Settlement[1]/TotalDuePanel[2]/TotalDueRowLayer[0]/TotalDueRowHolder[0]/TotalDueRow[0]/TotalDueValue[1]", + "entityName" : "TotalDueValue", + "entityKind" : "ParagraphNode", + "parentPath" : "PaymentsInvoice[0]/SettlementRow[4]/Settlement[1]/TotalDuePanel[2]/TotalDueRowLayer[0]/TotalDueRowHolder[0]/TotalDueRow[0]", + "childIndex" : 1, + "depth" : 8, + "layer" : 8, + "computedX" : 437.57, + "computedY" : 165.3, + "placementX" : 437.57, + "placementY" : 165.3, + "placementWidth" : 118.209, + "placementHeight" : 20.772, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 118.209, + "contentHeight" : 20.772, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/Notes[5]", + "entityName" : "Notes", + "entityKind" : "SectionNode", + "parentPath" : "PaymentsInvoice[0]", + "childIndex" : 5, + "depth" : 2, + "layer" : 2, + "computedX" : 29.905, + "computedY" : 102.364, + "placementX" : 29.905, + "placementY" : 102.364, + "placementWidth" : 536.03, + "placementHeight" : 35.3, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 536.03, + "contentHeight" : 35.3, + "margin" : { + "top" : 15.799, + "right" : 0.0, + "bottom" : 0.0, + "left" : 1.128 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/Notes[5]/NotesRow[0]", + "entityName" : "NotesRow", + "entityKind" : "RowNode", + "parentPath" : "PaymentsInvoice[0]/Notes[5]", + "childIndex" : 0, + "depth" : 3, + "layer" : 3, + "computedX" : 29.905, + "computedY" : 102.364, + "placementX" : 29.905, + "placementY" : 102.364, + "placementWidth" : 536.03, + "placementHeight" : 35.3, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 536.03, + "contentHeight" : 35.3, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/Notes[5]/NotesRow[0]/NotesDisc[0]", + "entityName" : "NotesDisc", + "entityKind" : "SectionNode", + "parentPath" : "PaymentsInvoice[0]/Notes[5]/NotesRow[0]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 29.905, + "computedY" : 115.658, + "placementX" : 29.905, + "placementY" : 115.658, + "placementWidth" : 22.005, + "placementHeight" : 22.005, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 22.005, + "contentHeight" : 22.005, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/Notes[5]/NotesRow[0]/NotesDisc[0]/Discdocument[0]", + "entityName" : "Discdocument", + "entityKind" : "ShapeContainerNode", + "parentPath" : "PaymentsInvoice[0]/Notes[5]/NotesRow[0]/NotesDisc[0]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 29.905, + "computedY" : 115.658, + "placementX" : 29.905, + "placementY" : 115.658, + "placementWidth" : 22.005, + "placementHeight" : 22.005, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 22.005, + "contentHeight" : 22.005, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/Notes[5]/NotesRow[0]/NotesDisc[0]/Discdocument[0]/Glyphdocument[0]", + "entityName" : "Glyphdocument", + "entityKind" : "SectionNode", + "parentPath" : "PaymentsInvoice[0]/Notes[5]/NotesRow[0]/NotesDisc[0]/Discdocument[0]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 29.905, + "computedY" : 118.386, + "placementX" : 29.905, + "placementY" : 118.386, + "placementWidth" : 22.005, + "placementHeight" : 16.551, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 22.005, + "contentHeight" : 16.551, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/Notes[5]/NotesRow[0]/NotesDisc[0]/Discdocument[0]/Glyphdocument[0]/Align[0]", + "entityName" : null, + "entityKind" : "Align", + "parentPath" : "PaymentsInvoice[0]/Notes[5]/NotesRow[0]/NotesDisc[0]/Discdocument[0]/Glyphdocument[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 29.905, + "computedY" : 118.386, + "placementX" : 29.905, + "placementY" : 118.386, + "placementWidth" : 22.005, + "placementHeight" : 16.551, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 22.005, + "contentHeight" : 16.551, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/Notes[5]/NotesRow[0]/NotesDisc[0]/Discdocument[0]/Glyphdocument[0]/Align[0]/SvgIcon[0]", + "entityName" : "SvgIcon", + "entityKind" : "LayerStackNode", + "parentPath" : "PaymentsInvoice[0]/Notes[5]/NotesRow[0]/NotesDisc[0]/Discdocument[0]/Glyphdocument[0]/Align[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 32.632, + "computedY" : 118.386, + "placementX" : 32.632, + "placementY" : 118.386, + "placementWidth" : 16.551, + "placementHeight" : 16.551, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 16.551, + "contentHeight" : 16.551, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/Notes[5]/NotesRow[0]/NotesDisc[0]/Discdocument[0]/Glyphdocument[0]/Align[0]/SvgIcon[0]/SvgLayer0[0]", + "entityName" : "SvgLayer0", + "entityKind" : "Path", + "parentPath" : "PaymentsInvoice[0]/Notes[5]/NotesRow[0]/NotesDisc[0]/Discdocument[0]/Glyphdocument[0]/Align[0]/SvgIcon[0]", + "childIndex" : 0, + "depth" : 9, + "layer" : 9, + "computedX" : 32.632, + "computedY" : 118.386, + "placementX" : 32.632, + "placementY" : 118.386, + "placementWidth" : 16.551, + "placementHeight" : 16.551, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 16.551, + "contentHeight" : 16.551, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/Notes[5]/NotesRow[0]/NotesText[1]", + "entityName" : "NotesText", + "entityKind" : "SectionNode", + "parentPath" : "PaymentsInvoice[0]/Notes[5]/NotesRow[0]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 59.81, + "computedY" : 102.364, + "placementX" : 59.81, + "placementY" : 102.364, + "placementWidth" : 328.75, + "placementHeight" : 35.3, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 328.75, + "contentHeight" : 35.3, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/Notes[5]/NotesRow[0]/NotesText[1]/NotesLabel[0]", + "entityName" : "NotesLabel", + "entityKind" : "ParagraphNode", + "parentPath" : "PaymentsInvoice[0]/Notes[5]/NotesRow[0]/NotesText[1]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 59.81, + "computedY" : 127.093, + "placementX" : 59.81, + "placementY" : 127.093, + "placementWidth" : 26.525, + "placementHeight" : 9.637, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 26.525, + "contentHeight" : 9.637, + "margin" : { + "top" : 0.934, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/Notes[5]/NotesRow[0]/NotesText[1]/NotesBody[1]", + "entityName" : "NotesBody", + "entityKind" : "SectionNode", + "parentPath" : "PaymentsInvoice[0]/Notes[5]/NotesRow[0]/NotesText[1]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 59.81, + "computedY" : 102.364, + "placementX" : 59.81, + "placementY" : 102.364, + "placementWidth" : 328.75, + "placementHeight" : 21.929, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 328.75, + "contentHeight" : 21.929, + "margin" : { + "top" : 2.8, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/Notes[5]/NotesRow[0]/NotesText[1]/NotesBody[1]/NotesLine1[0]", + "entityName" : "NotesLine1", + "entityKind" : "ParagraphNode", + "parentPath" : "PaymentsInvoice[0]/Notes[5]/NotesRow[0]/NotesText[1]/NotesBody[1]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 59.81, + "computedY" : 114.777, + "placementX" : 59.81, + "placementY" : 114.777, + "placementWidth" : 328.75, + "placementHeight" : 9.516, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 328.75, + "contentHeight" : 9.516, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/Notes[5]/NotesRow[0]/NotesText[1]/NotesBody[1]/NotesLine2[1]", + "entityName" : "NotesLine2", + "entityKind" : "ParagraphNode", + "parentPath" : "PaymentsInvoice[0]/Notes[5]/NotesRow[0]/NotesText[1]/NotesBody[1]", + "childIndex" : 1, + "depth" : 6, + "layer" : 6, + "computedX" : 59.81, + "computedY" : 102.364, + "placementX" : 59.81, + "placementY" : 102.364, + "placementWidth" : 186.262, + "placementHeight" : 9.516, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 186.262, + "contentHeight" : 9.516, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/FooterRule[6]", + "entityName" : "FooterRule", + "entityKind" : "LineNode", + "parentPath" : "PaymentsInvoice[0]", + "childIndex" : 6, + "depth" : 2, + "layer" : 2, + "computedX" : 28.212, + "computedY" : 83.759, + "placementX" : 28.212, + "placementY" : 83.759, + "placementWidth" : 538.851, + "placementHeight" : 1.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 538.851, + "contentHeight" : 1.0, + "margin" : { + "top" : 17.604, + "right" : -1.128, + "bottom" : 12.3, + "left" : -0.564 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/DocumentFooter[7]", + "entityName" : "DocumentFooter", + "entityKind" : "RowNode", + "parentPath" : "PaymentsInvoice[0]", + "childIndex" : 7, + "depth" : 2, + "layer" : 2, + "computedX" : 28.776, + "computedY" : 34.229, + "placementX" : 28.776, + "placementY" : 34.229, + "placementWidth" : 537.159, + "placementHeight" : 37.23, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 537.159, + "contentHeight" : 37.23, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/DocumentFooter[7]/FooterDue[0]", + "entityName" : "FooterDue", + "entityKind" : "SectionNode", + "parentPath" : "PaymentsInvoice[0]/DocumentFooter[7]", + "childIndex" : 0, + "depth" : 3, + "layer" : 3, + "computedX" : 28.776, + "computedY" : 43.153, + "placementX" : 28.776, + "placementY" : 43.153, + "placementWidth" : 276.479, + "placementHeight" : 28.306, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 276.479, + "contentHeight" : 28.306, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 2.257 + } + }, { + "path" : "PaymentsInvoice[0]/DocumentFooter[7]/FooterDue[0]/FooterDueRowLayer[0]", + "entityName" : "FooterDueRowLayer", + "entityKind" : "LayerStackNode", + "parentPath" : "PaymentsInvoice[0]/DocumentFooter[7]/FooterDue[0]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 31.033, + "computedY" : 43.153, + "placementX" : 31.033, + "placementY" : 43.153, + "placementWidth" : 274.222, + "placementHeight" : 28.306, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 274.222, + "contentHeight" : 28.306, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/DocumentFooter[7]/FooterDue[0]/FooterDueRowLayer[0]/FooterDueRowHolder[0]", + "entityName" : "FooterDueRowHolder", + "entityKind" : "SectionNode", + "parentPath" : "PaymentsInvoice[0]/DocumentFooter[7]/FooterDue[0]/FooterDueRowLayer[0]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 31.033, + "computedY" : 43.153, + "placementX" : 31.033, + "placementY" : 43.153, + "placementWidth" : 274.222, + "placementHeight" : 28.306, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 274.222, + "contentHeight" : 28.306, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/DocumentFooter[7]/FooterDue[0]/FooterDueRowLayer[0]/FooterDueRowHolder[0]/FooterDueRow[0]", + "entityName" : "FooterDueRow", + "entityKind" : "RowNode", + "parentPath" : "PaymentsInvoice[0]/DocumentFooter[7]/FooterDue[0]/FooterDueRowLayer[0]/FooterDueRowHolder[0]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 31.033, + "computedY" : 43.153, + "placementX" : 31.033, + "placementY" : 43.153, + "placementWidth" : 274.222, + "placementHeight" : 24.356, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 274.222, + "contentHeight" : 24.356, + "margin" : { + "top" : 3.95, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/DocumentFooter[7]/FooterDue[0]/FooterDueRowLayer[0]/FooterDueRowHolder[0]/FooterDueRow[0]/FooterDueIcon[0]", + "entityName" : "FooterDueIcon", + "entityKind" : "SectionNode", + "parentPath" : "PaymentsInvoice[0]/DocumentFooter[7]/FooterDue[0]/FooterDueRowLayer[0]/FooterDueRowHolder[0]/FooterDueRow[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 27.554, + "computedY" : 43.153, + "placementX" : 27.554, + "placementY" : 43.153, + "placementWidth" : 35.641, + "placementHeight" : 27.836, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 35.641, + "contentHeight" : 27.836, + "margin" : { + "top" : -3.479, + "right" : 0.0, + "bottom" : 0.0, + "left" : -3.479 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/DocumentFooter[7]/FooterDue[0]/FooterDueRowLayer[0]/FooterDueRowHolder[0]/FooterDueRow[0]/FooterDueIcon[0]/Glyphcalendar[0]", + "entityName" : "Glyphcalendar", + "entityKind" : "SectionNode", + "parentPath" : "PaymentsInvoice[0]/DocumentFooter[7]/FooterDue[0]/FooterDueRowLayer[0]/FooterDueRowHolder[0]/FooterDueRow[0]/FooterDueIcon[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 27.554, + "computedY" : 43.153, + "placementX" : 27.554, + "placementY" : 43.153, + "placementWidth" : 35.641, + "placementHeight" : 27.836, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 35.641, + "contentHeight" : 27.836, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/DocumentFooter[7]/FooterDue[0]/FooterDueRowLayer[0]/FooterDueRowHolder[0]/FooterDueRow[0]/FooterDueIcon[0]/Glyphcalendar[0]/Align[0]", + "entityName" : null, + "entityKind" : "Align", + "parentPath" : "PaymentsInvoice[0]/DocumentFooter[7]/FooterDue[0]/FooterDueRowLayer[0]/FooterDueRowHolder[0]/FooterDueRow[0]/FooterDueIcon[0]/Glyphcalendar[0]", + "childIndex" : 0, + "depth" : 9, + "layer" : 9, + "computedX" : 27.554, + "computedY" : 43.153, + "placementX" : 27.554, + "placementY" : 43.153, + "placementWidth" : 35.641, + "placementHeight" : 27.836, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 35.641, + "contentHeight" : 27.836, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/DocumentFooter[7]/FooterDue[0]/FooterDueRowLayer[0]/FooterDueRowHolder[0]/FooterDueRow[0]/FooterDueIcon[0]/Glyphcalendar[0]/Align[0]/SvgIcon[0]", + "entityName" : "SvgIcon", + "entityKind" : "LayerStackNode", + "parentPath" : "PaymentsInvoice[0]/DocumentFooter[7]/FooterDue[0]/FooterDueRowLayer[0]/FooterDueRowHolder[0]/FooterDueRow[0]/FooterDueIcon[0]/Glyphcalendar[0]/Align[0]", + "childIndex" : 0, + "depth" : 10, + "layer" : 10, + "computedX" : 27.554, + "computedY" : 43.153, + "placementX" : 27.554, + "placementY" : 43.153, + "placementWidth" : 27.836, + "placementHeight" : 27.836, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 27.836, + "contentHeight" : 27.836, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/DocumentFooter[7]/FooterDue[0]/FooterDueRowLayer[0]/FooterDueRowHolder[0]/FooterDueRow[0]/FooterDueIcon[0]/Glyphcalendar[0]/Align[0]/SvgIcon[0]/SvgLayer0[0]", + "entityName" : "SvgLayer0", + "entityKind" : "Path", + "parentPath" : "PaymentsInvoice[0]/DocumentFooter[7]/FooterDue[0]/FooterDueRowLayer[0]/FooterDueRowHolder[0]/FooterDueRow[0]/FooterDueIcon[0]/Glyphcalendar[0]/Align[0]/SvgIcon[0]", + "childIndex" : 0, + "depth" : 11, + "layer" : 11, + "computedX" : 27.554, + "computedY" : 43.153, + "placementX" : 27.554, + "placementY" : 43.153, + "placementWidth" : 27.836, + "placementHeight" : 27.836, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 27.836, + "contentHeight" : 27.836, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/DocumentFooter[7]/FooterDue[0]/FooterDueRowLayer[0]/FooterDueRowHolder[0]/FooterDueRow[0]/FooterDueIcon[0]/Glyphcalendar[0]/Align[0]/SvgIcon[0]/SvgLayer1[1]", + "entityName" : "SvgLayer1", + "entityKind" : "Path", + "parentPath" : "PaymentsInvoice[0]/DocumentFooter[7]/FooterDue[0]/FooterDueRowLayer[0]/FooterDueRowHolder[0]/FooterDueRow[0]/FooterDueIcon[0]/Glyphcalendar[0]/Align[0]/SvgIcon[0]", + "childIndex" : 1, + "depth" : 11, + "layer" : 11, + "computedX" : 27.554, + "computedY" : 43.153, + "placementX" : 27.554, + "placementY" : 43.153, + "placementWidth" : 27.836, + "placementHeight" : 27.836, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 27.836, + "contentHeight" : 27.836, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/DocumentFooter[7]/FooterDue[0]/FooterDueRowLayer[0]/FooterDueRowHolder[0]/FooterDueRow[0]/FooterDueIcon[0]/Glyphcalendar[0]/Align[0]/SvgIcon[0]/SvgLayer2[2]", + "entityName" : "SvgLayer2", + "entityKind" : "Path", + "parentPath" : "PaymentsInvoice[0]/DocumentFooter[7]/FooterDue[0]/FooterDueRowLayer[0]/FooterDueRowHolder[0]/FooterDueRow[0]/FooterDueIcon[0]/Glyphcalendar[0]/Align[0]/SvgIcon[0]", + "childIndex" : 2, + "depth" : 11, + "layer" : 11, + "computedX" : 27.554, + "computedY" : 43.153, + "placementX" : 27.554, + "placementY" : 43.153, + "placementWidth" : 27.836, + "placementHeight" : 27.836, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 27.836, + "contentHeight" : 27.836, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/DocumentFooter[7]/FooterDue[0]/FooterDueRowLayer[0]/FooterDueRowHolder[0]/FooterDueRow[0]/FooterDueText[1]", + "entityName" : "FooterDueText", + "entityKind" : "SectionNode", + "parentPath" : "PaymentsInvoice[0]/DocumentFooter[7]/FooterDue[0]/FooterDueRowLayer[0]/FooterDueRowHolder[0]/FooterDueRow[0]", + "childIndex" : 1, + "depth" : 7, + "layer" : 7, + "computedX" : 63.195, + "computedY" : 46.33, + "placementX" : 63.195, + "placementY" : 46.33, + "placementWidth" : 197.629, + "placementHeight" : 21.179, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 197.629, + "contentHeight" : 21.179, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/DocumentFooter[7]/FooterDue[0]/FooterDueRowLayer[0]/FooterDueRowHolder[0]/FooterDueRow[0]/FooterDueText[1]/FooterDueTitle[0]", + "entityName" : "FooterDueTitle", + "entityKind" : "ParagraphNode", + "parentPath" : "PaymentsInvoice[0]/DocumentFooter[7]/FooterDue[0]/FooterDueRowLayer[0]/FooterDueRowHolder[0]/FooterDueRow[0]/FooterDueText[1]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 63.195, + "computedY" : 57.6, + "placementX" : 63.195, + "placementY" : 57.6, + "placementWidth" : 122.528, + "placementHeight" : 9.515, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 122.528, + "contentHeight" : 9.515, + "margin" : { + "top" : 0.394, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/DocumentFooter[7]/FooterDue[0]/FooterDueRowLayer[0]/FooterDueRowHolder[0]/FooterDueRow[0]/FooterDueText[1]/FooterDueSubtitle[1]", + "entityName" : "FooterDueSubtitle", + "entityKind" : "ParagraphNode", + "parentPath" : "PaymentsInvoice[0]/DocumentFooter[7]/FooterDue[0]/FooterDueRowLayer[0]/FooterDueRowHolder[0]/FooterDueRow[0]/FooterDueText[1]", + "childIndex" : 1, + "depth" : 8, + "layer" : 8, + "computedX" : 63.195, + "computedY" : 46.33, + "placementX" : 63.195, + "placementY" : 46.33, + "placementWidth" : 197.629, + "placementHeight" : 9.497, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 197.629, + "contentHeight" : 9.497, + "margin" : { + "top" : 1.773, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/DocumentFooter[7]/FooterSupport[1]", + "entityName" : "FooterSupport", + "entityKind" : "SectionNode", + "parentPath" : "PaymentsInvoice[0]/DocumentFooter[7]", + "childIndex" : 1, + "depth" : 3, + "layer" : 3, + "computedX" : 305.255, + "computedY" : 34.229, + "placementX" : 305.255, + "placementY" : 34.229, + "placementWidth" : 260.68, + "placementHeight" : 37.23, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 260.68, + "contentHeight" : 37.23, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 29.905 + } + }, { + "path" : "PaymentsInvoice[0]/DocumentFooter[7]/FooterSupport[1]/FooterSupportRowLayer[0]", + "entityName" : "FooterSupportRowLayer", + "entityKind" : "LayerStackNode", + "parentPath" : "PaymentsInvoice[0]/DocumentFooter[7]/FooterSupport[1]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 335.16, + "computedY" : 34.229, + "placementX" : 335.16, + "placementY" : 34.229, + "placementWidth" : 230.775, + "placementHeight" : 37.23, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 230.775, + "contentHeight" : 37.23, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/DocumentFooter[7]/FooterSupport[1]/FooterSupportRowLayer[0]/FooterSupportRowHolder[0]", + "entityName" : "FooterSupportRowHolder", + "entityKind" : "SectionNode", + "parentPath" : "PaymentsInvoice[0]/DocumentFooter[7]/FooterSupport[1]/FooterSupportRowLayer[0]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 335.16, + "computedY" : 34.229, + "placementX" : 335.16, + "placementY" : 34.229, + "placementWidth" : 230.775, + "placementHeight" : 37.23, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 230.775, + "contentHeight" : 37.23, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/DocumentFooter[7]/FooterSupport[1]/FooterSupportRowLayer[0]/FooterSupportRowHolder[0]/FooterSupportRow[0]", + "entityName" : "FooterSupportRow", + "entityKind" : "RowNode", + "parentPath" : "PaymentsInvoice[0]/DocumentFooter[7]/FooterSupport[1]/FooterSupportRowLayer[0]/FooterSupportRowHolder[0]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 335.16, + "computedY" : 34.229, + "placementX" : 335.16, + "placementY" : 34.229, + "placementWidth" : 230.775, + "placementHeight" : 32.716, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 230.775, + "contentHeight" : 32.716, + "margin" : { + "top" : 4.514, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/DocumentFooter[7]/FooterSupport[1]/FooterSupportRowLayer[0]/FooterSupportRowHolder[0]/FooterSupportRow[0]/FooterSupportIcon[0]", + "entityName" : "FooterSupportIcon", + "entityKind" : "SectionNode", + "parentPath" : "PaymentsInvoice[0]/DocumentFooter[7]/FooterSupport[1]/FooterSupportRowLayer[0]/FooterSupportRowHolder[0]/FooterSupportRow[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 331.492, + "computedY" : 41.272, + "placementX" : 331.492, + "placementY" : 41.272, + "placementWidth" : 40.343, + "placementHeight" : 29.341, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 40.343, + "contentHeight" : 29.341, + "margin" : { + "top" : -3.668, + "right" : 0.0, + "bottom" : 0.0, + "left" : -3.668 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/DocumentFooter[7]/FooterSupport[1]/FooterSupportRowLayer[0]/FooterSupportRowHolder[0]/FooterSupportRow[0]/FooterSupportIcon[0]/Glyphsupport[0]", + "entityName" : "Glyphsupport", + "entityKind" : "SectionNode", + "parentPath" : "PaymentsInvoice[0]/DocumentFooter[7]/FooterSupport[1]/FooterSupportRowLayer[0]/FooterSupportRowHolder[0]/FooterSupportRow[0]/FooterSupportIcon[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 331.492, + "computedY" : 41.272, + "placementX" : 331.492, + "placementY" : 41.272, + "placementWidth" : 40.343, + "placementHeight" : 29.341, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 40.343, + "contentHeight" : 29.341, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/DocumentFooter[7]/FooterSupport[1]/FooterSupportRowLayer[0]/FooterSupportRowHolder[0]/FooterSupportRow[0]/FooterSupportIcon[0]/Glyphsupport[0]/Align[0]", + "entityName" : null, + "entityKind" : "Align", + "parentPath" : "PaymentsInvoice[0]/DocumentFooter[7]/FooterSupport[1]/FooterSupportRowLayer[0]/FooterSupportRowHolder[0]/FooterSupportRow[0]/FooterSupportIcon[0]/Glyphsupport[0]", + "childIndex" : 0, + "depth" : 9, + "layer" : 9, + "computedX" : 331.492, + "computedY" : 41.272, + "placementX" : 331.492, + "placementY" : 41.272, + "placementWidth" : 40.343, + "placementHeight" : 29.341, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 40.343, + "contentHeight" : 29.341, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/DocumentFooter[7]/FooterSupport[1]/FooterSupportRowLayer[0]/FooterSupportRowHolder[0]/FooterSupportRow[0]/FooterSupportIcon[0]/Glyphsupport[0]/Align[0]/SvgIcon[0]", + "entityName" : "SvgIcon", + "entityKind" : "LayerStackNode", + "parentPath" : "PaymentsInvoice[0]/DocumentFooter[7]/FooterSupport[1]/FooterSupportRowLayer[0]/FooterSupportRowHolder[0]/FooterSupportRow[0]/FooterSupportIcon[0]/Glyphsupport[0]/Align[0]", + "childIndex" : 0, + "depth" : 10, + "layer" : 10, + "computedX" : 331.492, + "computedY" : 41.272, + "placementX" : 331.492, + "placementY" : 41.272, + "placementWidth" : 29.341, + "placementHeight" : 29.341, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 29.341, + "contentHeight" : 29.341, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/DocumentFooter[7]/FooterSupport[1]/FooterSupportRowLayer[0]/FooterSupportRowHolder[0]/FooterSupportRow[0]/FooterSupportIcon[0]/Glyphsupport[0]/Align[0]/SvgIcon[0]/SvgLayer0[0]", + "entityName" : "SvgLayer0", + "entityKind" : "Path", + "parentPath" : "PaymentsInvoice[0]/DocumentFooter[7]/FooterSupport[1]/FooterSupportRowLayer[0]/FooterSupportRowHolder[0]/FooterSupportRow[0]/FooterSupportIcon[0]/Glyphsupport[0]/Align[0]/SvgIcon[0]", + "childIndex" : 0, + "depth" : 11, + "layer" : 11, + "computedX" : 331.492, + "computedY" : 41.272, + "placementX" : 331.492, + "placementY" : 41.272, + "placementWidth" : 29.341, + "placementHeight" : 29.341, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 29.341, + "contentHeight" : 29.341, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/DocumentFooter[7]/FooterSupport[1]/FooterSupportRowLayer[0]/FooterSupportRowHolder[0]/FooterSupportRow[0]/FooterSupportIcon[0]/Glyphsupport[0]/Align[0]/SvgIcon[0]/SvgLayer1[1]", + "entityName" : "SvgLayer1", + "entityKind" : "Path", + "parentPath" : "PaymentsInvoice[0]/DocumentFooter[7]/FooterSupport[1]/FooterSupportRowLayer[0]/FooterSupportRowHolder[0]/FooterSupportRow[0]/FooterSupportIcon[0]/Glyphsupport[0]/Align[0]/SvgIcon[0]", + "childIndex" : 1, + "depth" : 11, + "layer" : 11, + "computedX" : 331.492, + "computedY" : 41.272, + "placementX" : 331.492, + "placementY" : 41.272, + "placementWidth" : 29.341, + "placementHeight" : 29.341, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 29.341, + "contentHeight" : 29.341, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/DocumentFooter[7]/FooterSupport[1]/FooterSupportRowLayer[0]/FooterSupportRowHolder[0]/FooterSupportRow[0]/FooterSupportText[1]", + "entityName" : "FooterSupportText", + "entityKind" : "SectionNode", + "parentPath" : "PaymentsInvoice[0]/DocumentFooter[7]/FooterSupport[1]/FooterSupportRowLayer[0]/FooterSupportRowHolder[0]/FooterSupportRow[0]", + "childIndex" : 1, + "depth" : 7, + "layer" : 7, + "computedX" : 371.836, + "computedY" : 34.229, + "placementX" : 371.836, + "placementY" : 34.229, + "placementWidth" : 182.481, + "placementHeight" : 32.716, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 182.481, + "contentHeight" : 32.716, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/DocumentFooter[7]/FooterSupport[1]/FooterSupportRowLayer[0]/FooterSupportRowHolder[0]/FooterSupportRow[0]/FooterSupportText[1]/FooterSupportTitle[0]", + "entityName" : "FooterSupportTitle", + "entityKind" : "ParagraphNode", + "parentPath" : "PaymentsInvoice[0]/DocumentFooter[7]/FooterSupport[1]/FooterSupportRowLayer[0]/FooterSupportRowHolder[0]/FooterSupportRow[0]/FooterSupportText[1]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 371.836, + "computedY" : 56.531, + "placementX" : 371.836, + "placementY" : 56.531, + "placementWidth" : 119.69, + "placementHeight" : 10.414, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 119.69, + "contentHeight" : 10.414, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PaymentsInvoice[0]/DocumentFooter[7]/FooterSupport[1]/FooterSupportRowLayer[0]/FooterSupportRowHolder[0]/FooterSupportRow[0]/FooterSupportText[1]/FooterSupportContacts[1]", + "entityName" : "FooterSupportContacts", + "entityKind" : "ParagraphNode", + "parentPath" : "PaymentsInvoice[0]/DocumentFooter[7]/FooterSupport[1]/FooterSupportRowLayer[0]/FooterSupportRowHolder[0]/FooterSupportRow[0]/FooterSupportText[1]", + "childIndex" : 1, + "depth" : 8, + "layer" : 8, + "computedX" : 371.836, + "computedY" : 34.229, + "placementX" : 371.836, + "placementY" : 34.229, + "placementWidth" : 182.481, + "placementHeight" : 18.994, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 182.481, + "contentHeight" : 18.994, + "margin" : { + "top" : 3.307, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + } ] +} diff --git a/qa/src/test/resources/layout-snapshots/canonical-templates/invoice/platform_invoice_layout.json b/qa/src/test/resources/layout-snapshots/canonical-templates/invoice/platform_invoice_layout.json new file mode 100644 index 000000000..3e0f7b796 --- /dev/null +++ b/qa/src/test/resources/layout-snapshots/canonical-templates/invoice/platform_invoice_layout.json @@ -0,0 +1,5777 @@ +{ + "formatVersion" : "2.0", + "canvas" : { + "pageWidth" : 595.276, + "pageHeight" : 841.89, + "innerWidth" : 544.119, + "innerHeight" : 814.941, + "margin" : { + "top" : 14.948, + "right" : 25.578, + "bottom" : 12.0, + "left" : 25.578 + } + }, + "totalPages" : 1, + "nodes" : [ { + "path" : "PlatformInvoice[0]", + "entityName" : "PlatformInvoice", + "entityKind" : "ContainerNode", + "parentPath" : null, + "childIndex" : 0, + "depth" : 1, + "layer" : 1, + "computedX" : 25.578, + "computedY" : 14.039, + "placementX" : 25.578, + "placementY" : 14.039, + "placementWidth" : 544.119, + "placementHeight" : 812.902, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 544.119, + "contentHeight" : 812.902, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/Masthead[0]", + "entityName" : "Masthead", + "entityKind" : "RowNode", + "parentPath" : "PlatformInvoice[0]", + "childIndex" : 0, + "depth" : 2, + "layer" : 2, + "computedX" : 25.578, + "computedY" : 788.076, + "placementX" : 25.578, + "placementY" : 788.076, + "placementWidth" : 544.119, + "placementHeight" : 38.866, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 544.119, + "contentHeight" : 38.866, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/Masthead[0]/BrandLockup[0]", + "entityName" : "BrandLockup", + "entityKind" : "SectionNode", + "parentPath" : "PlatformInvoice[0]/Masthead[0]", + "childIndex" : 0, + "depth" : 3, + "layer" : 3, + "computedX" : 25.578, + "computedY" : 802.924, + "placementX" : 25.578, + "placementY" : 802.924, + "placementWidth" : 76.894, + "placementHeight" : 24.017, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 76.894, + "contentHeight" : 24.017, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/Masthead[0]/BrandLockup[0]/BrandWordmark[0]", + "entityName" : "BrandWordmark", + "entityKind" : "ParagraphNode", + "parentPath" : "PlatformInvoice[0]/Masthead[0]/BrandLockup[0]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 25.578, + "computedY" : 802.924, + "placementX" : 25.578, + "placementY" : 802.924, + "placementWidth" : 76.894, + "placementHeight" : 15.945, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 76.894, + "contentHeight" : 15.945, + "margin" : { + "top" : 8.072, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/Masthead[0]/InvoiceTitle[1]", + "entityName" : "InvoiceTitle", + "entityKind" : "ParagraphNode", + "parentPath" : "PlatformInvoice[0]/Masthead[0]", + "childIndex" : 1, + "depth" : 3, + "layer" : 3, + "computedX" : 195.325, + "computedY" : 788.076, + "placementX" : 195.325, + "placementY" : 788.076, + "placementWidth" : 374.373, + "placementHeight" : 38.866, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 374.373, + "contentHeight" : 38.866, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/IdentityRow[1]", + "entityName" : "IdentityRow", + "entityKind" : "RowNode", + "parentPath" : "PlatformInvoice[0]", + "childIndex" : 1, + "depth" : 2, + "layer" : 2, + "computedX" : 25.578, + "computedY" : 632.115, + "placementX" : 25.578, + "placementY" : 632.115, + "placementWidth" : 544.119, + "placementHeight" : 139.767, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 544.119, + "contentHeight" : 139.767, + "margin" : { + "top" : 16.194, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/IdentityRow[1]/SupplierColumn[0]", + "entityName" : "SupplierColumn", + "entityKind" : "SectionNode", + "parentPath" : "PlatformInvoice[0]/IdentityRow[1]", + "childIndex" : 0, + "depth" : 3, + "layer" : 3, + "computedX" : 25.578, + "computedY" : 635.055, + "placementX" : 25.578, + "placementY" : 635.055, + "placementWidth" : 127.15, + "placementHeight" : 136.827, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 127.15, + "contentHeight" : 136.827, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 3.488 + } + }, { + "path" : "PlatformInvoice[0]/IdentityRow[1]/SupplierColumn[0]/SupplierName[0]", + "entityName" : "SupplierName", + "entityKind" : "ParagraphNode", + "parentPath" : "PlatformInvoice[0]/IdentityRow[1]/SupplierColumn[0]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 29.066, + "computedY" : 755.937, + "placementX" : 29.066, + "placementY" : 755.937, + "placementWidth" : 101.037, + "placementHeight" : 15.945, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 101.037, + "contentHeight" : 15.945, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/IdentityRow[1]/SupplierColumn[0]/SupplierAddress[1]", + "entityName" : "SupplierAddress", + "entityKind" : "SectionNode", + "parentPath" : "PlatformInvoice[0]/IdentityRow[1]/SupplierColumn[0]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 29.066, + "computedY" : 711.79, + "placementX" : 29.066, + "placementY" : 711.79, + "placementWidth" : 103.353, + "placementHeight" : 38.268, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 103.353, + "contentHeight" : 38.268, + "margin" : { + "top" : 5.88, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/IdentityRow[1]/SupplierColumn[0]/SupplierAddress[1]/SupplierAddress_0[0]", + "entityName" : "SupplierAddress_0", + "entityKind" : "ParagraphNode", + "parentPath" : "PlatformInvoice[0]/IdentityRow[1]/SupplierColumn[0]/SupplierAddress[1]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 29.066, + "computedY" : 738.099, + "placementX" : 29.066, + "placementY" : 738.099, + "placementWidth" : 82.774, + "placementHeight" : 11.959, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 82.774, + "contentHeight" : 11.959, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/IdentityRow[1]/SupplierColumn[0]/SupplierAddress[1]/SupplierAddress_1[1]", + "entityName" : "SupplierAddress_1", + "entityKind" : "ParagraphNode", + "parentPath" : "PlatformInvoice[0]/IdentityRow[1]/SupplierColumn[0]/SupplierAddress[1]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 29.066, + "computedY" : 724.944, + "placementX" : 29.066, + "placementY" : 724.944, + "placementWidth" : 103.353, + "placementHeight" : 11.959, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 103.353, + "contentHeight" : 11.959, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/IdentityRow[1]/SupplierColumn[0]/SupplierAddress[1]/SupplierAddress_2[2]", + "entityName" : "SupplierAddress_2", + "entityKind" : "ParagraphNode", + "parentPath" : "PlatformInvoice[0]/IdentityRow[1]/SupplierColumn[0]/SupplierAddress[1]", + "childIndex" : 2, + "depth" : 5, + "layer" : 5, + "computedX" : 29.066, + "computedY" : 711.79, + "placementX" : 29.066, + "placementY" : 711.79, + "placementWidth" : 32.139, + "placementHeight" : 11.959, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 32.139, + "contentHeight" : 11.959, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/IdentityRow[1]/SupplierColumn[0]/SupplierContact[2]", + "entityName" : "SupplierContact", + "entityKind" : "SectionNode", + "parentPath" : "PlatformInvoice[0]/IdentityRow[1]/SupplierColumn[0]", + "childIndex" : 2, + "depth" : 4, + "layer" : 4, + "computedX" : 29.066, + "computedY" : 655.883, + "placementX" : 29.066, + "placementY" : 655.883, + "placementWidth" : 123.663, + "placementHeight" : 42.653, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 123.663, + "contentHeight" : 42.653, + "margin" : { + "top" : 13.254, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/IdentityRow[1]/SupplierColumn[0]/SupplierContact[2]/SupplierContactWebsite[0]", + "entityName" : "SupplierContactWebsite", + "entityKind" : "ParagraphNode", + "parentPath" : "PlatformInvoice[0]/IdentityRow[1]/SupplierColumn[0]/SupplierContact[2]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 29.066, + "computedY" : 686.577, + "placementX" : 29.066, + "placementY" : 686.577, + "placementWidth" : 89.61, + "placementHeight" : 11.959, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 89.61, + "contentHeight" : 11.959, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/IdentityRow[1]/SupplierColumn[0]/SupplierContact[2]/SupplierContactEmail[1]", + "entityName" : "SupplierContactEmail", + "entityKind" : "ParagraphNode", + "parentPath" : "PlatformInvoice[0]/IdentityRow[1]/SupplierColumn[0]/SupplierContact[2]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 29.066, + "computedY" : 671.23, + "placementX" : 29.066, + "placementY" : 671.23, + "placementWidth" : 123.663, + "placementHeight" : 11.959, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 123.663, + "contentHeight" : 11.959, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/IdentityRow[1]/SupplierColumn[0]/SupplierContact[2]/SupplierContactPhone[2]", + "entityName" : "SupplierContactPhone", + "entityKind" : "ParagraphNode", + "parentPath" : "PlatformInvoice[0]/IdentityRow[1]/SupplierColumn[0]/SupplierContact[2]", + "childIndex" : 2, + "depth" : 5, + "layer" : 5, + "computedX" : 29.066, + "computedY" : 655.883, + "placementX" : 29.066, + "placementY" : 655.883, + "placementWidth" : 92.002, + "placementHeight" : 11.959, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 92.002, + "contentHeight" : 11.959, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/IdentityRow[1]/SupplierColumn[0]/SupplierTaxId[3]", + "entityName" : "SupplierTaxId", + "entityKind" : "ParagraphNode", + "parentPath" : "PlatformInvoice[0]/IdentityRow[1]/SupplierColumn[0]", + "childIndex" : 3, + "depth" : 4, + "layer" : 4, + "computedX" : 29.066, + "computedY" : 635.055, + "placementX" : 29.066, + "placementY" : 635.055, + "placementWidth" : 119.039, + "placementHeight" : 11.959, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 119.039, + "contentHeight" : 11.959, + "margin" : { + "top" : 8.869, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/IdentityRow[1]/IdentityColumnDivider[1]", + "entityName" : "IdentityColumnDivider", + "entityKind" : "SectionNode", + "parentPath" : "PlatformInvoice[0]/IdentityRow[1]", + "childIndex" : 1, + "depth" : 3, + "layer" : 3, + "computedX" : 294.731, + "computedY" : 632.115, + "placementX" : 294.731, + "placementY" : 632.115, + "placementWidth" : 0.581, + "placementHeight" : 139.767, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 0.581, + "contentHeight" : 139.767, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/IdentityRow[1]/IdentityColumnDivider[1]/IdentityColumnDivider[0]", + "entityName" : "IdentityColumnDivider", + "entityKind" : "LineNode", + "parentPath" : "PlatformInvoice[0]/IdentityRow[1]/IdentityColumnDivider[1]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 294.731, + "computedY" : 632.115, + "placementX" : 294.731, + "placementY" : 632.115, + "placementWidth" : 1.0, + "placementHeight" : 139.767, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 1.0, + "contentHeight" : 139.767, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/IdentityRow[1]/MetaColumn[2]", + "entityName" : "MetaColumn", + "entityKind" : "SectionNode", + "parentPath" : "PlatformInvoice[0]/IdentityRow[1]", + "childIndex" : 2, + "depth" : 3, + "layer" : 3, + "computedX" : 295.313, + "computedY" : 638.332, + "placementX" : 295.313, + "placementY" : 638.332, + "placementWidth" : 274.385, + "placementHeight" : 130.908, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 274.385, + "contentHeight" : 130.908, + "margin" : { + "top" : 2.641, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 37.786 + } + }, { + "path" : "PlatformInvoice[0]/IdentityRow[1]/MetaColumn[2]/MetaRow_0Layer[0]", + "entityName" : "MetaRow_0Layer", + "entityKind" : "LayerStackNode", + "parentPath" : "PlatformInvoice[0]/IdentityRow[1]/MetaColumn[2]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 333.099, + "computedY" : 757.282, + "placementX" : 333.099, + "placementY" : 757.282, + "placementWidth" : 236.599, + "placementHeight" : 11.959, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 236.599, + "contentHeight" : 11.959, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/IdentityRow[1]/MetaColumn[2]/MetaRow_0Layer[0]/MetaRow_0Holder[0]", + "entityName" : "MetaRow_0Holder", + "entityKind" : "SectionNode", + "parentPath" : "PlatformInvoice[0]/IdentityRow[1]/MetaColumn[2]/MetaRow_0Layer[0]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 333.099, + "computedY" : 757.282, + "placementX" : 333.099, + "placementY" : 757.282, + "placementWidth" : 236.599, + "placementHeight" : 11.959, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 236.599, + "contentHeight" : 11.959, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/IdentityRow[1]/MetaColumn[2]/MetaRow_0Layer[0]/MetaRow_0Holder[0]/MetaRow_0[0]", + "entityName" : "MetaRow_0", + "entityKind" : "RowNode", + "parentPath" : "PlatformInvoice[0]/IdentityRow[1]/MetaColumn[2]/MetaRow_0Layer[0]/MetaRow_0Holder[0]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 333.099, + "computedY" : 757.282, + "placementX" : 333.099, + "placementY" : 757.282, + "placementWidth" : 236.599, + "placementHeight" : 11.959, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 236.599, + "contentHeight" : 11.959, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/IdentityRow[1]/MetaColumn[2]/MetaRow_0Layer[0]/MetaRow_0Holder[0]/MetaRow_0[0]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "PlatformInvoice[0]/IdentityRow[1]/MetaColumn[2]/MetaRow_0Layer[0]/MetaRow_0Holder[0]/MetaRow_0[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 333.099, + "computedY" : 757.282, + "placementX" : 333.099, + "placementY" : 757.282, + "placementWidth" : 71.224, + "placementHeight" : 11.959, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 71.224, + "contentHeight" : 11.959, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/IdentityRow[1]/MetaColumn[2]/MetaRow_0Layer[0]/MetaRow_0Holder[0]/MetaRow_0[0]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "PlatformInvoice[0]/IdentityRow[1]/MetaColumn[2]/MetaRow_0Layer[0]/MetaRow_0Holder[0]/MetaRow_0[0]", + "childIndex" : 1, + "depth" : 7, + "layer" : 7, + "computedX" : 434.83, + "computedY" : 757.282, + "placementX" : 434.83, + "placementY" : 757.282, + "placementWidth" : 106.203, + "placementHeight" : 11.959, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 106.203, + "contentHeight" : 11.959, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/IdentityRow[1]/MetaColumn[2]/MetaRow_1Layer[1]", + "entityName" : "MetaRow_1Layer", + "entityKind" : "LayerStackNode", + "parentPath" : "PlatformInvoice[0]/IdentityRow[1]/MetaColumn[2]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 333.099, + "computedY" : 737.457, + "placementX" : 333.099, + "placementY" : 737.457, + "placementWidth" : 236.599, + "placementHeight" : 11.959, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 236.599, + "contentHeight" : 11.959, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/IdentityRow[1]/MetaColumn[2]/MetaRow_1Layer[1]/MetaRow_1Holder[0]", + "entityName" : "MetaRow_1Holder", + "entityKind" : "SectionNode", + "parentPath" : "PlatformInvoice[0]/IdentityRow[1]/MetaColumn[2]/MetaRow_1Layer[1]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 333.099, + "computedY" : 737.457, + "placementX" : 333.099, + "placementY" : 737.457, + "placementWidth" : 236.599, + "placementHeight" : 11.959, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 236.599, + "contentHeight" : 11.959, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/IdentityRow[1]/MetaColumn[2]/MetaRow_1Layer[1]/MetaRow_1Holder[0]/MetaRow_1[0]", + "entityName" : "MetaRow_1", + "entityKind" : "RowNode", + "parentPath" : "PlatformInvoice[0]/IdentityRow[1]/MetaColumn[2]/MetaRow_1Layer[1]/MetaRow_1Holder[0]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 333.099, + "computedY" : 737.457, + "placementX" : 333.099, + "placementY" : 737.457, + "placementWidth" : 236.599, + "placementHeight" : 11.959, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 236.599, + "contentHeight" : 11.959, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/IdentityRow[1]/MetaColumn[2]/MetaRow_1Layer[1]/MetaRow_1Holder[0]/MetaRow_1[0]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "PlatformInvoice[0]/IdentityRow[1]/MetaColumn[2]/MetaRow_1Layer[1]/MetaRow_1Holder[0]/MetaRow_1[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 333.099, + "computedY" : 737.457, + "placementX" : 333.099, + "placementY" : 737.457, + "placementWidth" : 56.714, + "placementHeight" : 11.959, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 56.714, + "contentHeight" : 11.959, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/IdentityRow[1]/MetaColumn[2]/MetaRow_1Layer[1]/MetaRow_1Holder[0]/MetaRow_1[0]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "PlatformInvoice[0]/IdentityRow[1]/MetaColumn[2]/MetaRow_1Layer[1]/MetaRow_1Holder[0]/MetaRow_1[0]", + "childIndex" : 1, + "depth" : 7, + "layer" : 7, + "computedX" : 434.83, + "computedY" : 737.457, + "placementX" : 434.83, + "placementY" : 737.457, + "placementWidth" : 52.03, + "placementHeight" : 11.959, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 52.03, + "contentHeight" : 11.959, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/IdentityRow[1]/MetaColumn[2]/MetaRow_2Layer[2]", + "entityName" : "MetaRow_2Layer", + "entityKind" : "LayerStackNode", + "parentPath" : "PlatformInvoice[0]/IdentityRow[1]/MetaColumn[2]", + "childIndex" : 2, + "depth" : 4, + "layer" : 4, + "computedX" : 333.099, + "computedY" : 717.632, + "placementX" : 333.099, + "placementY" : 717.632, + "placementWidth" : 236.599, + "placementHeight" : 11.959, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 236.599, + "contentHeight" : 11.959, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/IdentityRow[1]/MetaColumn[2]/MetaRow_2Layer[2]/MetaRow_2Holder[0]", + "entityName" : "MetaRow_2Holder", + "entityKind" : "SectionNode", + "parentPath" : "PlatformInvoice[0]/IdentityRow[1]/MetaColumn[2]/MetaRow_2Layer[2]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 333.099, + "computedY" : 717.632, + "placementX" : 333.099, + "placementY" : 717.632, + "placementWidth" : 236.599, + "placementHeight" : 11.959, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 236.599, + "contentHeight" : 11.959, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/IdentityRow[1]/MetaColumn[2]/MetaRow_2Layer[2]/MetaRow_2Holder[0]/MetaRow_2[0]", + "entityName" : "MetaRow_2", + "entityKind" : "RowNode", + "parentPath" : "PlatformInvoice[0]/IdentityRow[1]/MetaColumn[2]/MetaRow_2Layer[2]/MetaRow_2Holder[0]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 333.099, + "computedY" : 717.632, + "placementX" : 333.099, + "placementY" : 717.632, + "placementWidth" : 236.599, + "placementHeight" : 11.959, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 236.599, + "contentHeight" : 11.959, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/IdentityRow[1]/MetaColumn[2]/MetaRow_2Layer[2]/MetaRow_2Holder[0]/MetaRow_2[0]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "PlatformInvoice[0]/IdentityRow[1]/MetaColumn[2]/MetaRow_2Layer[2]/MetaRow_2Holder[0]/MetaRow_2[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 333.099, + "computedY" : 717.632, + "placementX" : 333.099, + "placementY" : 717.632, + "placementWidth" : 42.374, + "placementHeight" : 11.959, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 42.374, + "contentHeight" : 11.959, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/IdentityRow[1]/MetaColumn[2]/MetaRow_2Layer[2]/MetaRow_2Holder[0]/MetaRow_2[0]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "PlatformInvoice[0]/IdentityRow[1]/MetaColumn[2]/MetaRow_2Layer[2]/MetaRow_2Holder[0]/MetaRow_2[0]", + "childIndex" : 1, + "depth" : 7, + "layer" : 7, + "computedX" : 434.83, + "computedY" : 717.632, + "placementX" : 434.83, + "placementY" : 717.632, + "placementWidth" : 66.68, + "placementHeight" : 11.959, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 66.68, + "contentHeight" : 11.959, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/IdentityRow[1]/MetaColumn[2]/MetaRow_3Layer[3]", + "entityName" : "MetaRow_3Layer", + "entityKind" : "LayerStackNode", + "parentPath" : "PlatformInvoice[0]/IdentityRow[1]/MetaColumn[2]", + "childIndex" : 3, + "depth" : 4, + "layer" : 4, + "computedX" : 333.099, + "computedY" : 697.807, + "placementX" : 333.099, + "placementY" : 697.807, + "placementWidth" : 236.599, + "placementHeight" : 11.959, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 236.599, + "contentHeight" : 11.959, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/IdentityRow[1]/MetaColumn[2]/MetaRow_3Layer[3]/MetaRow_3Holder[0]", + "entityName" : "MetaRow_3Holder", + "entityKind" : "SectionNode", + "parentPath" : "PlatformInvoice[0]/IdentityRow[1]/MetaColumn[2]/MetaRow_3Layer[3]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 333.099, + "computedY" : 697.807, + "placementX" : 333.099, + "placementY" : 697.807, + "placementWidth" : 236.599, + "placementHeight" : 11.959, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 236.599, + "contentHeight" : 11.959, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/IdentityRow[1]/MetaColumn[2]/MetaRow_3Layer[3]/MetaRow_3Holder[0]/MetaRow_3[0]", + "entityName" : "MetaRow_3", + "entityKind" : "RowNode", + "parentPath" : "PlatformInvoice[0]/IdentityRow[1]/MetaColumn[2]/MetaRow_3Layer[3]/MetaRow_3Holder[0]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 333.099, + "computedY" : 697.807, + "placementX" : 333.099, + "placementY" : 697.807, + "placementWidth" : 236.599, + "placementHeight" : 11.959, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 236.599, + "contentHeight" : 11.959, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/IdentityRow[1]/MetaColumn[2]/MetaRow_3Layer[3]/MetaRow_3Holder[0]/MetaRow_3[0]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "PlatformInvoice[0]/IdentityRow[1]/MetaColumn[2]/MetaRow_3Layer[3]/MetaRow_3Holder[0]/MetaRow_3[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 333.099, + "computedY" : 697.807, + "placementX" : 333.099, + "placementY" : 697.807, + "placementWidth" : 78.907, + "placementHeight" : 11.959, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 78.907, + "contentHeight" : 11.959, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/IdentityRow[1]/MetaColumn[2]/MetaRow_3Layer[3]/MetaRow_3Holder[0]/MetaRow_3[0]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "PlatformInvoice[0]/IdentityRow[1]/MetaColumn[2]/MetaRow_3Layer[3]/MetaRow_3Holder[0]/MetaRow_3[0]", + "childIndex" : 1, + "depth" : 7, + "layer" : 7, + "computedX" : 434.83, + "computedY" : 697.807, + "placementX" : 434.83, + "placementY" : 697.807, + "placementWidth" : 108.475, + "placementHeight" : 11.959, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 108.475, + "contentHeight" : 11.959, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/IdentityRow[1]/MetaColumn[2]/MetaRow_4Layer[4]", + "entityName" : "MetaRow_4Layer", + "entityKind" : "LayerStackNode", + "parentPath" : "PlatformInvoice[0]/IdentityRow[1]/MetaColumn[2]", + "childIndex" : 4, + "depth" : 4, + "layer" : 4, + "computedX" : 333.099, + "computedY" : 677.982, + "placementX" : 333.099, + "placementY" : 677.982, + "placementWidth" : 236.599, + "placementHeight" : 11.959, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 236.599, + "contentHeight" : 11.959, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/IdentityRow[1]/MetaColumn[2]/MetaRow_4Layer[4]/MetaRow_4Holder[0]", + "entityName" : "MetaRow_4Holder", + "entityKind" : "SectionNode", + "parentPath" : "PlatformInvoice[0]/IdentityRow[1]/MetaColumn[2]/MetaRow_4Layer[4]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 333.099, + "computedY" : 677.982, + "placementX" : 333.099, + "placementY" : 677.982, + "placementWidth" : 236.599, + "placementHeight" : 11.959, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 236.599, + "contentHeight" : 11.959, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/IdentityRow[1]/MetaColumn[2]/MetaRow_4Layer[4]/MetaRow_4Holder[0]/MetaRow_4[0]", + "entityName" : "MetaRow_4", + "entityKind" : "RowNode", + "parentPath" : "PlatformInvoice[0]/IdentityRow[1]/MetaColumn[2]/MetaRow_4Layer[4]/MetaRow_4Holder[0]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 333.099, + "computedY" : 677.982, + "placementX" : 333.099, + "placementY" : 677.982, + "placementWidth" : 236.599, + "placementHeight" : 11.959, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 236.599, + "contentHeight" : 11.959, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/IdentityRow[1]/MetaColumn[2]/MetaRow_4Layer[4]/MetaRow_4Holder[0]/MetaRow_4[0]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "PlatformInvoice[0]/IdentityRow[1]/MetaColumn[2]/MetaRow_4Layer[4]/MetaRow_4Holder[0]/MetaRow_4[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 333.099, + "computedY" : 677.982, + "placementX" : 333.099, + "placementY" : 677.982, + "placementWidth" : 60.242, + "placementHeight" : 11.959, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 60.242, + "contentHeight" : 11.959, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/IdentityRow[1]/MetaColumn[2]/MetaRow_4Layer[4]/MetaRow_4Holder[0]/MetaRow_4[0]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "PlatformInvoice[0]/IdentityRow[1]/MetaColumn[2]/MetaRow_4Layer[4]/MetaRow_4Holder[0]/MetaRow_4[0]", + "childIndex" : 1, + "depth" : 7, + "layer" : 7, + "computedX" : 434.83, + "computedY" : 677.982, + "placementX" : 434.83, + "placementY" : 677.982, + "placementWidth" : 103.841, + "placementHeight" : 11.959, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 103.841, + "contentHeight" : 11.959, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/IdentityRow[1]/MetaColumn[2]/MetaRow_5Layer[5]", + "entityName" : "MetaRow_5Layer", + "entityKind" : "LayerStackNode", + "parentPath" : "PlatformInvoice[0]/IdentityRow[1]/MetaColumn[2]", + "childIndex" : 5, + "depth" : 4, + "layer" : 4, + "computedX" : 333.099, + "computedY" : 658.157, + "placementX" : 333.099, + "placementY" : 658.157, + "placementWidth" : 236.599, + "placementHeight" : 11.959, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 236.599, + "contentHeight" : 11.959, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/IdentityRow[1]/MetaColumn[2]/MetaRow_5Layer[5]/MetaRow_5Holder[0]", + "entityName" : "MetaRow_5Holder", + "entityKind" : "SectionNode", + "parentPath" : "PlatformInvoice[0]/IdentityRow[1]/MetaColumn[2]/MetaRow_5Layer[5]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 333.099, + "computedY" : 658.157, + "placementX" : 333.099, + "placementY" : 658.157, + "placementWidth" : 236.599, + "placementHeight" : 11.959, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 236.599, + "contentHeight" : 11.959, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/IdentityRow[1]/MetaColumn[2]/MetaRow_5Layer[5]/MetaRow_5Holder[0]/MetaRow_5[0]", + "entityName" : "MetaRow_5", + "entityKind" : "RowNode", + "parentPath" : "PlatformInvoice[0]/IdentityRow[1]/MetaColumn[2]/MetaRow_5Layer[5]/MetaRow_5Holder[0]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 333.099, + "computedY" : 658.157, + "placementX" : 333.099, + "placementY" : 658.157, + "placementWidth" : 236.599, + "placementHeight" : 11.959, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 236.599, + "contentHeight" : 11.959, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/IdentityRow[1]/MetaColumn[2]/MetaRow_5Layer[5]/MetaRow_5Holder[0]/MetaRow_5[0]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "PlatformInvoice[0]/IdentityRow[1]/MetaColumn[2]/MetaRow_5Layer[5]/MetaRow_5Holder[0]/MetaRow_5[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 333.099, + "computedY" : 658.157, + "placementX" : 333.099, + "placementY" : 658.157, + "placementWidth" : 71.154, + "placementHeight" : 11.959, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 71.154, + "contentHeight" : 11.959, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/IdentityRow[1]/MetaColumn[2]/MetaRow_5Layer[5]/MetaRow_5Holder[0]/MetaRow_5[0]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "PlatformInvoice[0]/IdentityRow[1]/MetaColumn[2]/MetaRow_5Layer[5]/MetaRow_5Holder[0]/MetaRow_5[0]", + "childIndex" : 1, + "depth" : 7, + "layer" : 7, + "computedX" : 434.83, + "computedY" : 658.157, + "placementX" : 434.83, + "placementY" : 658.157, + "placementWidth" : 51.063, + "placementHeight" : 11.959, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 51.063, + "contentHeight" : 11.959, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/IdentityRow[1]/MetaColumn[2]/MetaRow_6Layer[6]", + "entityName" : "MetaRow_6Layer", + "entityKind" : "LayerStackNode", + "parentPath" : "PlatformInvoice[0]/IdentityRow[1]/MetaColumn[2]", + "childIndex" : 6, + "depth" : 4, + "layer" : 4, + "computedX" : 333.099, + "computedY" : 638.332, + "placementX" : 333.099, + "placementY" : 638.332, + "placementWidth" : 236.599, + "placementHeight" : 11.959, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 236.599, + "contentHeight" : 11.959, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/IdentityRow[1]/MetaColumn[2]/MetaRow_6Layer[6]/MetaRow_6Holder[0]", + "entityName" : "MetaRow_6Holder", + "entityKind" : "SectionNode", + "parentPath" : "PlatformInvoice[0]/IdentityRow[1]/MetaColumn[2]/MetaRow_6Layer[6]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 333.099, + "computedY" : 638.332, + "placementX" : 333.099, + "placementY" : 638.332, + "placementWidth" : 236.599, + "placementHeight" : 11.959, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 236.599, + "contentHeight" : 11.959, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/IdentityRow[1]/MetaColumn[2]/MetaRow_6Layer[6]/MetaRow_6Holder[0]/MetaRow_6[0]", + "entityName" : "MetaRow_6", + "entityKind" : "RowNode", + "parentPath" : "PlatformInvoice[0]/IdentityRow[1]/MetaColumn[2]/MetaRow_6Layer[6]/MetaRow_6Holder[0]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 333.099, + "computedY" : 638.332, + "placementX" : 333.099, + "placementY" : 638.332, + "placementWidth" : 236.599, + "placementHeight" : 11.959, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 236.599, + "contentHeight" : 11.959, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/IdentityRow[1]/MetaColumn[2]/MetaRow_6Layer[6]/MetaRow_6Holder[0]/MetaRow_6[0]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "PlatformInvoice[0]/IdentityRow[1]/MetaColumn[2]/MetaRow_6Layer[6]/MetaRow_6Holder[0]/MetaRow_6[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 333.099, + "computedY" : 638.332, + "placementX" : 333.099, + "placementY" : 638.332, + "placementWidth" : 42.483, + "placementHeight" : 11.959, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 42.483, + "contentHeight" : 11.959, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/IdentityRow[1]/MetaColumn[2]/MetaRow_6Layer[6]/MetaRow_6Holder[0]/MetaRow_6[0]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "PlatformInvoice[0]/IdentityRow[1]/MetaColumn[2]/MetaRow_6Layer[6]/MetaRow_6Holder[0]/MetaRow_6[0]", + "childIndex" : 1, + "depth" : 7, + "layer" : 7, + "computedX" : 434.83, + "computedY" : 638.332, + "placementX" : 434.83, + "placementY" : 638.332, + "placementWidth" : 18.257, + "placementHeight" : 11.959, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 18.257, + "contentHeight" : 11.959, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/IdentityRule[2]", + "entityName" : "IdentityRule", + "entityKind" : "LineNode", + "parentPath" : "PlatformInvoice[0]", + "childIndex" : 2, + "depth" : 2, + "layer" : 2, + "computedX" : 28.485, + "computedY" : 617.412, + "placementX" : 28.485, + "placementY" : 617.412, + "placementWidth" : 537.143, + "placementHeight" : 1.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 537.143, + "contentHeight" : 1.0, + "margin" : { + "top" : 13.703, + "right" : 0.0, + "bottom" : 0.0, + "left" : 2.907 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/PartiesRow[3]", + "entityName" : "PartiesRow", + "entityKind" : "RowNode", + "parentPath" : "PlatformInvoice[0]", + "childIndex" : 3, + "depth" : 2, + "layer" : 2, + "computedX" : 25.578, + "computedY" : 500.666, + "placementX" : 25.578, + "placementY" : 500.666, + "placementWidth" : 544.119, + "placementHeight" : 104.14, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 544.119, + "contentHeight" : 104.14, + "margin" : { + "top" : 12.606, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/PartiesRow[3]/BillToColumn[0]", + "entityName" : "BillToColumn", + "entityKind" : "SectionNode", + "parentPath" : "PlatformInvoice[0]/PartiesRow[3]", + "childIndex" : 0, + "depth" : 3, + "layer" : 3, + "computedX" : 25.578, + "computedY" : 506.167, + "placementX" : 25.578, + "placementY" : 506.167, + "placementWidth" : 269.153, + "placementHeight" : 98.639, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 269.153, + "contentHeight" : 98.639, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 2.907 + } + }, { + "path" : "PlatformInvoice[0]/PartiesRow[3]/BillToColumn[0]/BillToLabelLayer[0]", + "entityName" : "BillToLabelLayer", + "entityKind" : "LayerStackNode", + "parentPath" : "PlatformInvoice[0]/PartiesRow[3]/BillToColumn[0]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 28.485, + "computedY" : 586.17, + "placementX" : 28.485, + "placementY" : 586.17, + "placementWidth" : 266.246, + "placementHeight" : 18.636, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 266.246, + "contentHeight" : 18.636, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/PartiesRow[3]/BillToColumn[0]/BillToLabelLayer[0]/BillToLabelHolder[0]", + "entityName" : "BillToLabelHolder", + "entityKind" : "SectionNode", + "parentPath" : "PlatformInvoice[0]/PartiesRow[3]/BillToColumn[0]/BillToLabelLayer[0]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 28.485, + "computedY" : 586.17, + "placementX" : 28.485, + "placementY" : 586.17, + "placementWidth" : 266.246, + "placementHeight" : 18.636, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 266.246, + "contentHeight" : 18.636, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/PartiesRow[3]/BillToColumn[0]/BillToLabelLayer[0]/BillToLabelHolder[0]/BillToLabel[0]", + "entityName" : "BillToLabel", + "entityKind" : "RowNode", + "parentPath" : "PlatformInvoice[0]/PartiesRow[3]/BillToColumn[0]/BillToLabelLayer[0]/BillToLabelHolder[0]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 28.485, + "computedY" : 586.17, + "placementX" : 28.485, + "placementY" : 586.17, + "placementWidth" : 266.246, + "placementHeight" : 18.636, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 266.246, + "contentHeight" : 18.636, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/PartiesRow[3]/BillToColumn[0]/BillToLabelLayer[0]/BillToLabelHolder[0]/BillToLabel[0]/BillToDisc[0]", + "entityName" : "BillToDisc", + "entityKind" : "ShapeContainerNode", + "parentPath" : "PlatformInvoice[0]/PartiesRow[3]/BillToColumn[0]/BillToLabelLayer[0]/BillToLabelHolder[0]/BillToLabel[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 28.485, + "computedY" : 586.17, + "placementX" : 28.485, + "placementY" : 586.17, + "placementWidth" : 18.636, + "placementHeight" : 18.636, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 18.636, + "contentHeight" : 18.636, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/PartiesRow[3]/BillToColumn[0]/BillToLabelLayer[0]/BillToLabelHolder[0]/BillToLabel[0]/BillToDisc[0]/SvgIcon[0]", + "entityName" : "SvgIcon", + "entityKind" : "LayerStackNode", + "parentPath" : "PlatformInvoice[0]/PartiesRow[3]/BillToColumn[0]/BillToLabelLayer[0]/BillToLabelHolder[0]/BillToLabel[0]/BillToDisc[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 32.72, + "computedY" : 590.406, + "placementX" : 32.72, + "placementY" : 590.406, + "placementWidth" : 10.165, + "placementHeight" : 10.165, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 10.165, + "contentHeight" : 10.165, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/PartiesRow[3]/BillToColumn[0]/BillToLabelLayer[0]/BillToLabelHolder[0]/BillToLabel[0]/BillToDisc[0]/SvgIcon[0]/SvgLayer0[0]", + "entityName" : "SvgLayer0", + "entityKind" : "Path", + "parentPath" : "PlatformInvoice[0]/PartiesRow[3]/BillToColumn[0]/BillToLabelLayer[0]/BillToLabelHolder[0]/BillToLabel[0]/BillToDisc[0]/SvgIcon[0]", + "childIndex" : 0, + "depth" : 9, + "layer" : 9, + "computedX" : 32.72, + "computedY" : 590.406, + "placementX" : 32.72, + "placementY" : 590.406, + "placementWidth" : 10.165, + "placementHeight" : 10.165, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 10.165, + "contentHeight" : 10.165, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/PartiesRow[3]/BillToColumn[0]/BillToLabelLayer[0]/BillToLabelHolder[0]/BillToLabel[0]/BillToLabelText[1]", + "entityName" : "BillToLabelText", + "entityKind" : "ParagraphNode", + "parentPath" : "PlatformInvoice[0]/PartiesRow[3]/BillToColumn[0]/BillToLabelLayer[0]/BillToLabelHolder[0]/BillToLabel[0]", + "childIndex" : 1, + "depth" : 7, + "layer" : 7, + "computedX" : 61.039, + "computedY" : 589.509, + "placementX" : 61.039, + "placementY" : 589.509, + "placementWidth" : 34.212, + "placementHeight" : 11.959, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 34.212, + "contentHeight" : 11.959, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/PartiesRow[3]/BillToColumn[0]/BillToBody[1]", + "entityName" : "BillToBody", + "entityKind" : "SectionNode", + "parentPath" : "PlatformInvoice[0]/PartiesRow[3]/BillToColumn[0]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 28.485, + "computedY" : 506.167, + "placementX" : 28.485, + "placementY" : 506.167, + "placementWidth" : 143.341, + "placementHeight" : 80.003, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 143.341, + "contentHeight" : 80.003, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 32.554 + } + }, { + "path" : "PlatformInvoice[0]/PartiesRow[3]/BillToColumn[0]/BillToBody[1]/BillToName[0]", + "entityName" : "BillToName", + "entityKind" : "ParagraphNode", + "parentPath" : "PlatformInvoice[0]/PartiesRow[3]/BillToColumn[0]/BillToBody[1]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 61.039, + "computedY" : 572.069, + "placementX" : 61.039, + "placementY" : 572.069, + "placementWidth" : 85.159, + "placementHeight" : 12.955, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 85.159, + "contentHeight" : 12.955, + "margin" : { + "top" : 1.146, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/PartiesRow[3]/BillToColumn[0]/BillToBody[1]/BillToAddress[1]", + "entityName" : "BillToAddress", + "entityKind" : "SectionNode", + "parentPath" : "PlatformInvoice[0]/PartiesRow[3]/BillToColumn[0]/BillToBody[1]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 61.039, + "computedY" : 528.639, + "placementX" : 61.039, + "placementY" : 528.639, + "placementWidth" : 90.597, + "placementHeight" : 39.693, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 90.597, + "contentHeight" : 39.693, + "margin" : { + "top" : 3.737, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/PartiesRow[3]/BillToColumn[0]/BillToBody[1]/BillToAddress[1]/BillToAddress_0[0]", + "entityName" : "BillToAddress_0", + "entityKind" : "ParagraphNode", + "parentPath" : "PlatformInvoice[0]/PartiesRow[3]/BillToColumn[0]/BillToBody[1]/BillToAddress[1]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 61.039, + "computedY" : 556.373, + "placementX" : 61.039, + "placementY" : 556.373, + "placementWidth" : 61.258, + "placementHeight" : 11.959, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 61.258, + "contentHeight" : 11.959, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/PartiesRow[3]/BillToColumn[0]/BillToBody[1]/BillToAddress[1]/BillToAddress_1[1]", + "entityName" : "BillToAddress_1", + "entityKind" : "ParagraphNode", + "parentPath" : "PlatformInvoice[0]/PartiesRow[3]/BillToColumn[0]/BillToBody[1]/BillToAddress[1]", + "childIndex" : 1, + "depth" : 6, + "layer" : 6, + "computedX" : 61.039, + "computedY" : 542.506, + "placementX" : 61.039, + "placementY" : 542.506, + "placementWidth" : 90.597, + "placementHeight" : 11.959, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 90.597, + "contentHeight" : 11.959, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/PartiesRow[3]/BillToColumn[0]/BillToBody[1]/BillToAddress[1]/BillToAddress_2[2]", + "entityName" : "BillToAddress_2", + "entityKind" : "ParagraphNode", + "parentPath" : "PlatformInvoice[0]/PartiesRow[3]/BillToColumn[0]/BillToBody[1]/BillToAddress[1]", + "childIndex" : 2, + "depth" : 6, + "layer" : 6, + "computedX" : 61.039, + "computedY" : 528.639, + "placementX" : 61.039, + "placementY" : 528.639, + "placementWidth" : 69.191, + "placementHeight" : 11.959, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 69.191, + "contentHeight" : 11.959, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/PartiesRow[3]/BillToColumn[0]/BillToBody[1]/BillToRegistration[2]", + "entityName" : "BillToRegistration", + "entityKind" : "ParagraphNode", + "parentPath" : "PlatformInvoice[0]/PartiesRow[3]/BillToColumn[0]/BillToBody[1]", + "childIndex" : 2, + "depth" : 5, + "layer" : 5, + "computedX" : 61.039, + "computedY" : 506.167, + "placementX" : 61.039, + "placementY" : 506.167, + "placementWidth" : 110.787, + "placementHeight" : 11.959, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 110.787, + "contentHeight" : 11.959, + "margin" : { + "top" : 10.514, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/PartiesRow[3]/PartyColumnDivider[1]", + "entityName" : "PartyColumnDivider", + "entityKind" : "SectionNode", + "parentPath" : "PlatformInvoice[0]/PartiesRow[3]", + "childIndex" : 1, + "depth" : 3, + "layer" : 3, + "computedX" : 294.731, + "computedY" : 500.666, + "placementX" : 294.731, + "placementY" : 500.666, + "placementWidth" : 0.581, + "placementHeight" : 104.14, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 0.581, + "contentHeight" : 104.14, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/PartiesRow[3]/PartyColumnDivider[1]/PartyColumnDivider[0]", + "entityName" : "PartyColumnDivider", + "entityKind" : "LineNode", + "parentPath" : "PlatformInvoice[0]/PartiesRow[3]/PartyColumnDivider[1]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 294.731, + "computedY" : 500.666, + "placementX" : 294.731, + "placementY" : 500.666, + "placementWidth" : 1.0, + "placementHeight" : 101.399, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 1.0, + "contentHeight" : 101.399, + "margin" : { + "top" : 2.741, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/PartiesRow[3]/ShipToColumn[2]", + "entityName" : "ShipToColumn", + "entityKind" : "SectionNode", + "parentPath" : "PlatformInvoice[0]/PartiesRow[3]", + "childIndex" : 2, + "depth" : 3, + "layer" : 3, + "computedX" : 295.313, + "computedY" : 517.031, + "placementX" : 295.313, + "placementY" : 517.031, + "placementWidth" : 274.385, + "placementHeight" : 87.775, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 274.385, + "contentHeight" : 87.775, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 18.602 + } + }, { + "path" : "PlatformInvoice[0]/PartiesRow[3]/ShipToColumn[2]/ShipToLabelLayer[0]", + "entityName" : "ShipToLabelLayer", + "entityKind" : "LayerStackNode", + "parentPath" : "PlatformInvoice[0]/PartiesRow[3]/ShipToColumn[2]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 313.915, + "computedY" : 588.429, + "placementX" : 313.915, + "placementY" : 588.429, + "placementWidth" : 255.782, + "placementHeight" : 16.377, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 255.782, + "contentHeight" : 16.377, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/PartiesRow[3]/ShipToColumn[2]/ShipToLabelLayer[0]/ShipToLabelHolder[0]", + "entityName" : "ShipToLabelHolder", + "entityKind" : "SectionNode", + "parentPath" : "PlatformInvoice[0]/PartiesRow[3]/ShipToColumn[2]/ShipToLabelLayer[0]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 313.915, + "computedY" : 588.429, + "placementX" : 313.915, + "placementY" : 588.429, + "placementWidth" : 255.782, + "placementHeight" : 16.377, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 255.782, + "contentHeight" : 16.377, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/PartiesRow[3]/ShipToColumn[2]/ShipToLabelLayer[0]/ShipToLabelHolder[0]/ShipToLabel[0]", + "entityName" : "ShipToLabel", + "entityKind" : "RowNode", + "parentPath" : "PlatformInvoice[0]/PartiesRow[3]/ShipToColumn[2]/ShipToLabelLayer[0]/ShipToLabelHolder[0]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 313.915, + "computedY" : 588.429, + "placementX" : 313.915, + "placementY" : 588.429, + "placementWidth" : 255.782, + "placementHeight" : 16.377, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 255.782, + "contentHeight" : 16.377, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/PartiesRow[3]/ShipToColumn[2]/ShipToLabelLayer[0]/ShipToLabelHolder[0]/ShipToLabel[0]/SvgIcon[0]", + "entityName" : "SvgIcon", + "entityKind" : "LayerStackNode", + "parentPath" : "PlatformInvoice[0]/PartiesRow[3]/ShipToColumn[2]/ShipToLabelLayer[0]/ShipToLabelHolder[0]/ShipToLabel[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 313.915, + "computedY" : 588.429, + "placementX" : 313.915, + "placementY" : 588.429, + "placementWidth" : 16.377, + "placementHeight" : 16.377, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 16.377, + "contentHeight" : 16.377, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/PartiesRow[3]/ShipToColumn[2]/ShipToLabelLayer[0]/ShipToLabelHolder[0]/ShipToLabel[0]/SvgIcon[0]/SvgLayer0[0]", + "entityName" : "SvgLayer0", + "entityKind" : "Path", + "parentPath" : "PlatformInvoice[0]/PartiesRow[3]/ShipToColumn[2]/ShipToLabelLayer[0]/ShipToLabelHolder[0]/ShipToLabel[0]/SvgIcon[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 313.915, + "computedY" : 588.429, + "placementX" : 313.915, + "placementY" : 588.429, + "placementWidth" : 16.377, + "placementHeight" : 16.377, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 16.377, + "contentHeight" : 16.377, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/PartiesRow[3]/ShipToColumn[2]/ShipToLabelLayer[0]/ShipToLabelHolder[0]/ShipToLabel[0]/ShipToLabelText[1]", + "entityName" : "ShipToLabelText", + "entityKind" : "ParagraphNode", + "parentPath" : "PlatformInvoice[0]/PartiesRow[3]/ShipToColumn[2]/ShipToLabelLayer[0]/ShipToLabelHolder[0]/ShipToLabel[0]", + "childIndex" : 1, + "depth" : 7, + "layer" : 7, + "computedX" : 344.725, + "computedY" : 590.638, + "placementX" : 344.725, + "placementY" : 590.638, + "placementWidth" : 34.77, + "placementHeight" : 11.959, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 34.77, + "contentHeight" : 11.959, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/PartiesRow[3]/ShipToColumn[2]/ShipToBody[1]", + "entityName" : "ShipToBody", + "entityKind" : "SectionNode", + "parentPath" : "PlatformInvoice[0]/PartiesRow[3]/ShipToColumn[2]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 313.915, + "computedY" : 517.031, + "placementX" : 313.915, + "placementY" : 517.031, + "placementWidth" : 128.782, + "placementHeight" : 71.398, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 128.782, + "contentHeight" : 71.398, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 30.81 + } + }, { + "path" : "PlatformInvoice[0]/PartiesRow[3]/ShipToColumn[2]/ShipToBody[1]/ShipToName[0]", + "entityName" : "ShipToName", + "entityKind" : "ParagraphNode", + "parentPath" : "PlatformInvoice[0]/PartiesRow[3]/ShipToColumn[2]/ShipToBody[1]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 344.725, + "computedY" : 574.328, + "placementX" : 344.725, + "placementY" : 574.328, + "placementWidth" : 85.159, + "placementHeight" : 12.955, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 85.159, + "contentHeight" : 12.955, + "margin" : { + "top" : 1.146, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/PartiesRow[3]/ShipToColumn[2]/ShipToBody[1]/ShipToAddress[1]", + "entityName" : "ShipToAddress", + "entityKind" : "SectionNode", + "parentPath" : "PlatformInvoice[0]/PartiesRow[3]/ShipToColumn[2]/ShipToBody[1]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 344.725, + "computedY" : 517.031, + "placementX" : 344.725, + "placementY" : 517.031, + "placementWidth" : 97.971, + "placementHeight" : 53.56, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 97.971, + "contentHeight" : 53.56, + "margin" : { + "top" : 3.737, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/PartiesRow[3]/ShipToColumn[2]/ShipToBody[1]/ShipToAddress[1]/ShipToAddress_0[0]", + "entityName" : "ShipToAddress_0", + "entityKind" : "ParagraphNode", + "parentPath" : "PlatformInvoice[0]/PartiesRow[3]/ShipToColumn[2]/ShipToBody[1]/ShipToAddress[1]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 344.725, + "computedY" : 558.632, + "placementX" : 344.725, + "placementY" : 558.632, + "placementWidth" : 55.737, + "placementHeight" : 11.959, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 55.737, + "contentHeight" : 11.959, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/PartiesRow[3]/ShipToColumn[2]/ShipToBody[1]/ShipToAddress[1]/ShipToAddress_1[1]", + "entityName" : "ShipToAddress_1", + "entityKind" : "ParagraphNode", + "parentPath" : "PlatformInvoice[0]/PartiesRow[3]/ShipToColumn[2]/ShipToBody[1]/ShipToAddress[1]", + "childIndex" : 1, + "depth" : 6, + "layer" : 6, + "computedX" : 344.725, + "computedY" : 544.765, + "placementX" : 344.725, + "placementY" : 544.765, + "placementWidth" : 97.971, + "placementHeight" : 11.959, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 97.971, + "contentHeight" : 11.959, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/PartiesRow[3]/ShipToColumn[2]/ShipToBody[1]/ShipToAddress[1]/ShipToAddress_2[2]", + "entityName" : "ShipToAddress_2", + "entityKind" : "ParagraphNode", + "parentPath" : "PlatformInvoice[0]/PartiesRow[3]/ShipToColumn[2]/ShipToBody[1]/ShipToAddress[1]", + "childIndex" : 2, + "depth" : 6, + "layer" : 6, + "computedX" : 344.725, + "computedY" : 530.898, + "placementX" : 344.725, + "placementY" : 530.898, + "placementWidth" : 90.597, + "placementHeight" : 11.959, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 90.597, + "contentHeight" : 11.959, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/PartiesRow[3]/ShipToColumn[2]/ShipToBody[1]/ShipToAddress[1]/ShipToAddress_3[3]", + "entityName" : "ShipToAddress_3", + "entityKind" : "ParagraphNode", + "parentPath" : "PlatformInvoice[0]/PartiesRow[3]/ShipToColumn[2]/ShipToBody[1]/ShipToAddress[1]", + "childIndex" : 3, + "depth" : 6, + "layer" : 6, + "computedX" : 344.725, + "computedY" : 517.031, + "placementX" : 344.725, + "placementY" : 517.031, + "placementWidth" : 69.191, + "placementHeight" : 11.959, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 69.191, + "contentHeight" : 11.959, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/LineItems[4]", + "entityName" : "LineItems", + "entityKind" : "TableNode", + "parentPath" : "PlatformInvoice[0]", + "childIndex" : 4, + "depth" : 2, + "layer" : 2, + "computedX" : 25.578, + "computedY" : 274.395, + "placementX" : 25.578, + "placementY" : 274.395, + "placementWidth" : 544.119, + "placementHeight" : 208.732, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 544.119, + "contentHeight" : 208.732, + "margin" : { + "top" : 17.539, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/Settlement[5]", + "entityName" : "Settlement", + "entityKind" : "RowNode", + "parentPath" : "PlatformInvoice[0]", + "childIndex" : 5, + "depth" : 2, + "layer" : 2, + "computedX" : 25.578, + "computedY" : 99.7, + "placementX" : 25.578, + "placementY" : 99.7, + "placementWidth" : 544.119, + "placementHeight" : 168.117, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 544.119, + "contentHeight" : 168.117, + "margin" : { + "top" : 6.577, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/Settlement[5]/PaymentPanel[0]", + "entityName" : "PaymentPanel", + "entityKind" : "SectionNode", + "parentPath" : "PlatformInvoice[0]/Settlement[5]", + "childIndex" : 0, + "depth" : 3, + "layer" : 3, + "computedX" : 25.578, + "computedY" : 99.7, + "placementX" : 25.578, + "placementY" : 99.7, + "placementWidth" : 241.249, + "placementHeight" : 168.117, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 241.249, + "contentHeight" : 168.117, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/Settlement[5]/PaymentPanel[0]/PaymentHeadingLayer[0]", + "entityName" : "PaymentHeadingLayer", + "entityKind" : "LayerStackNode", + "parentPath" : "PlatformInvoice[0]/Settlement[5]/PaymentPanel[0]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 25.578, + "computedY" : 244.398, + "placementX" : 25.578, + "placementY" : 244.398, + "placementWidth" : 241.249, + "placementHeight" : 23.419, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 241.249, + "contentHeight" : 23.419, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/Settlement[5]/PaymentPanel[0]/PaymentHeadingLayer[0]/PaymentHeadingHolder[0]", + "entityName" : "PaymentHeadingHolder", + "entityKind" : "SectionNode", + "parentPath" : "PlatformInvoice[0]/Settlement[5]/PaymentPanel[0]/PaymentHeadingLayer[0]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 25.578, + "computedY" : 244.398, + "placementX" : 25.578, + "placementY" : 244.398, + "placementWidth" : 241.249, + "placementHeight" : 23.419, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 241.249, + "contentHeight" : 23.419, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/Settlement[5]/PaymentPanel[0]/PaymentHeadingLayer[0]/PaymentHeadingHolder[0]/PaymentHeading[0]", + "entityName" : "PaymentHeading", + "entityKind" : "RowNode", + "parentPath" : "PlatformInvoice[0]/Settlement[5]/PaymentPanel[0]/PaymentHeadingLayer[0]/PaymentHeadingHolder[0]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 25.578, + "computedY" : 244.398, + "placementX" : 25.578, + "placementY" : 244.398, + "placementWidth" : 241.249, + "placementHeight" : 23.419, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 241.249, + "contentHeight" : 23.419, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 9.866, + "right" : 0.0, + "bottom" : 0.0, + "left" : 13.952 + } + }, { + "path" : "PlatformInvoice[0]/Settlement[5]/PaymentPanel[0]/PaymentHeadingLayer[0]/PaymentHeadingHolder[0]/PaymentHeading[0]/SvgIcon[0]", + "entityName" : "SvgIcon", + "entityKind" : "LayerStackNode", + "parentPath" : "PlatformInvoice[0]/Settlement[5]/PaymentPanel[0]/PaymentHeadingLayer[0]/PaymentHeadingHolder[0]/PaymentHeading[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 39.53, + "computedY" : 244.398, + "placementX" : 39.53, + "placementY" : 244.398, + "placementWidth" : 13.553, + "placementHeight" : 13.553, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 13.553, + "contentHeight" : 13.553, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/Settlement[5]/PaymentPanel[0]/PaymentHeadingLayer[0]/PaymentHeadingHolder[0]/PaymentHeading[0]/SvgIcon[0]/SvgLayer0[0]", + "entityName" : "SvgLayer0", + "entityKind" : "Path", + "parentPath" : "PlatformInvoice[0]/Settlement[5]/PaymentPanel[0]/PaymentHeadingLayer[0]/PaymentHeadingHolder[0]/PaymentHeading[0]/SvgIcon[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 39.53, + "computedY" : 244.398, + "placementX" : 39.53, + "placementY" : 244.398, + "placementWidth" : 13.553, + "placementHeight" : 13.553, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 13.553, + "contentHeight" : 13.553, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/Settlement[5]/PaymentPanel[0]/PaymentHeadingLayer[0]/PaymentHeadingHolder[0]/PaymentHeading[0]/PaymentHeadingText[1]", + "entityName" : "PaymentHeadingText", + "entityKind" : "ParagraphNode", + "parentPath" : "PlatformInvoice[0]/Settlement[5]/PaymentPanel[0]/PaymentHeadingLayer[0]/PaymentHeadingHolder[0]/PaymentHeading[0]", + "childIndex" : 1, + "depth" : 7, + "layer" : 7, + "computedX" : 68.596, + "computedY" : 245.196, + "placementX" : 68.596, + "placementY" : 245.196, + "placementWidth" : 108.395, + "placementHeight" : 11.959, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 108.395, + "contentHeight" : 11.959, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/Settlement[5]/PaymentPanel[0]/PaymentRows[1]", + "entityName" : "PaymentRows", + "entityKind" : "SectionNode", + "parentPath" : "PlatformInvoice[0]/Settlement[5]/PaymentPanel[0]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 25.578, + "computedY" : 145.097, + "placementX" : 25.578, + "placementY" : 145.097, + "placementWidth" : 241.249, + "placementHeight" : 94.219, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 241.249, + "contentHeight" : 94.219, + "margin" : { + "top" : 5.082, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 13.952, + "bottom" : 0.0, + "left" : 14.533 + } + }, { + "path" : "PlatformInvoice[0]/Settlement[5]/PaymentPanel[0]/PaymentRows[1]/PaymentRow_0Layer[0]", + "entityName" : "PaymentRow_0Layer", + "entityKind" : "LayerStackNode", + "parentPath" : "PlatformInvoice[0]/Settlement[5]/PaymentPanel[0]/PaymentRows[1]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 40.111, + "computedY" : 228.354, + "placementX" : 40.111, + "placementY" : 228.354, + "placementWidth" : 212.765, + "placementHeight" : 10.962, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 212.765, + "contentHeight" : 10.962, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/Settlement[5]/PaymentPanel[0]/PaymentRows[1]/PaymentRow_0Layer[0]/PaymentRow_0Holder[0]", + "entityName" : "PaymentRow_0Holder", + "entityKind" : "SectionNode", + "parentPath" : "PlatformInvoice[0]/Settlement[5]/PaymentPanel[0]/PaymentRows[1]/PaymentRow_0Layer[0]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 40.111, + "computedY" : 228.354, + "placementX" : 40.111, + "placementY" : 228.354, + "placementWidth" : 212.765, + "placementHeight" : 10.962, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 212.765, + "contentHeight" : 10.962, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/Settlement[5]/PaymentPanel[0]/PaymentRows[1]/PaymentRow_0Layer[0]/PaymentRow_0Holder[0]/PaymentRow_0[0]", + "entityName" : "PaymentRow_0", + "entityKind" : "RowNode", + "parentPath" : "PlatformInvoice[0]/Settlement[5]/PaymentPanel[0]/PaymentRows[1]/PaymentRow_0Layer[0]/PaymentRow_0Holder[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 40.111, + "computedY" : 228.354, + "placementX" : 40.111, + "placementY" : 228.354, + "placementWidth" : 212.765, + "placementHeight" : 10.962, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 212.765, + "contentHeight" : 10.962, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/Settlement[5]/PaymentPanel[0]/PaymentRows[1]/PaymentRow_0Layer[0]/PaymentRow_0Holder[0]/PaymentRow_0[0]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "PlatformInvoice[0]/Settlement[5]/PaymentPanel[0]/PaymentRows[1]/PaymentRow_0Layer[0]/PaymentRow_0Holder[0]/PaymentRow_0[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 40.111, + "computedY" : 228.354, + "placementX" : 40.111, + "placementY" : 228.354, + "placementWidth" : 48.087, + "placementHeight" : 10.962, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 48.087, + "contentHeight" : 10.962, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/Settlement[5]/PaymentPanel[0]/PaymentRows[1]/PaymentRow_0Layer[0]/PaymentRow_0Holder[0]/PaymentRow_0[0]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "PlatformInvoice[0]/Settlement[5]/PaymentPanel[0]/PaymentRows[1]/PaymentRow_0Layer[0]/PaymentRow_0Holder[0]/PaymentRow_0[0]", + "childIndex" : 1, + "depth" : 8, + "layer" : 8, + "computedX" : 144.75, + "computedY" : 228.354, + "placementX" : 144.75, + "placementY" : 228.354, + "placementWidth" : 92.657, + "placementHeight" : 10.962, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 92.657, + "contentHeight" : 10.962, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/Settlement[5]/PaymentPanel[0]/PaymentRows[1]/PaymentRow_1Layer[1]", + "entityName" : "PaymentRow_1Layer", + "entityKind" : "LayerStackNode", + "parentPath" : "PlatformInvoice[0]/Settlement[5]/PaymentPanel[0]/PaymentRows[1]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 40.111, + "computedY" : 216.46, + "placementX" : 40.111, + "placementY" : 216.46, + "placementWidth" : 212.765, + "placementHeight" : 10.962, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 212.765, + "contentHeight" : 10.962, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/Settlement[5]/PaymentPanel[0]/PaymentRows[1]/PaymentRow_1Layer[1]/PaymentRow_1Holder[0]", + "entityName" : "PaymentRow_1Holder", + "entityKind" : "SectionNode", + "parentPath" : "PlatformInvoice[0]/Settlement[5]/PaymentPanel[0]/PaymentRows[1]/PaymentRow_1Layer[1]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 40.111, + "computedY" : 216.46, + "placementX" : 40.111, + "placementY" : 216.46, + "placementWidth" : 212.765, + "placementHeight" : 10.962, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 212.765, + "contentHeight" : 10.962, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/Settlement[5]/PaymentPanel[0]/PaymentRows[1]/PaymentRow_1Layer[1]/PaymentRow_1Holder[0]/PaymentRow_1[0]", + "entityName" : "PaymentRow_1", + "entityKind" : "RowNode", + "parentPath" : "PlatformInvoice[0]/Settlement[5]/PaymentPanel[0]/PaymentRows[1]/PaymentRow_1Layer[1]/PaymentRow_1Holder[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 40.111, + "computedY" : 216.46, + "placementX" : 40.111, + "placementY" : 216.46, + "placementWidth" : 212.765, + "placementHeight" : 10.962, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 212.765, + "contentHeight" : 10.962, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/Settlement[5]/PaymentPanel[0]/PaymentRows[1]/PaymentRow_1Layer[1]/PaymentRow_1Holder[0]/PaymentRow_1[0]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "PlatformInvoice[0]/Settlement[5]/PaymentPanel[0]/PaymentRows[1]/PaymentRow_1Layer[1]/PaymentRow_1Holder[0]/PaymentRow_1[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 40.111, + "computedY" : 216.46, + "placementX" : 40.111, + "placementY" : 216.46, + "placementWidth" : 61.36, + "placementHeight" : 10.962, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 61.36, + "contentHeight" : 10.962, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/Settlement[5]/PaymentPanel[0]/PaymentRows[1]/PaymentRow_1Layer[1]/PaymentRow_1Holder[0]/PaymentRow_1[0]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "PlatformInvoice[0]/Settlement[5]/PaymentPanel[0]/PaymentRows[1]/PaymentRow_1Layer[1]/PaymentRow_1Holder[0]/PaymentRow_1[0]", + "childIndex" : 1, + "depth" : 8, + "layer" : 8, + "computedX" : 144.75, + "computedY" : 216.46, + "placementX" : 144.75, + "placementY" : 216.46, + "placementWidth" : 67.481, + "placementHeight" : 10.962, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 67.481, + "contentHeight" : 10.962, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/Settlement[5]/PaymentPanel[0]/PaymentRows[1]/PaymentRow_2Layer[2]", + "entityName" : "PaymentRow_2Layer", + "entityKind" : "LayerStackNode", + "parentPath" : "PlatformInvoice[0]/Settlement[5]/PaymentPanel[0]/PaymentRows[1]", + "childIndex" : 2, + "depth" : 5, + "layer" : 5, + "computedX" : 40.111, + "computedY" : 204.566, + "placementX" : 40.111, + "placementY" : 204.566, + "placementWidth" : 212.765, + "placementHeight" : 10.962, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 212.765, + "contentHeight" : 10.962, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/Settlement[5]/PaymentPanel[0]/PaymentRows[1]/PaymentRow_2Layer[2]/PaymentRow_2Holder[0]", + "entityName" : "PaymentRow_2Holder", + "entityKind" : "SectionNode", + "parentPath" : "PlatformInvoice[0]/Settlement[5]/PaymentPanel[0]/PaymentRows[1]/PaymentRow_2Layer[2]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 40.111, + "computedY" : 204.566, + "placementX" : 40.111, + "placementY" : 204.566, + "placementWidth" : 212.765, + "placementHeight" : 10.962, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 212.765, + "contentHeight" : 10.962, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/Settlement[5]/PaymentPanel[0]/PaymentRows[1]/PaymentRow_2Layer[2]/PaymentRow_2Holder[0]/PaymentRow_2[0]", + "entityName" : "PaymentRow_2", + "entityKind" : "RowNode", + "parentPath" : "PlatformInvoice[0]/Settlement[5]/PaymentPanel[0]/PaymentRows[1]/PaymentRow_2Layer[2]/PaymentRow_2Holder[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 40.111, + "computedY" : 204.566, + "placementX" : 40.111, + "placementY" : 204.566, + "placementWidth" : 212.765, + "placementHeight" : 10.962, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 212.765, + "contentHeight" : 10.962, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/Settlement[5]/PaymentPanel[0]/PaymentRows[1]/PaymentRow_2Layer[2]/PaymentRow_2Holder[0]/PaymentRow_2[0]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "PlatformInvoice[0]/Settlement[5]/PaymentPanel[0]/PaymentRows[1]/PaymentRow_2Layer[2]/PaymentRow_2Holder[0]/PaymentRow_2[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 40.111, + "computedY" : 204.566, + "placementX" : 40.111, + "placementY" : 204.566, + "placementWidth" : 69.902, + "placementHeight" : 10.962, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 69.902, + "contentHeight" : 10.962, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/Settlement[5]/PaymentPanel[0]/PaymentRows[1]/PaymentRow_2Layer[2]/PaymentRow_2Holder[0]/PaymentRow_2[0]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "PlatformInvoice[0]/Settlement[5]/PaymentPanel[0]/PaymentRows[1]/PaymentRow_2Layer[2]/PaymentRow_2Holder[0]/PaymentRow_2[0]", + "childIndex" : 1, + "depth" : 8, + "layer" : 8, + "computedX" : 144.75, + "computedY" : 204.566, + "placementX" : 144.75, + "placementY" : 204.566, + "placementWidth" : 45.136, + "placementHeight" : 10.962, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 45.136, + "contentHeight" : 10.962, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/Settlement[5]/PaymentPanel[0]/PaymentRows[1]/PaymentRow_3Layer[3]", + "entityName" : "PaymentRow_3Layer", + "entityKind" : "LayerStackNode", + "parentPath" : "PlatformInvoice[0]/Settlement[5]/PaymentPanel[0]/PaymentRows[1]", + "childIndex" : 3, + "depth" : 5, + "layer" : 5, + "computedX" : 40.111, + "computedY" : 192.672, + "placementX" : 40.111, + "placementY" : 192.672, + "placementWidth" : 212.765, + "placementHeight" : 10.962, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 212.765, + "contentHeight" : 10.962, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/Settlement[5]/PaymentPanel[0]/PaymentRows[1]/PaymentRow_3Layer[3]/PaymentRow_3Holder[0]", + "entityName" : "PaymentRow_3Holder", + "entityKind" : "SectionNode", + "parentPath" : "PlatformInvoice[0]/Settlement[5]/PaymentPanel[0]/PaymentRows[1]/PaymentRow_3Layer[3]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 40.111, + "computedY" : 192.672, + "placementX" : 40.111, + "placementY" : 192.672, + "placementWidth" : 212.765, + "placementHeight" : 10.962, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 212.765, + "contentHeight" : 10.962, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/Settlement[5]/PaymentPanel[0]/PaymentRows[1]/PaymentRow_3Layer[3]/PaymentRow_3Holder[0]/PaymentRow_3[0]", + "entityName" : "PaymentRow_3", + "entityKind" : "RowNode", + "parentPath" : "PlatformInvoice[0]/Settlement[5]/PaymentPanel[0]/PaymentRows[1]/PaymentRow_3Layer[3]/PaymentRow_3Holder[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 40.111, + "computedY" : 192.672, + "placementX" : 40.111, + "placementY" : 192.672, + "placementWidth" : 212.765, + "placementHeight" : 10.962, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 212.765, + "contentHeight" : 10.962, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/Settlement[5]/PaymentPanel[0]/PaymentRows[1]/PaymentRow_3Layer[3]/PaymentRow_3Holder[0]/PaymentRow_3[0]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "PlatformInvoice[0]/Settlement[5]/PaymentPanel[0]/PaymentRows[1]/PaymentRow_3Layer[3]/PaymentRow_3Holder[0]/PaymentRow_3[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 40.111, + "computedY" : 192.672, + "placementX" : 40.111, + "placementY" : 192.672, + "placementWidth" : 51.412, + "placementHeight" : 10.962, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 51.412, + "contentHeight" : 10.962, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/Settlement[5]/PaymentPanel[0]/PaymentRows[1]/PaymentRow_3Layer[3]/PaymentRow_3Holder[0]/PaymentRow_3[0]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "PlatformInvoice[0]/Settlement[5]/PaymentPanel[0]/PaymentRows[1]/PaymentRow_3Layer[3]/PaymentRow_3Holder[0]/PaymentRow_3[0]", + "childIndex" : 1, + "depth" : 8, + "layer" : 8, + "computedX" : 144.75, + "computedY" : 192.672, + "placementX" : 144.75, + "placementY" : 192.672, + "placementWidth" : 42.277, + "placementHeight" : 10.962, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 42.277, + "contentHeight" : 10.962, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/Settlement[5]/PaymentPanel[0]/PaymentRows[1]/PaymentRow_4Layer[4]", + "entityName" : "PaymentRow_4Layer", + "entityKind" : "LayerStackNode", + "parentPath" : "PlatformInvoice[0]/Settlement[5]/PaymentPanel[0]/PaymentRows[1]", + "childIndex" : 4, + "depth" : 5, + "layer" : 5, + "computedX" : 40.111, + "computedY" : 180.778, + "placementX" : 40.111, + "placementY" : 180.778, + "placementWidth" : 212.765, + "placementHeight" : 10.962, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 212.765, + "contentHeight" : 10.962, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/Settlement[5]/PaymentPanel[0]/PaymentRows[1]/PaymentRow_4Layer[4]/PaymentRow_4Holder[0]", + "entityName" : "PaymentRow_4Holder", + "entityKind" : "SectionNode", + "parentPath" : "PlatformInvoice[0]/Settlement[5]/PaymentPanel[0]/PaymentRows[1]/PaymentRow_4Layer[4]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 40.111, + "computedY" : 180.778, + "placementX" : 40.111, + "placementY" : 180.778, + "placementWidth" : 212.765, + "placementHeight" : 10.962, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 212.765, + "contentHeight" : 10.962, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/Settlement[5]/PaymentPanel[0]/PaymentRows[1]/PaymentRow_4Layer[4]/PaymentRow_4Holder[0]/PaymentRow_4[0]", + "entityName" : "PaymentRow_4", + "entityKind" : "RowNode", + "parentPath" : "PlatformInvoice[0]/Settlement[5]/PaymentPanel[0]/PaymentRows[1]/PaymentRow_4Layer[4]/PaymentRow_4Holder[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 40.111, + "computedY" : 180.778, + "placementX" : 40.111, + "placementY" : 180.778, + "placementWidth" : 212.765, + "placementHeight" : 10.962, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 212.765, + "contentHeight" : 10.962, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/Settlement[5]/PaymentPanel[0]/PaymentRows[1]/PaymentRow_4Layer[4]/PaymentRow_4Holder[0]/PaymentRow_4[0]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "PlatformInvoice[0]/Settlement[5]/PaymentPanel[0]/PaymentRows[1]/PaymentRow_4Layer[4]/PaymentRow_4Holder[0]/PaymentRow_4[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 40.111, + "computedY" : 180.778, + "placementX" : 40.111, + "placementY" : 180.778, + "placementWidth" : 64.896, + "placementHeight" : 10.962, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 64.896, + "contentHeight" : 10.962, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/Settlement[5]/PaymentPanel[0]/PaymentRows[1]/PaymentRow_4Layer[4]/PaymentRow_4Holder[0]/PaymentRow_4[0]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "PlatformInvoice[0]/Settlement[5]/PaymentPanel[0]/PaymentRows[1]/PaymentRow_4Layer[4]/PaymentRow_4Holder[0]/PaymentRow_4[0]", + "childIndex" : 1, + "depth" : 8, + "layer" : 8, + "computedX" : 144.75, + "computedY" : 180.778, + "placementX" : 144.75, + "placementY" : 180.778, + "placementWidth" : 23.176, + "placementHeight" : 10.962, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 23.176, + "contentHeight" : 10.962, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/Settlement[5]/PaymentPanel[0]/PaymentRows[1]/PaymentRow_5Layer[5]", + "entityName" : "PaymentRow_5Layer", + "entityKind" : "LayerStackNode", + "parentPath" : "PlatformInvoice[0]/Settlement[5]/PaymentPanel[0]/PaymentRows[1]", + "childIndex" : 5, + "depth" : 5, + "layer" : 5, + "computedX" : 40.111, + "computedY" : 168.884, + "placementX" : 40.111, + "placementY" : 168.884, + "placementWidth" : 212.765, + "placementHeight" : 10.962, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 212.765, + "contentHeight" : 10.962, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/Settlement[5]/PaymentPanel[0]/PaymentRows[1]/PaymentRow_5Layer[5]/PaymentRow_5Holder[0]", + "entityName" : "PaymentRow_5Holder", + "entityKind" : "SectionNode", + "parentPath" : "PlatformInvoice[0]/Settlement[5]/PaymentPanel[0]/PaymentRows[1]/PaymentRow_5Layer[5]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 40.111, + "computedY" : 168.884, + "placementX" : 40.111, + "placementY" : 168.884, + "placementWidth" : 212.765, + "placementHeight" : 10.962, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 212.765, + "contentHeight" : 10.962, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/Settlement[5]/PaymentPanel[0]/PaymentRows[1]/PaymentRow_5Layer[5]/PaymentRow_5Holder[0]/PaymentRow_5[0]", + "entityName" : "PaymentRow_5", + "entityKind" : "RowNode", + "parentPath" : "PlatformInvoice[0]/Settlement[5]/PaymentPanel[0]/PaymentRows[1]/PaymentRow_5Layer[5]/PaymentRow_5Holder[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 40.111, + "computedY" : 168.884, + "placementX" : 40.111, + "placementY" : 168.884, + "placementWidth" : 212.765, + "placementHeight" : 10.962, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 212.765, + "contentHeight" : 10.962, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/Settlement[5]/PaymentPanel[0]/PaymentRows[1]/PaymentRow_5Layer[5]/PaymentRow_5Holder[0]/PaymentRow_5[0]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "PlatformInvoice[0]/Settlement[5]/PaymentPanel[0]/PaymentRows[1]/PaymentRow_5Layer[5]/PaymentRow_5Holder[0]/PaymentRow_5[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 40.111, + "computedY" : 168.884, + "placementX" : 40.111, + "placementY" : 168.884, + "placementWidth" : 77.849, + "placementHeight" : 10.962, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 77.849, + "contentHeight" : 10.962, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/Settlement[5]/PaymentPanel[0]/PaymentRows[1]/PaymentRow_5Layer[5]/PaymentRow_5Holder[0]/PaymentRow_5[0]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "PlatformInvoice[0]/Settlement[5]/PaymentPanel[0]/PaymentRows[1]/PaymentRow_5Layer[5]/PaymentRow_5Holder[0]/PaymentRow_5[0]", + "childIndex" : 1, + "depth" : 8, + "layer" : 8, + "computedX" : 144.75, + "computedY" : 168.884, + "placementX" : 144.75, + "placementY" : 168.884, + "placementWidth" : 14.954, + "placementHeight" : 10.962, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 14.954, + "contentHeight" : 10.962, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/Settlement[5]/PaymentPanel[0]/PaymentRows[1]/PaymentRow_6Layer[6]", + "entityName" : "PaymentRow_6Layer", + "entityKind" : "LayerStackNode", + "parentPath" : "PlatformInvoice[0]/Settlement[5]/PaymentPanel[0]/PaymentRows[1]", + "childIndex" : 6, + "depth" : 5, + "layer" : 5, + "computedX" : 40.111, + "computedY" : 156.99, + "placementX" : 40.111, + "placementY" : 156.99, + "placementWidth" : 212.765, + "placementHeight" : 10.962, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 212.765, + "contentHeight" : 10.962, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/Settlement[5]/PaymentPanel[0]/PaymentRows[1]/PaymentRow_6Layer[6]/PaymentRow_6Holder[0]", + "entityName" : "PaymentRow_6Holder", + "entityKind" : "SectionNode", + "parentPath" : "PlatformInvoice[0]/Settlement[5]/PaymentPanel[0]/PaymentRows[1]/PaymentRow_6Layer[6]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 40.111, + "computedY" : 156.99, + "placementX" : 40.111, + "placementY" : 156.99, + "placementWidth" : 212.765, + "placementHeight" : 10.962, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 212.765, + "contentHeight" : 10.962, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/Settlement[5]/PaymentPanel[0]/PaymentRows[1]/PaymentRow_6Layer[6]/PaymentRow_6Holder[0]/PaymentRow_6[0]", + "entityName" : "PaymentRow_6", + "entityKind" : "RowNode", + "parentPath" : "PlatformInvoice[0]/Settlement[5]/PaymentPanel[0]/PaymentRows[1]/PaymentRow_6Layer[6]/PaymentRow_6Holder[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 40.111, + "computedY" : 156.99, + "placementX" : 40.111, + "placementY" : 156.99, + "placementWidth" : 212.765, + "placementHeight" : 10.962, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 212.765, + "contentHeight" : 10.962, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/Settlement[5]/PaymentPanel[0]/PaymentRows[1]/PaymentRow_6Layer[6]/PaymentRow_6Holder[0]/PaymentRow_6[0]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "PlatformInvoice[0]/Settlement[5]/PaymentPanel[0]/PaymentRows[1]/PaymentRow_6Layer[6]/PaymentRow_6Holder[0]/PaymentRow_6[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 40.111, + "computedY" : 156.99, + "placementX" : 40.111, + "placementY" : 156.99, + "placementWidth" : 57.414, + "placementHeight" : 10.962, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 57.414, + "contentHeight" : 10.962, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/Settlement[5]/PaymentPanel[0]/PaymentRows[1]/PaymentRow_6Layer[6]/PaymentRow_6Holder[0]/PaymentRow_6[0]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "PlatformInvoice[0]/Settlement[5]/PaymentPanel[0]/PaymentRows[1]/PaymentRow_6Layer[6]/PaymentRow_6Holder[0]/PaymentRow_6[0]", + "childIndex" : 1, + "depth" : 8, + "layer" : 8, + "computedX" : 144.75, + "computedY" : 156.99, + "placementX" : 144.75, + "placementY" : 156.99, + "placementWidth" : 75.173, + "placementHeight" : 10.962, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 75.173, + "contentHeight" : 10.962, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/Settlement[5]/PaymentPanel[0]/PaymentRows[1]/PaymentRow_7Layer[7]", + "entityName" : "PaymentRow_7Layer", + "entityKind" : "LayerStackNode", + "parentPath" : "PlatformInvoice[0]/Settlement[5]/PaymentPanel[0]/PaymentRows[1]", + "childIndex" : 7, + "depth" : 5, + "layer" : 5, + "computedX" : 40.111, + "computedY" : 145.097, + "placementX" : 40.111, + "placementY" : 145.097, + "placementWidth" : 212.765, + "placementHeight" : 10.962, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 212.765, + "contentHeight" : 10.962, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/Settlement[5]/PaymentPanel[0]/PaymentRows[1]/PaymentRow_7Layer[7]/PaymentRow_7Holder[0]", + "entityName" : "PaymentRow_7Holder", + "entityKind" : "SectionNode", + "parentPath" : "PlatformInvoice[0]/Settlement[5]/PaymentPanel[0]/PaymentRows[1]/PaymentRow_7Layer[7]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 40.111, + "computedY" : 145.097, + "placementX" : 40.111, + "placementY" : 145.097, + "placementWidth" : 212.765, + "placementHeight" : 10.962, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 212.765, + "contentHeight" : 10.962, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/Settlement[5]/PaymentPanel[0]/PaymentRows[1]/PaymentRow_7Layer[7]/PaymentRow_7Holder[0]/PaymentRow_7[0]", + "entityName" : "PaymentRow_7", + "entityKind" : "RowNode", + "parentPath" : "PlatformInvoice[0]/Settlement[5]/PaymentPanel[0]/PaymentRows[1]/PaymentRow_7Layer[7]/PaymentRow_7Holder[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 40.111, + "computedY" : 145.097, + "placementX" : 40.111, + "placementY" : 145.097, + "placementWidth" : 212.765, + "placementHeight" : 10.962, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 212.765, + "contentHeight" : 10.962, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/Settlement[5]/PaymentPanel[0]/PaymentRows[1]/PaymentRow_7Layer[7]/PaymentRow_7Holder[0]/PaymentRow_7[0]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "PlatformInvoice[0]/Settlement[5]/PaymentPanel[0]/PaymentRows[1]/PaymentRow_7Layer[7]/PaymentRow_7Holder[0]/PaymentRow_7[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 40.111, + "computedY" : 145.097, + "placementX" : 40.111, + "placementY" : 145.097, + "placementWidth" : 73.309, + "placementHeight" : 10.962, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 73.309, + "contentHeight" : 10.962, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/Settlement[5]/PaymentPanel[0]/PaymentRows[1]/PaymentRow_7Layer[7]/PaymentRow_7Holder[0]/PaymentRow_7[0]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "PlatformInvoice[0]/Settlement[5]/PaymentPanel[0]/PaymentRows[1]/PaymentRow_7Layer[7]/PaymentRow_7Holder[0]/PaymentRow_7[0]", + "childIndex" : 1, + "depth" : 8, + "layer" : 8, + "computedX" : 144.75, + "computedY" : 145.097, + "placementX" : 144.75, + "placementY" : 145.097, + "placementWidth" : 93.077, + "placementHeight" : 10.962, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 93.077, + "contentHeight" : 10.962, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/Settlement[5]/PaymentPanel[0]/PaymentPanelRule[2]", + "entityName" : "PaymentPanelRule", + "entityKind" : "LineNode", + "parentPath" : "PlatformInvoice[0]/Settlement[5]/PaymentPanel[0]", + "childIndex" : 2, + "depth" : 4, + "layer" : 4, + "computedX" : 25.578, + "computedY" : 134.779, + "placementX" : 25.578, + "placementY" : 134.779, + "placementWidth" : 241.249, + "placementHeight" : 1.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 241.249, + "contentHeight" : 1.0, + "margin" : { + "top" : 9.318, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/Settlement[5]/PaymentPanel[0]/PaymentNoteLayer[3]", + "entityName" : "PaymentNoteLayer", + "entityKind" : "LayerStackNode", + "parentPath" : "PlatformInvoice[0]/Settlement[5]/PaymentPanel[0]", + "childIndex" : 3, + "depth" : 4, + "layer" : 4, + "computedX" : 25.578, + "computedY" : 99.7, + "placementX" : 25.578, + "placementY" : 99.7, + "placementWidth" : 241.249, + "placementHeight" : 35.079, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 241.249, + "contentHeight" : 35.079, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/Settlement[5]/PaymentPanel[0]/PaymentNoteLayer[3]/PaymentNoteHolder[0]", + "entityName" : "PaymentNoteHolder", + "entityKind" : "SectionNode", + "parentPath" : "PlatformInvoice[0]/Settlement[5]/PaymentPanel[0]/PaymentNoteLayer[3]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 25.578, + "computedY" : 99.7, + "placementX" : 25.578, + "placementY" : 99.7, + "placementWidth" : 241.249, + "placementHeight" : 35.079, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 241.249, + "contentHeight" : 35.079, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/Settlement[5]/PaymentPanel[0]/PaymentNoteLayer[3]/PaymentNoteHolder[0]/PaymentNote[0]", + "entityName" : "PaymentNote", + "entityKind" : "RowNode", + "parentPath" : "PlatformInvoice[0]/Settlement[5]/PaymentPanel[0]/PaymentNoteLayer[3]/PaymentNoteHolder[0]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 25.578, + "computedY" : 99.7, + "placementX" : 25.578, + "placementY" : 99.7, + "placementWidth" : 241.249, + "placementHeight" : 35.079, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 241.249, + "contentHeight" : 35.079, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 6.029, + "right" : 0.0, + "bottom" : 6.029, + "left" : 13.952 + } + }, { + "path" : "PlatformInvoice[0]/Settlement[5]/PaymentPanel[0]/PaymentNoteLayer[3]/PaymentNoteHolder[0]/PaymentNote[0]/SvgIcon[0]", + "entityName" : "SvgIcon", + "entityKind" : "LayerStackNode", + "parentPath" : "PlatformInvoice[0]/Settlement[5]/PaymentPanel[0]/PaymentNoteLayer[3]/PaymentNoteHolder[0]/PaymentNote[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 39.53, + "computedY" : 115.761, + "placementX" : 39.53, + "placementY" : 115.761, + "placementWidth" : 12.988, + "placementHeight" : 12.988, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 12.988, + "contentHeight" : 12.988, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/Settlement[5]/PaymentPanel[0]/PaymentNoteLayer[3]/PaymentNoteHolder[0]/PaymentNote[0]/SvgIcon[0]/SvgLayer0[0]", + "entityName" : "SvgLayer0", + "entityKind" : "Path", + "parentPath" : "PlatformInvoice[0]/Settlement[5]/PaymentPanel[0]/PaymentNoteLayer[3]/PaymentNoteHolder[0]/PaymentNote[0]/SvgIcon[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 39.53, + "computedY" : 115.761, + "placementX" : 39.53, + "placementY" : 115.761, + "placementWidth" : 12.988, + "placementHeight" : 12.988, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 12.988, + "contentHeight" : 12.988, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/Settlement[5]/PaymentPanel[0]/PaymentNoteLayer[3]/PaymentNoteHolder[0]/PaymentNote[0]/PaymentNoteText[1]", + "entityName" : "PaymentNoteText", + "entityKind" : "SectionNode", + "parentPath" : "PlatformInvoice[0]/Settlement[5]/PaymentPanel[0]/PaymentNoteLayer[3]/PaymentNoteHolder[0]/PaymentNote[0]", + "childIndex" : 1, + "depth" : 7, + "layer" : 7, + "computedX" : 62.202, + "computedY" : 105.729, + "placementX" : 62.202, + "placementY" : 105.729, + "placementWidth" : 119.195, + "placementHeight" : 23.02, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 119.195, + "contentHeight" : 23.02, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/Settlement[5]/PaymentPanel[0]/PaymentNoteLayer[3]/PaymentNoteHolder[0]/PaymentNote[0]/PaymentNoteText[1]/PaymentNoteLine_0[0]", + "entityName" : "PaymentNoteLine_0", + "entityKind" : "ParagraphNode", + "parentPath" : "PlatformInvoice[0]/Settlement[5]/PaymentPanel[0]/PaymentNoteLayer[3]/PaymentNoteHolder[0]/PaymentNote[0]/PaymentNoteText[1]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 62.202, + "computedY" : 117.787, + "placementX" : 62.202, + "placementY" : 117.787, + "placementWidth" : 119.195, + "placementHeight" : 10.962, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 119.195, + "contentHeight" : 10.962, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/Settlement[5]/PaymentPanel[0]/PaymentNoteLayer[3]/PaymentNoteHolder[0]/PaymentNote[0]/PaymentNoteText[1]/PaymentNoteLine_1[1]", + "entityName" : "PaymentNoteLine_1", + "entityKind" : "ParagraphNode", + "parentPath" : "PlatformInvoice[0]/Settlement[5]/PaymentPanel[0]/PaymentNoteLayer[3]/PaymentNoteHolder[0]/PaymentNote[0]/PaymentNoteText[1]", + "childIndex" : 1, + "depth" : 8, + "layer" : 8, + "computedX" : 62.202, + "computedY" : 105.729, + "placementX" : 62.202, + "placementY" : 105.729, + "placementWidth" : 113.869, + "placementHeight" : 10.962, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 113.869, + "contentHeight" : 10.962, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/Settlement[5]/SettlementGap[1]", + "entityName" : "SettlementGap", + "entityKind" : "SectionNode", + "parentPath" : "PlatformInvoice[0]/Settlement[5]", + "childIndex" : 1, + "depth" : 3, + "layer" : 3, + "computedX" : 266.828, + "computedY" : 267.817, + "placementX" : 266.828, + "placementY" : 267.817, + "placementWidth" : 0.0, + "placementHeight" : 0.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 0.0, + "contentHeight" : 0.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/Settlement[5]/SettlementRight[2]", + "entityName" : "SettlementRight", + "entityKind" : "SectionNode", + "parentPath" : "PlatformInvoice[0]/Settlement[5]", + "childIndex" : 2, + "depth" : 3, + "layer" : 3, + "computedX" : 313.915, + "computedY" : 123.114, + "placementX" : 313.915, + "placementY" : 123.114, + "placementWidth" : 235.436, + "placementHeight" : 144.703, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 235.436, + "contentHeight" : 144.703, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/Settlement[5]/SettlementRight[2]/Totals[0]", + "entityName" : "Totals", + "entityKind" : "SectionNode", + "parentPath" : "PlatformInvoice[0]/Settlement[5]/SettlementRight[2]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 313.915, + "computedY" : 193.222, + "placementX" : 313.915, + "placementY" : 193.222, + "placementWidth" : 235.436, + "placementHeight" : 67.171, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 235.436, + "contentHeight" : 67.171, + "margin" : { + "top" : 7.424, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 5.813 + } + }, { + "path" : "PlatformInvoice[0]/Settlement[5]/SettlementRight[2]/Totals[0]/TotalsRow_0Layer[0]", + "entityName" : "TotalsRow_0Layer", + "entityKind" : "LayerStackNode", + "parentPath" : "PlatformInvoice[0]/Settlement[5]/SettlementRight[2]/Totals[0]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 319.728, + "computedY" : 248.434, + "placementX" : 319.728, + "placementY" : 248.434, + "placementWidth" : 229.623, + "placementHeight" : 11.959, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 229.623, + "contentHeight" : 11.959, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/Settlement[5]/SettlementRight[2]/Totals[0]/TotalsRow_0Layer[0]/TotalsRow_0Holder[0]", + "entityName" : "TotalsRow_0Holder", + "entityKind" : "SectionNode", + "parentPath" : "PlatformInvoice[0]/Settlement[5]/SettlementRight[2]/Totals[0]/TotalsRow_0Layer[0]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 319.728, + "computedY" : 248.434, + "placementX" : 319.728, + "placementY" : 248.434, + "placementWidth" : 229.623, + "placementHeight" : 11.959, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 229.623, + "contentHeight" : 11.959, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/Settlement[5]/SettlementRight[2]/Totals[0]/TotalsRow_0Layer[0]/TotalsRow_0Holder[0]/TotalsRow_0[0]", + "entityName" : "TotalsRow_0", + "entityKind" : "RowNode", + "parentPath" : "PlatformInvoice[0]/Settlement[5]/SettlementRight[2]/Totals[0]/TotalsRow_0Layer[0]/TotalsRow_0Holder[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 319.728, + "computedY" : 248.434, + "placementX" : 319.728, + "placementY" : 248.434, + "placementWidth" : 229.623, + "placementHeight" : 11.959, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 229.623, + "contentHeight" : 11.959, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 7.557, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/Settlement[5]/SettlementRight[2]/Totals[0]/TotalsRow_0Layer[0]/TotalsRow_0Holder[0]/TotalsRow_0[0]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "PlatformInvoice[0]/Settlement[5]/SettlementRight[2]/Totals[0]/TotalsRow_0Layer[0]/TotalsRow_0Holder[0]/TotalsRow_0[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 319.728, + "computedY" : 248.434, + "placementX" : 319.728, + "placementY" : 248.434, + "placementWidth" : 36.673, + "placementHeight" : 11.959, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 36.673, + "contentHeight" : 11.959, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/Settlement[5]/SettlementRight[2]/Totals[0]/TotalsRow_0Layer[0]/TotalsRow_0Holder[0]/TotalsRow_0[0]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "PlatformInvoice[0]/Settlement[5]/SettlementRight[2]/Totals[0]/TotalsRow_0Layer[0]/TotalsRow_0Holder[0]/TotalsRow_0[0]", + "childIndex" : 1, + "depth" : 8, + "layer" : 8, + "computedX" : 430.761, + "computedY" : 248.434, + "placementX" : 430.761, + "placementY" : 248.434, + "placementWidth" : 111.033, + "placementHeight" : 11.959, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 111.033, + "contentHeight" : 11.959, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/Settlement[5]/SettlementRight[2]/Totals[0]/TotalsRow_1Layer[1]", + "entityName" : "TotalsRow_1Layer", + "entityKind" : "LayerStackNode", + "parentPath" : "PlatformInvoice[0]/Settlement[5]/SettlementRight[2]/Totals[0]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 319.728, + "computedY" : 231.443, + "placementX" : 319.728, + "placementY" : 231.443, + "placementWidth" : 229.623, + "placementHeight" : 16.991, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 229.623, + "contentHeight" : 16.991, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/Settlement[5]/SettlementRight[2]/Totals[0]/TotalsRow_1Layer[1]/TotalsRow_1Holder[0]", + "entityName" : "TotalsRow_1Holder", + "entityKind" : "SectionNode", + "parentPath" : "PlatformInvoice[0]/Settlement[5]/SettlementRight[2]/Totals[0]/TotalsRow_1Layer[1]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 319.728, + "computedY" : 231.443, + "placementX" : 319.728, + "placementY" : 231.443, + "placementWidth" : 229.623, + "placementHeight" : 16.991, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 229.623, + "contentHeight" : 16.991, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/Settlement[5]/SettlementRight[2]/Totals[0]/TotalsRow_1Layer[1]/TotalsRow_1Holder[0]/TotalsRow_1[0]", + "entityName" : "TotalsRow_1", + "entityKind" : "RowNode", + "parentPath" : "PlatformInvoice[0]/Settlement[5]/SettlementRight[2]/Totals[0]/TotalsRow_1Layer[1]/TotalsRow_1Holder[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 319.728, + "computedY" : 231.443, + "placementX" : 319.728, + "placementY" : 231.443, + "placementWidth" : 229.623, + "placementHeight" : 11.959, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 229.623, + "contentHeight" : 11.959, + "margin" : { + "top" : 5.033, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 7.557, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/Settlement[5]/SettlementRight[2]/Totals[0]/TotalsRow_1Layer[1]/TotalsRow_1Holder[0]/TotalsRow_1[0]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "PlatformInvoice[0]/Settlement[5]/SettlementRight[2]/Totals[0]/TotalsRow_1Layer[1]/TotalsRow_1Holder[0]/TotalsRow_1[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 319.728, + "computedY" : 231.443, + "placementX" : 319.728, + "placementY" : 231.443, + "placementWidth" : 36.285, + "placementHeight" : 11.959, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 36.285, + "contentHeight" : 11.959, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/Settlement[5]/SettlementRight[2]/Totals[0]/TotalsRow_1Layer[1]/TotalsRow_1Holder[0]/TotalsRow_1[0]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "PlatformInvoice[0]/Settlement[5]/SettlementRight[2]/Totals[0]/TotalsRow_1Layer[1]/TotalsRow_1Holder[0]/TotalsRow_1[0]", + "childIndex" : 1, + "depth" : 8, + "layer" : 8, + "computedX" : 430.761, + "computedY" : 231.443, + "placementX" : 430.761, + "placementY" : 231.443, + "placementWidth" : 111.033, + "placementHeight" : 11.959, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 111.033, + "contentHeight" : 11.959, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/Settlement[5]/SettlementRight[2]/Totals[0]/TotalsRule[2]", + "entityName" : "TotalsRule", + "entityKind" : "LineNode", + "parentPath" : "PlatformInvoice[0]/Settlement[5]/SettlementRight[2]/Totals[0]", + "childIndex" : 2, + "depth" : 5, + "layer" : 5, + "computedX" : 319.728, + "computedY" : 222.421, + "placementX" : 319.728, + "placementY" : 222.421, + "placementWidth" : 229.623, + "placementHeight" : 1.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 229.623, + "contentHeight" : 1.0, + "margin" : { + "top" : 8.022, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/Settlement[5]/SettlementRight[2]/Totals[0]/TotalDueLayer[3]", + "entityName" : "TotalDueLayer", + "entityKind" : "LayerStackNode", + "parentPath" : "PlatformInvoice[0]/Settlement[5]/SettlementRight[2]/Totals[0]", + "childIndex" : 3, + "depth" : 5, + "layer" : 5, + "computedX" : 319.728, + "computedY" : 193.222, + "placementX" : 319.728, + "placementY" : 193.222, + "placementWidth" : 229.623, + "placementHeight" : 29.199, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 229.623, + "contentHeight" : 29.199, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/Settlement[5]/SettlementRight[2]/Totals[0]/TotalDueLayer[3]/TotalDueHolder[0]", + "entityName" : "TotalDueHolder", + "entityKind" : "SectionNode", + "parentPath" : "PlatformInvoice[0]/Settlement[5]/SettlementRight[2]/Totals[0]/TotalDueLayer[3]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 319.728, + "computedY" : 193.222, + "placementX" : 319.728, + "placementY" : 193.222, + "placementWidth" : 229.623, + "placementHeight" : 29.199, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 229.623, + "contentHeight" : 29.199, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/Settlement[5]/SettlementRight[2]/Totals[0]/TotalDueLayer[3]/TotalDueHolder[0]/TotalDue[0]", + "entityName" : "TotalDue", + "entityKind" : "RowNode", + "parentPath" : "PlatformInvoice[0]/Settlement[5]/SettlementRight[2]/Totals[0]/TotalDueLayer[3]/TotalDueHolder[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 319.728, + "computedY" : 193.222, + "placementX" : 319.728, + "placementY" : 193.222, + "placementWidth" : 229.623, + "placementHeight" : 29.199, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 229.623, + "contentHeight" : 29.199, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 9.268, + "right" : 7.557, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/Settlement[5]/SettlementRight[2]/Totals[0]/TotalDueLayer[3]/TotalDueHolder[0]/TotalDue[0]/TotalDueLabel[0]", + "entityName" : "TotalDueLabel", + "entityKind" : "ParagraphNode", + "parentPath" : "PlatformInvoice[0]/Settlement[5]/SettlementRight[2]/Totals[0]/TotalDueLayer[3]/TotalDueHolder[0]/TotalDue[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 319.728, + "computedY" : 195.713, + "placementX" : 319.728, + "placementY" : 195.713, + "placementWidth" : 63.094, + "placementHeight" : 14.948, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 63.094, + "contentHeight" : 14.948, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/Settlement[5]/SettlementRight[2]/Totals[0]/TotalDueLayer[3]/TotalDueHolder[0]/TotalDue[0]/TotalDueValue[1]", + "entityName" : "TotalDueValue", + "entityKind" : "ParagraphNode", + "parentPath" : "PlatformInvoice[0]/Settlement[5]/SettlementRight[2]/Totals[0]/TotalDueLayer[3]/TotalDueHolder[0]/TotalDue[0]", + "childIndex" : 1, + "depth" : 8, + "layer" : 8, + "computedX" : 430.761, + "computedY" : 193.222, + "placementX" : 430.761, + "placementY" : 193.222, + "placementWidth" : 111.033, + "placementHeight" : 19.931, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 111.033, + "contentHeight" : 19.931, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/Settlement[5]/SettlementRight[2]/DueDateCard[1]", + "entityName" : "DueDateCard", + "entityKind" : "SectionNode", + "parentPath" : "PlatformInvoice[0]/Settlement[5]/SettlementRight[2]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 313.915, + "computedY" : 123.114, + "placementX" : 313.915, + "placementY" : 123.114, + "placementWidth" : 235.436, + "placementHeight" : 52.02, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 235.436, + "contentHeight" : 52.02, + "margin" : { + "top" : 18.087, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/Settlement[5]/SettlementRight[2]/DueDateCard[1]/DueDateRowLayer[0]", + "entityName" : "DueDateRowLayer", + "entityKind" : "LayerStackNode", + "parentPath" : "PlatformInvoice[0]/Settlement[5]/SettlementRight[2]/DueDateCard[1]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 313.915, + "computedY" : 123.114, + "placementX" : 313.915, + "placementY" : 123.114, + "placementWidth" : 235.436, + "placementHeight" : 52.02, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 235.436, + "contentHeight" : 52.02, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/Settlement[5]/SettlementRight[2]/DueDateCard[1]/DueDateRowLayer[0]/DueDateRowHolder[0]", + "entityName" : "DueDateRowHolder", + "entityKind" : "SectionNode", + "parentPath" : "PlatformInvoice[0]/Settlement[5]/SettlementRight[2]/DueDateCard[1]/DueDateRowLayer[0]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 313.915, + "computedY" : 123.114, + "placementX" : 313.915, + "placementY" : 123.114, + "placementWidth" : 235.436, + "placementHeight" : 52.02, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 235.436, + "contentHeight" : 52.02, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/Settlement[5]/SettlementRight[2]/DueDateCard[1]/DueDateRowLayer[0]/DueDateRowHolder[0]/DueDateRow[0]", + "entityName" : "DueDateRow", + "entityKind" : "RowNode", + "parentPath" : "PlatformInvoice[0]/Settlement[5]/SettlementRight[2]/DueDateCard[1]/DueDateRowLayer[0]/DueDateRowHolder[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 313.915, + "computedY" : 123.114, + "placementX" : 313.915, + "placementY" : 123.114, + "placementWidth" : 235.436, + "placementHeight" : 52.02, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 235.436, + "contentHeight" : 52.02, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 10.962, + "right" : 0.0, + "bottom" : 11.51, + "left" : 13.37 + } + }, { + "path" : "PlatformInvoice[0]/Settlement[5]/SettlementRight[2]/DueDateCard[1]/DueDateRowLayer[0]/DueDateRowHolder[0]/DueDateRow[0]/DueDateDisc[0]", + "entityName" : "DueDateDisc", + "entityKind" : "ShapeContainerNode", + "parentPath" : "PlatformInvoice[0]/Settlement[5]/SettlementRight[2]/DueDateCard[1]/DueDateRowLayer[0]/DueDateRowHolder[0]/DueDateRow[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 327.285, + "computedY" : 135.28, + "placementX" : 327.285, + "placementY" : 135.28, + "placementWidth" : 28.236, + "placementHeight" : 28.236, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 28.236, + "contentHeight" : 28.236, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/Settlement[5]/SettlementRight[2]/DueDateCard[1]/DueDateRowLayer[0]/DueDateRowHolder[0]/DueDateRow[0]/DueDateDisc[0]/SvgIcon[0]", + "entityName" : "SvgIcon", + "entityKind" : "LayerStackNode", + "parentPath" : "PlatformInvoice[0]/Settlement[5]/SettlementRight[2]/DueDateCard[1]/DueDateRowLayer[0]/DueDateRowHolder[0]/DueDateRow[0]/DueDateDisc[0]", + "childIndex" : 0, + "depth" : 9, + "layer" : 9, + "computedX" : 334.062, + "computedY" : 142.057, + "placementX" : 334.062, + "placementY" : 142.057, + "placementWidth" : 14.683, + "placementHeight" : 14.683, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 14.683, + "contentHeight" : 14.683, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/Settlement[5]/SettlementRight[2]/DueDateCard[1]/DueDateRowLayer[0]/DueDateRowHolder[0]/DueDateRow[0]/DueDateDisc[0]/SvgIcon[0]/SvgLayer0[0]", + "entityName" : "SvgLayer0", + "entityKind" : "Path", + "parentPath" : "PlatformInvoice[0]/Settlement[5]/SettlementRight[2]/DueDateCard[1]/DueDateRowLayer[0]/DueDateRowHolder[0]/DueDateRow[0]/DueDateDisc[0]/SvgIcon[0]", + "childIndex" : 0, + "depth" : 10, + "layer" : 10, + "computedX" : 334.062, + "computedY" : 142.057, + "placementX" : 334.062, + "placementY" : 142.057, + "placementWidth" : 14.683, + "placementHeight" : 14.683, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 14.683, + "contentHeight" : 14.683, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/Settlement[5]/SettlementRight[2]/DueDateCard[1]/DueDateRowLayer[0]/DueDateRowHolder[0]/DueDateRow[0]/DueDateText[1]", + "entityName" : "DueDateText", + "entityKind" : "SectionNode", + "parentPath" : "PlatformInvoice[0]/Settlement[5]/SettlementRight[2]/DueDateCard[1]/DueDateRowLayer[0]/DueDateRowHolder[0]/DueDateRow[0]", + "childIndex" : 1, + "depth" : 8, + "layer" : 8, + "computedX" : 367.397, + "computedY" : 134.624, + "placementX" : 367.397, + "placementY" : 134.624, + "placementWidth" : 86.401, + "placementHeight" : 29.548, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 86.401, + "contentHeight" : 29.548, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/Settlement[5]/SettlementRight[2]/DueDateCard[1]/DueDateRowLayer[0]/DueDateRowHolder[0]/DueDateRow[0]/DueDateText[1]/DueDateLabel[0]", + "entityName" : "DueDateLabel", + "entityKind" : "ParagraphNode", + "parentPath" : "PlatformInvoice[0]/Settlement[5]/SettlementRight[2]/DueDateCard[1]/DueDateRowLayer[0]/DueDateRowHolder[0]/DueDateRow[0]/DueDateText[1]", + "childIndex" : 0, + "depth" : 9, + "layer" : 9, + "computedX" : 367.397, + "computedY" : 152.213, + "placementX" : 367.397, + "placementY" : 152.213, + "placementWidth" : 44.735, + "placementHeight" : 11.959, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 44.735, + "contentHeight" : 11.959, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/Settlement[5]/SettlementRight[2]/DueDateCard[1]/DueDateRowLayer[0]/DueDateRowHolder[0]/DueDateRow[0]/DueDateText[1]/DueDateValue[1]", + "entityName" : "DueDateValue", + "entityKind" : "ParagraphNode", + "parentPath" : "PlatformInvoice[0]/Settlement[5]/SettlementRight[2]/DueDateCard[1]/DueDateRowLayer[0]/DueDateRowHolder[0]/DueDateRow[0]/DueDateText[1]", + "childIndex" : 1, + "depth" : 9, + "layer" : 9, + "computedX" : 367.397, + "computedY" : 134.624, + "placementX" : 367.397, + "placementY" : 134.624, + "placementWidth" : 86.401, + "placementHeight" : 14.948, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 86.401, + "contentHeight" : 14.948, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/Settlement[5]/SettlementTail[3]", + "entityName" : "SettlementTail", + "entityKind" : "SectionNode", + "parentPath" : "PlatformInvoice[0]/Settlement[5]", + "childIndex" : 3, + "depth" : 3, + "layer" : 3, + "computedX" : 549.351, + "computedY" : 267.817, + "placementX" : 549.351, + "placementY" : 267.817, + "placementWidth" : 0.0, + "placementHeight" : 0.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 0.0, + "contentHeight" : 0.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/ClosingNoteRule[6]", + "entityName" : "ClosingNoteRule", + "entityKind" : "LineNode", + "parentPath" : "PlatformInvoice[0]", + "childIndex" : 6, + "depth" : 2, + "layer" : 2, + "computedX" : 25.578, + "computedY" : 91.027, + "placementX" : 25.578, + "placementY" : 91.027, + "placementWidth" : 544.119, + "placementHeight" : 1.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 544.119, + "contentHeight" : 1.0, + "margin" : { + "top" : 7.673, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/ClosingNote[7]", + "entityName" : "ClosingNote", + "entityKind" : "RowNode", + "parentPath" : "PlatformInvoice[0]", + "childIndex" : 7, + "depth" : 2, + "layer" : 2, + "computedX" : 25.578, + "computedY" : 55.948, + "placementX" : 25.578, + "placementY" : 55.948, + "placementWidth" : 544.119, + "placementHeight" : 23.02, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 544.119, + "contentHeight" : 23.02, + "margin" : { + "top" : 12.058, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/ClosingNote[7]/ClosingNoteIcon[0]", + "entityName" : "ClosingNoteIcon", + "entityKind" : "SectionNode", + "parentPath" : "PlatformInvoice[0]/ClosingNote[7]", + "childIndex" : 0, + "depth" : 3, + "layer" : 3, + "computedX" : 25.578, + "computedY" : 64.286, + "placementX" : 25.578, + "placementY" : 64.286, + "placementWidth" : 14.683, + "placementHeight" : 14.683, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 14.683, + "contentHeight" : 14.683, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/ClosingNote[7]/ClosingNoteIcon[0]/SvgIcon[0]", + "entityName" : "SvgIcon", + "entityKind" : "LayerStackNode", + "parentPath" : "PlatformInvoice[0]/ClosingNote[7]/ClosingNoteIcon[0]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 25.578, + "computedY" : 64.286, + "placementX" : 25.578, + "placementY" : 64.286, + "placementWidth" : 14.683, + "placementHeight" : 14.683, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 14.683, + "contentHeight" : 14.683, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/ClosingNote[7]/ClosingNoteIcon[0]/SvgIcon[0]/SvgLayer0[0]", + "entityName" : "SvgLayer0", + "entityKind" : "Path", + "parentPath" : "PlatformInvoice[0]/ClosingNote[7]/ClosingNoteIcon[0]/SvgIcon[0]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 25.578, + "computedY" : 64.286, + "placementX" : 25.578, + "placementY" : 64.286, + "placementWidth" : 14.683, + "placementHeight" : 14.683, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 14.683, + "contentHeight" : 14.683, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/ClosingNote[7]/ClosingNoteText[1]", + "entityName" : "ClosingNoteText", + "entityKind" : "SectionNode", + "parentPath" : "PlatformInvoice[0]/ClosingNote[7]", + "childIndex" : 1, + "depth" : 3, + "layer" : 3, + "computedX" : 51.738, + "computedY" : 55.948, + "placementX" : 51.738, + "placementY" : 55.948, + "placementWidth" : 297.384, + "placementHeight" : 23.02, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 297.384, + "contentHeight" : 23.02, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/ClosingNote[7]/ClosingNoteText[1]/ClosingNoteLead[0]", + "entityName" : "ClosingNoteLead", + "entityKind" : "ParagraphNode", + "parentPath" : "PlatformInvoice[0]/ClosingNote[7]/ClosingNoteText[1]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 51.738, + "computedY" : 68.006, + "placementX" : 51.738, + "placementY" : 68.006, + "placementWidth" : 152.794, + "placementHeight" : 10.962, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 152.794, + "contentHeight" : 10.962, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/ClosingNote[7]/ClosingNoteText[1]/ClosingNoteLine_0[1]", + "entityName" : "ClosingNoteLine_0", + "entityKind" : "ParagraphNode", + "parentPath" : "PlatformInvoice[0]/ClosingNote[7]/ClosingNoteText[1]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 51.738, + "computedY" : 55.948, + "placementX" : 51.738, + "placementY" : 55.948, + "placementWidth" : 297.384, + "placementHeight" : 10.962, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 297.384, + "contentHeight" : 10.962, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/FooterIdentityRule[8]", + "entityName" : "FooterIdentityRule", + "entityKind" : "LineNode", + "parentPath" : "PlatformInvoice[0]", + "childIndex" : 8, + "depth" : 2, + "layer" : 2, + "computedX" : 25.578, + "computedY" : 45.082, + "placementX" : 25.578, + "placementY" : 45.082, + "placementWidth" : 544.119, + "placementHeight" : 1.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 544.119, + "contentHeight" : 1.0, + "margin" : { + "top" : 9.866, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/FooterIdentity[9]", + "entityName" : "FooterIdentity", + "entityKind" : "RowNode", + "parentPath" : "PlatformInvoice[0]", + "childIndex" : 9, + "depth" : 2, + "layer" : 2, + "computedX" : 26.16, + "computedY" : 14.039, + "placementX" : 26.16, + "placementY" : 14.039, + "placementWidth" : 543.538, + "placementHeight" : 21.725, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 543.538, + "contentHeight" : 21.725, + "margin" : { + "top" : 9.318, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.581 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/FooterIdentity[9]/FooterLockup[0]", + "entityName" : "FooterLockup", + "entityKind" : "SectionNode", + "parentPath" : "PlatformInvoice[0]/FooterIdentity[9]", + "childIndex" : 0, + "depth" : 3, + "layer" : 3, + "computedX" : 26.16, + "computedY" : 24.802, + "placementX" : 26.16, + "placementY" : 24.802, + "placementWidth" : 52.865, + "placementHeight" : 10.962, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 52.865, + "contentHeight" : 10.962, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/FooterIdentity[9]/FooterLockup[0]/FooterWordmark[0]", + "entityName" : "FooterWordmark", + "entityKind" : "ParagraphNode", + "parentPath" : "PlatformInvoice[0]/FooterIdentity[9]/FooterLockup[0]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 26.16, + "computedY" : 24.802, + "placementX" : 26.16, + "placementY" : 24.802, + "placementWidth" : 52.865, + "placementHeight" : 10.962, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 52.865, + "contentHeight" : 10.962, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/FooterIdentity[9]/FooterAddress[1]", + "entityName" : "FooterAddress", + "entityKind" : "SectionNode", + "parentPath" : "PlatformInvoice[0]/FooterIdentity[9]", + "childIndex" : 1, + "depth" : 3, + "layer" : 3, + "computedX" : 130.798, + "computedY" : 14.039, + "placementX" : 130.798, + "placementY" : 14.039, + "placementWidth" : 438.9, + "placementHeight" : 20.928, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 438.9, + "contentHeight" : 20.928, + "margin" : { + "top" : 0.797, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/FooterIdentity[9]/FooterAddress[1]/FooterIdentityName[0]", + "entityName" : "FooterIdentityName", + "entityKind" : "ParagraphNode", + "parentPath" : "PlatformInvoice[0]/FooterIdentity[9]/FooterAddress[1]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 130.798, + "computedY" : 25.001, + "placementX" : 130.798, + "placementY" : 25.001, + "placementWidth" : 438.9, + "placementHeight" : 9.966, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 438.9, + "contentHeight" : 9.966, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "PlatformInvoice[0]/FooterIdentity[9]/FooterAddress[1]/FooterIdentityAddress[1]", + "entityName" : "FooterIdentityAddress", + "entityKind" : "ParagraphNode", + "parentPath" : "PlatformInvoice[0]/FooterIdentity[9]/FooterAddress[1]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 130.798, + "computedY" : 14.039, + "placementX" : 130.798, + "placementY" : 14.039, + "placementWidth" : 438.9, + "placementHeight" : 9.966, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 438.9, + "contentHeight" : 9.966, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + } ] +} diff --git a/qa/src/test/resources/layout-snapshots/canonical-templates/invoice/subscription_invoice_layout.json b/qa/src/test/resources/layout-snapshots/canonical-templates/invoice/subscription_invoice_layout.json new file mode 100644 index 000000000..95370e330 --- /dev/null +++ b/qa/src/test/resources/layout-snapshots/canonical-templates/invoice/subscription_invoice_layout.json @@ -0,0 +1,5057 @@ +{ + "formatVersion" : "2.0", + "canvas" : { + "pageWidth" : 595.276, + "pageHeight" : 841.89, + "innerWidth" : 595.276, + "innerHeight" : 813.113, + "margin" : { + "top" : 17.492, + "right" : 0.0, + "bottom" : 11.285, + "left" : 0.0 + } + }, + "totalPages" : 1, + "nodes" : [ { + "path" : "SubscriptionInvoice[0]", + "entityName" : "SubscriptionInvoice", + "entityKind" : "ContainerNode", + "parentPath" : null, + "childIndex" : 0, + "depth" : 1, + "layer" : 1, + "computedX" : 0.0, + "computedY" : 15.283, + "placementX" : 0.0, + "placementY" : 15.283, + "placementWidth" : 595.276, + "placementHeight" : 809.116, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 595.276, + "contentHeight" : 809.116, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SubscriptionInvoice[0]/Masthead[0]", + "entityName" : "Masthead", + "entityKind" : "RowNode", + "parentPath" : "SubscriptionInvoice[0]", + "childIndex" : 0, + "depth" : 2, + "layer" : 2, + "computedX" : 0.0, + "computedY" : 773.592, + "placementX" : 0.0, + "placementY" : 773.592, + "placementWidth" : 595.276, + "placementHeight" : 50.806, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 595.276, + "contentHeight" : 50.806, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 27.084, + "bottom" : 0.0, + "left" : 27.084 + } + }, { + "path" : "SubscriptionInvoice[0]/Masthead[0]/BrandMark[0]", + "entityName" : "BrandMark", + "entityKind" : "SectionNode", + "parentPath" : "SubscriptionInvoice[0]/Masthead[0]", + "childIndex" : 0, + "depth" : 3, + "layer" : 3, + "computedX" : 27.084, + "computedY" : 824.398, + "placementX" : 27.084, + "placementY" : 824.398, + "placementWidth" : 0.0, + "placementHeight" : 0.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 0.0, + "contentHeight" : 0.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SubscriptionInvoice[0]/Masthead[0]/BrandWordmark[1]", + "entityName" : "BrandWordmark", + "entityKind" : "ParagraphNode", + "parentPath" : "SubscriptionInvoice[0]/Masthead[0]", + "childIndex" : 1, + "depth" : 3, + "layer" : 3, + "computedX" : 66.581, + "computedY" : 783.031, + "placementX" : 66.581, + "placementY" : 783.031, + "placementWidth" : 95.37, + "placementHeight" : 31.493, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 95.37, + "contentHeight" : 31.493, + "margin" : { + "top" : 9.874, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SubscriptionInvoice[0]/Masthead[0]/InvoiceTitle[2]", + "entityName" : "InvoiceTitle", + "entityKind" : "ParagraphNode", + "parentPath" : "SubscriptionInvoice[0]/Masthead[0]", + "childIndex" : 2, + "depth" : 3, + "layer" : 3, + "computedX" : 161.951, + "computedY" : 773.592, + "placementX" : 161.951, + "placementY" : 773.592, + "placementWidth" : 406.241, + "placementHeight" : 50.806, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 406.241, + "contentHeight" : 50.806, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SubscriptionInvoice[0]/IdentityBand[1]", + "entityName" : "IdentityBand", + "entityKind" : "RowNode", + "parentPath" : "SubscriptionInvoice[0]", + "childIndex" : 1, + "depth" : 2, + "layer" : 2, + "computedX" : 0.0, + "computedY" : 629.767, + "placementX" : 0.0, + "placementY" : 629.767, + "placementWidth" : 595.276, + "placementHeight" : 132.597, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 595.276, + "contentHeight" : 132.597, + "margin" : { + "top" : 11.228, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 27.084, + "bottom" : 0.0, + "left" : 27.084 + } + }, { + "path" : "SubscriptionInvoice[0]/IdentityBand[1]/SupplierIdentity[0]", + "entityName" : "SupplierIdentity", + "entityKind" : "SectionNode", + "parentPath" : "SubscriptionInvoice[0]/IdentityBand[1]", + "childIndex" : 0, + "depth" : 3, + "layer" : 3, + "computedX" : 27.084, + "computedY" : 637.753, + "placementX" : 27.084, + "placementY" : 637.753, + "placementWidth" : 330.082, + "placementHeight" : 123.087, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 330.082, + "contentHeight" : 123.087, + "margin" : { + "top" : 1.523, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SubscriptionInvoice[0]/IdentityBand[1]/SupplierIdentity[0]/SupplierName[0]", + "entityName" : "SupplierName", + "entityKind" : "ParagraphNode", + "parentPath" : "SubscriptionInvoice[0]/IdentityBand[1]/SupplierIdentity[0]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 27.084, + "computedY" : 746.185, + "placementX" : 27.084, + "placementY" : 746.185, + "placementWidth" : 116.134, + "placementHeight" : 14.656, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 116.134, + "contentHeight" : 14.656, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SubscriptionInvoice[0]/IdentityBand[1]/SupplierIdentity[0]/SupplierAddress[1]", + "entityName" : "SupplierAddress", + "entityKind" : "SectionNode", + "parentPath" : "SubscriptionInvoice[0]/IdentityBand[1]/SupplierIdentity[0]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 27.084, + "computedY" : 699.199, + "placementX" : 27.084, + "placementY" : 699.199, + "placementWidth" : 72.296, + "placementHeight" : 43.263, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 72.296, + "contentHeight" : 43.263, + "margin" : { + "top" : 3.722, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SubscriptionInvoice[0]/IdentityBand[1]/SupplierIdentity[0]/SupplierAddress[1]/SupplierAddress_0[0]", + "entityName" : "SupplierAddress_0", + "entityKind" : "ParagraphNode", + "parentPath" : "SubscriptionInvoice[0]/IdentityBand[1]/SupplierIdentity[0]/SupplierAddress[1]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 27.084, + "computedY" : 729.668, + "placementX" : 27.084, + "placementY" : 729.668, + "placementWidth" : 72.296, + "placementHeight" : 12.794, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 72.296, + "contentHeight" : 12.794, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SubscriptionInvoice[0]/IdentityBand[1]/SupplierIdentity[0]/SupplierAddress[1]/SupplierAddress_1[1]", + "entityName" : "SupplierAddress_1", + "entityKind" : "ParagraphNode", + "parentPath" : "SubscriptionInvoice[0]/IdentityBand[1]/SupplierIdentity[0]/SupplierAddress[1]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 27.084, + "computedY" : 714.434, + "placementX" : 27.084, + "placementY" : 714.434, + "placementWidth" : 61.496, + "placementHeight" : 12.794, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 61.496, + "contentHeight" : 12.794, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SubscriptionInvoice[0]/IdentityBand[1]/SupplierIdentity[0]/SupplierAddress[1]/SupplierAddress_2[2]", + "entityName" : "SupplierAddress_2", + "entityKind" : "ParagraphNode", + "parentPath" : "SubscriptionInvoice[0]/IdentityBand[1]/SupplierIdentity[0]/SupplierAddress[1]", + "childIndex" : 2, + "depth" : 5, + "layer" : 5, + "computedX" : 27.084, + "computedY" : 699.199, + "placementX" : 27.084, + "placementY" : 699.199, + "placementWidth" : 70.025, + "placementHeight" : 12.794, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 70.025, + "contentHeight" : 12.794, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SubscriptionInvoice[0]/IdentityBand[1]/SupplierIdentity[0]/SupplierContacts[2]", + "entityName" : "SupplierContacts", + "entityKind" : "SectionNode", + "parentPath" : "SubscriptionInvoice[0]/IdentityBand[1]/SupplierIdentity[0]", + "childIndex" : 2, + "depth" : 4, + "layer" : 4, + "computedX" : 27.084, + "computedY" : 637.753, + "placementX" : 27.084, + "placementY" : 637.753, + "placementWidth" : 330.082, + "placementHeight" : 46.648, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 330.082, + "contentHeight" : 46.648, + "margin" : { + "top" : 14.798, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SubscriptionInvoice[0]/IdentityBand[1]/SupplierIdentity[0]/SupplierContacts[2]/ContactEmailLayer[0]", + "entityName" : "ContactEmailLayer", + "entityKind" : "LayerStackNode", + "parentPath" : "SubscriptionInvoice[0]/IdentityBand[1]/SupplierIdentity[0]/SupplierContacts[2]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 27.084, + "computedY" : 671.608, + "placementX" : 27.084, + "placementY" : 671.608, + "placementWidth" : 330.082, + "placementHeight" : 12.794, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 330.082, + "contentHeight" : 12.794, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SubscriptionInvoice[0]/IdentityBand[1]/SupplierIdentity[0]/SupplierContacts[2]/ContactEmailLayer[0]/ContactEmailHolder[0]", + "entityName" : "ContactEmailHolder", + "entityKind" : "SectionNode", + "parentPath" : "SubscriptionInvoice[0]/IdentityBand[1]/SupplierIdentity[0]/SupplierContacts[2]/ContactEmailLayer[0]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 27.084, + "computedY" : 671.608, + "placementX" : 27.084, + "placementY" : 671.608, + "placementWidth" : 330.082, + "placementHeight" : 12.794, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 330.082, + "contentHeight" : 12.794, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SubscriptionInvoice[0]/IdentityBand[1]/SupplierIdentity[0]/SupplierContacts[2]/ContactEmailLayer[0]/ContactEmailHolder[0]/ContactEmail[0]", + "entityName" : "ContactEmail", + "entityKind" : "RowNode", + "parentPath" : "SubscriptionInvoice[0]/IdentityBand[1]/SupplierIdentity[0]/SupplierContacts[2]/ContactEmailLayer[0]/ContactEmailHolder[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 27.084, + "computedY" : 671.608, + "placementX" : 27.084, + "placementY" : 671.608, + "placementWidth" : 330.082, + "placementHeight" : 12.794, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 330.082, + "contentHeight" : 12.794, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SubscriptionInvoice[0]/IdentityBand[1]/SupplierIdentity[0]/SupplierContacts[2]/ContactEmailLayer[0]/ContactEmailHolder[0]/ContactEmail[0]/ContactEmailLabel[0]", + "entityName" : "ContactEmailLabel", + "entityKind" : "ParagraphNode", + "parentPath" : "SubscriptionInvoice[0]/IdentityBand[1]/SupplierIdentity[0]/SupplierContacts[2]/ContactEmailLayer[0]/ContactEmailHolder[0]/ContactEmail[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 27.084, + "computedY" : 671.608, + "placementX" : 27.084, + "placementY" : 671.608, + "placementWidth" : 5.054, + "placementHeight" : 12.794, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 5.054, + "contentHeight" : 12.794, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SubscriptionInvoice[0]/IdentityBand[1]/SupplierIdentity[0]/SupplierContacts[2]/ContactEmailLayer[0]/ContactEmailHolder[0]/ContactEmail[0]/ContactEmailValue[1]", + "entityName" : "ContactEmailValue", + "entityKind" : "ParagraphNode", + "parentPath" : "SubscriptionInvoice[0]/IdentityBand[1]/SupplierIdentity[0]/SupplierContacts[2]/ContactEmailLayer[0]/ContactEmailHolder[0]/ContactEmail[0]", + "childIndex" : 1, + "depth" : 8, + "layer" : 8, + "computedX" : 66.581, + "computedY" : 671.608, + "placementX" : 66.581, + "placementY" : 671.608, + "placementWidth" : 114.43, + "placementHeight" : 12.794, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 114.43, + "contentHeight" : 12.794, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SubscriptionInvoice[0]/IdentityBand[1]/SupplierIdentity[0]/SupplierContacts[2]/ContactWebsiteLayer[1]", + "entityName" : "ContactWebsiteLayer", + "entityKind" : "LayerStackNode", + "parentPath" : "SubscriptionInvoice[0]/IdentityBand[1]/SupplierIdentity[0]/SupplierContacts[2]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 27.084, + "computedY" : 654.681, + "placementX" : 27.084, + "placementY" : 654.681, + "placementWidth" : 330.082, + "placementHeight" : 12.794, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 330.082, + "contentHeight" : 12.794, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SubscriptionInvoice[0]/IdentityBand[1]/SupplierIdentity[0]/SupplierContacts[2]/ContactWebsiteLayer[1]/ContactWebsiteHolder[0]", + "entityName" : "ContactWebsiteHolder", + "entityKind" : "SectionNode", + "parentPath" : "SubscriptionInvoice[0]/IdentityBand[1]/SupplierIdentity[0]/SupplierContacts[2]/ContactWebsiteLayer[1]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 27.084, + "computedY" : 654.681, + "placementX" : 27.084, + "placementY" : 654.681, + "placementWidth" : 330.082, + "placementHeight" : 12.794, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 330.082, + "contentHeight" : 12.794, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SubscriptionInvoice[0]/IdentityBand[1]/SupplierIdentity[0]/SupplierContacts[2]/ContactWebsiteLayer[1]/ContactWebsiteHolder[0]/ContactWebsite[0]", + "entityName" : "ContactWebsite", + "entityKind" : "RowNode", + "parentPath" : "SubscriptionInvoice[0]/IdentityBand[1]/SupplierIdentity[0]/SupplierContacts[2]/ContactWebsiteLayer[1]/ContactWebsiteHolder[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 27.084, + "computedY" : 654.681, + "placementX" : 27.084, + "placementY" : 654.681, + "placementWidth" : 330.082, + "placementHeight" : 12.794, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 330.082, + "contentHeight" : 12.794, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SubscriptionInvoice[0]/IdentityBand[1]/SupplierIdentity[0]/SupplierContacts[2]/ContactWebsiteLayer[1]/ContactWebsiteHolder[0]/ContactWebsite[0]/ContactWebsiteLabel[0]", + "entityName" : "ContactWebsiteLabel", + "entityKind" : "ParagraphNode", + "parentPath" : "SubscriptionInvoice[0]/IdentityBand[1]/SupplierIdentity[0]/SupplierContacts[2]/ContactWebsiteLayer[1]/ContactWebsiteHolder[0]/ContactWebsite[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 27.084, + "computedY" : 654.681, + "placementX" : 27.084, + "placementY" : 654.681, + "placementWidth" : 8.231, + "placementHeight" : 12.794, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 8.231, + "contentHeight" : 12.794, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SubscriptionInvoice[0]/IdentityBand[1]/SupplierIdentity[0]/SupplierContacts[2]/ContactWebsiteLayer[1]/ContactWebsiteHolder[0]/ContactWebsite[0]/ContactWebsiteValue[1]", + "entityName" : "ContactWebsiteValue", + "entityKind" : "ParagraphNode", + "parentPath" : "SubscriptionInvoice[0]/IdentityBand[1]/SupplierIdentity[0]/SupplierContacts[2]/ContactWebsiteLayer[1]/ContactWebsiteHolder[0]/ContactWebsite[0]", + "childIndex" : 1, + "depth" : 8, + "layer" : 8, + "computedX" : 66.581, + "computedY" : 654.681, + "placementX" : 66.581, + "placementY" : 654.681, + "placementWidth" : 100.485, + "placementHeight" : 12.794, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 100.485, + "contentHeight" : 12.794, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SubscriptionInvoice[0]/IdentityBand[1]/SupplierIdentity[0]/SupplierContacts[2]/ContactRegistrationLayer[2]", + "entityName" : "ContactRegistrationLayer", + "entityKind" : "LayerStackNode", + "parentPath" : "SubscriptionInvoice[0]/IdentityBand[1]/SupplierIdentity[0]/SupplierContacts[2]", + "childIndex" : 2, + "depth" : 5, + "layer" : 5, + "computedX" : 27.084, + "computedY" : 637.753, + "placementX" : 27.084, + "placementY" : 637.753, + "placementWidth" : 330.082, + "placementHeight" : 12.794, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 330.082, + "contentHeight" : 12.794, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SubscriptionInvoice[0]/IdentityBand[1]/SupplierIdentity[0]/SupplierContacts[2]/ContactRegistrationLayer[2]/ContactRegistrationHolder[0]", + "entityName" : "ContactRegistrationHolder", + "entityKind" : "SectionNode", + "parentPath" : "SubscriptionInvoice[0]/IdentityBand[1]/SupplierIdentity[0]/SupplierContacts[2]/ContactRegistrationLayer[2]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 27.084, + "computedY" : 637.753, + "placementX" : 27.084, + "placementY" : 637.753, + "placementWidth" : 330.082, + "placementHeight" : 12.794, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 330.082, + "contentHeight" : 12.794, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SubscriptionInvoice[0]/IdentityBand[1]/SupplierIdentity[0]/SupplierContacts[2]/ContactRegistrationLayer[2]/ContactRegistrationHolder[0]/ContactRegistration[0]", + "entityName" : "ContactRegistration", + "entityKind" : "RowNode", + "parentPath" : "SubscriptionInvoice[0]/IdentityBand[1]/SupplierIdentity[0]/SupplierContacts[2]/ContactRegistrationLayer[2]/ContactRegistrationHolder[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 27.084, + "computedY" : 637.753, + "placementX" : 27.084, + "placementY" : 637.753, + "placementWidth" : 330.082, + "placementHeight" : 12.794, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 330.082, + "contentHeight" : 12.794, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SubscriptionInvoice[0]/IdentityBand[1]/SupplierIdentity[0]/SupplierContacts[2]/ContactRegistrationLayer[2]/ContactRegistrationHolder[0]/ContactRegistration[0]/ContactRegistrationLabel[0]", + "entityName" : "ContactRegistrationLabel", + "entityKind" : "ParagraphNode", + "parentPath" : "SubscriptionInvoice[0]/IdentityBand[1]/SupplierIdentity[0]/SupplierContacts[2]/ContactRegistrationLayer[2]/ContactRegistrationHolder[0]/ContactRegistration[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 27.084, + "computedY" : 637.753, + "placementX" : 27.084, + "placementY" : 637.753, + "placementWidth" : 30.908, + "placementHeight" : 12.794, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 30.908, + "contentHeight" : 12.794, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SubscriptionInvoice[0]/IdentityBand[1]/SupplierIdentity[0]/SupplierContacts[2]/ContactRegistrationLayer[2]/ContactRegistrationHolder[0]/ContactRegistration[0]/ContactRegistrationValue[1]", + "entityName" : "ContactRegistrationValue", + "entityKind" : "ParagraphNode", + "parentPath" : "SubscriptionInvoice[0]/IdentityBand[1]/SupplierIdentity[0]/SupplierContacts[2]/ContactRegistrationLayer[2]/ContactRegistrationHolder[0]/ContactRegistration[0]", + "childIndex" : 1, + "depth" : 8, + "layer" : 8, + "computedX" : 66.581, + "computedY" : 637.753, + "placementX" : 66.581, + "placementY" : 637.753, + "placementWidth" : 63.479, + "placementHeight" : 12.794, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 63.479, + "contentHeight" : 12.794, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SubscriptionInvoice[0]/IdentityBand[1]/MetaPanel[1]", + "entityName" : "MetaPanel", + "entityKind" : "SectionNode", + "parentPath" : "SubscriptionInvoice[0]/IdentityBand[1]", + "childIndex" : 1, + "depth" : 3, + "layer" : 3, + "computedX" : 357.165, + "computedY" : 629.767, + "placementX" : 357.165, + "placementY" : 629.767, + "placementWidth" : 211.027, + "placementHeight" : 132.597, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 211.027, + "contentHeight" : 132.597, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SubscriptionInvoice[0]/IdentityBand[1]/MetaPanel[1]/MetaRow_0[0]", + "entityName" : "MetaRow_0", + "entityKind" : "SectionNode", + "parentPath" : "SubscriptionInvoice[0]/IdentityBand[1]/MetaPanel[1]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 357.165, + "computedY" : 737.537, + "placementX" : 357.165, + "placementY" : 737.537, + "placementWidth" : 211.027, + "placementHeight" : 24.827, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 211.027, + "contentHeight" : 24.827, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SubscriptionInvoice[0]/IdentityBand[1]/MetaPanel[1]/MetaRow_0[0]/MetaLine_0Layer[0]", + "entityName" : "MetaLine_0Layer", + "entityKind" : "LayerStackNode", + "parentPath" : "SubscriptionInvoice[0]/IdentityBand[1]/MetaPanel[1]/MetaRow_0[0]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 357.165, + "computedY" : 740.358, + "placementX" : 357.165, + "placementY" : 740.358, + "placementWidth" : 211.027, + "placementHeight" : 22.005, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 211.027, + "contentHeight" : 22.005, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SubscriptionInvoice[0]/IdentityBand[1]/MetaPanel[1]/MetaRow_0[0]/MetaLine_0Layer[0]/MetaLine_0Holder[0]", + "entityName" : "MetaLine_0Holder", + "entityKind" : "SectionNode", + "parentPath" : "SubscriptionInvoice[0]/IdentityBand[1]/MetaPanel[1]/MetaRow_0[0]/MetaLine_0Layer[0]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 357.165, + "computedY" : 740.358, + "placementX" : 357.165, + "placementY" : 740.358, + "placementWidth" : 211.027, + "placementHeight" : 22.005, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 211.027, + "contentHeight" : 22.005, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SubscriptionInvoice[0]/IdentityBand[1]/MetaPanel[1]/MetaRow_0[0]/MetaLine_0Layer[0]/MetaLine_0Holder[0]/MetaLine_0[0]", + "entityName" : "MetaLine_0", + "entityKind" : "RowNode", + "parentPath" : "SubscriptionInvoice[0]/IdentityBand[1]/MetaPanel[1]/MetaRow_0[0]/MetaLine_0Layer[0]/MetaLine_0Holder[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 357.165, + "computedY" : 740.358, + "placementX" : 357.165, + "placementY" : 740.358, + "placementWidth" : 211.027, + "placementHeight" : 22.005, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 211.027, + "contentHeight" : 22.005, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 8.464, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SubscriptionInvoice[0]/IdentityBand[1]/MetaPanel[1]/MetaRow_0[0]/MetaLine_0Layer[0]/MetaLine_0Holder[0]/MetaLine_0[0]/MetaAccent_0[0]", + "entityName" : "MetaAccent_0", + "entityKind" : "ShapeNode", + "parentPath" : "SubscriptionInvoice[0]/IdentityBand[1]/MetaPanel[1]/MetaRow_0[0]/MetaLine_0Layer[0]/MetaLine_0Holder[0]/MetaLine_0[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 357.165, + "computedY" : 740.358, + "placementX" : 357.165, + "placementY" : 740.358, + "placementWidth" : 2.257, + "placementHeight" : 22.005, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 2.257, + "contentHeight" : 22.005, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SubscriptionInvoice[0]/IdentityBand[1]/MetaPanel[1]/MetaRow_0[0]/MetaLine_0Layer[0]/MetaLine_0Holder[0]/MetaLine_0[0]/MetaLabel_0[1]", + "entityName" : "MetaLabel_0", + "entityKind" : "ParagraphNode", + "parentPath" : "SubscriptionInvoice[0]/IdentityBand[1]/MetaPanel[1]/MetaRow_0[0]/MetaLine_0Layer[0]/MetaLine_0Holder[0]/MetaLine_0[0]", + "childIndex" : 1, + "depth" : 8, + "layer" : 8, + "computedX" : 359.422, + "computedY" : 744.766, + "placementX" : 359.422, + "placementY" : 744.766, + "placementWidth" : 57.296, + "placementHeight" : 13.19, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 57.296, + "contentHeight" : 13.19, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 9.592 + } + }, { + "path" : "SubscriptionInvoice[0]/IdentityBand[1]/MetaPanel[1]/MetaRow_0[0]/MetaLine_0Layer[0]/MetaLine_0Holder[0]/MetaLine_0[0]/MetaValue_0[2]", + "entityName" : "MetaValue_0", + "entityKind" : "ParagraphNode", + "parentPath" : "SubscriptionInvoice[0]/IdentityBand[1]/MetaPanel[1]/MetaRow_0[0]/MetaLine_0Layer[0]/MetaLine_0Holder[0]/MetaLine_0[0]", + "childIndex" : 2, + "depth" : 8, + "layer" : 8, + "computedX" : 459.575, + "computedY" : 744.718, + "placementX" : 459.575, + "placementY" : 744.718, + "placementWidth" : 100.153, + "placementHeight" : 13.286, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 100.153, + "contentHeight" : 13.286, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SubscriptionInvoice[0]/IdentityBand[1]/MetaPanel[1]/MetaRow_0[0]/MetaSeparator_0[1]", + "entityName" : "MetaSeparator_0", + "entityKind" : "LineNode", + "parentPath" : "SubscriptionInvoice[0]/IdentityBand[1]/MetaPanel[1]/MetaRow_0[0]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 360.551, + "computedY" : 737.537, + "placementX" : 360.551, + "placementY" : 737.537, + "placementWidth" : 207.641, + "placementHeight" : 1.128, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 207.641, + "contentHeight" : 1.128, + "margin" : { + "top" : 1.693, + "right" : 0.0, + "bottom" : 0.0, + "left" : 3.385 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SubscriptionInvoice[0]/IdentityBand[1]/MetaPanel[1]/MetaRow_1[1]", + "entityName" : "MetaRow_1", + "entityKind" : "SectionNode", + "parentPath" : "SubscriptionInvoice[0]/IdentityBand[1]/MetaPanel[1]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 357.165, + "computedY" : 709.889, + "placementX" : 357.165, + "placementY" : 709.889, + "placementWidth" : 211.027, + "placementHeight" : 24.827, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 211.027, + "contentHeight" : 24.827, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SubscriptionInvoice[0]/IdentityBand[1]/MetaPanel[1]/MetaRow_1[1]/MetaLine_1Layer[0]", + "entityName" : "MetaLine_1Layer", + "entityKind" : "LayerStackNode", + "parentPath" : "SubscriptionInvoice[0]/IdentityBand[1]/MetaPanel[1]/MetaRow_1[1]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 357.165, + "computedY" : 712.71, + "placementX" : 357.165, + "placementY" : 712.71, + "placementWidth" : 211.027, + "placementHeight" : 22.005, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 211.027, + "contentHeight" : 22.005, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SubscriptionInvoice[0]/IdentityBand[1]/MetaPanel[1]/MetaRow_1[1]/MetaLine_1Layer[0]/MetaLine_1Holder[0]", + "entityName" : "MetaLine_1Holder", + "entityKind" : "SectionNode", + "parentPath" : "SubscriptionInvoice[0]/IdentityBand[1]/MetaPanel[1]/MetaRow_1[1]/MetaLine_1Layer[0]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 357.165, + "computedY" : 712.71, + "placementX" : 357.165, + "placementY" : 712.71, + "placementWidth" : 211.027, + "placementHeight" : 22.005, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 211.027, + "contentHeight" : 22.005, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SubscriptionInvoice[0]/IdentityBand[1]/MetaPanel[1]/MetaRow_1[1]/MetaLine_1Layer[0]/MetaLine_1Holder[0]/MetaLine_1[0]", + "entityName" : "MetaLine_1", + "entityKind" : "RowNode", + "parentPath" : "SubscriptionInvoice[0]/IdentityBand[1]/MetaPanel[1]/MetaRow_1[1]/MetaLine_1Layer[0]/MetaLine_1Holder[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 357.165, + "computedY" : 712.71, + "placementX" : 357.165, + "placementY" : 712.71, + "placementWidth" : 211.027, + "placementHeight" : 22.005, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 211.027, + "contentHeight" : 22.005, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 8.464, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SubscriptionInvoice[0]/IdentityBand[1]/MetaPanel[1]/MetaRow_1[1]/MetaLine_1Layer[0]/MetaLine_1Holder[0]/MetaLine_1[0]/MetaAccent_1[0]", + "entityName" : "MetaAccent_1", + "entityKind" : "ShapeNode", + "parentPath" : "SubscriptionInvoice[0]/IdentityBand[1]/MetaPanel[1]/MetaRow_1[1]/MetaLine_1Layer[0]/MetaLine_1Holder[0]/MetaLine_1[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 357.165, + "computedY" : 712.71, + "placementX" : 357.165, + "placementY" : 712.71, + "placementWidth" : 2.257, + "placementHeight" : 22.005, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 2.257, + "contentHeight" : 22.005, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SubscriptionInvoice[0]/IdentityBand[1]/MetaPanel[1]/MetaRow_1[1]/MetaLine_1Layer[0]/MetaLine_1Holder[0]/MetaLine_1[0]/MetaLabel_1[1]", + "entityName" : "MetaLabel_1", + "entityKind" : "ParagraphNode", + "parentPath" : "SubscriptionInvoice[0]/IdentityBand[1]/MetaPanel[1]/MetaRow_1[1]/MetaLine_1Layer[0]/MetaLine_1Holder[0]/MetaLine_1[0]", + "childIndex" : 1, + "depth" : 8, + "layer" : 8, + "computedX" : 359.422, + "computedY" : 717.118, + "placementX" : 359.422, + "placementY" : 717.118, + "placementWidth" : 57.186, + "placementHeight" : 13.19, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 57.186, + "contentHeight" : 13.19, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 9.592 + } + }, { + "path" : "SubscriptionInvoice[0]/IdentityBand[1]/MetaPanel[1]/MetaRow_1[1]/MetaLine_1Layer[0]/MetaLine_1Holder[0]/MetaLine_1[0]/MetaValue_1[2]", + "entityName" : "MetaValue_1", + "entityKind" : "ParagraphNode", + "parentPath" : "SubscriptionInvoice[0]/IdentityBand[1]/MetaPanel[1]/MetaRow_1[1]/MetaLine_1Layer[0]/MetaLine_1Holder[0]/MetaLine_1[0]", + "childIndex" : 2, + "depth" : 8, + "layer" : 8, + "computedX" : 459.575, + "computedY" : 717.07, + "placementX" : 459.575, + "placementY" : 717.07, + "placementWidth" : 100.153, + "placementHeight" : 13.286, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 100.153, + "contentHeight" : 13.286, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SubscriptionInvoice[0]/IdentityBand[1]/MetaPanel[1]/MetaRow_1[1]/MetaSeparator_1[1]", + "entityName" : "MetaSeparator_1", + "entityKind" : "LineNode", + "parentPath" : "SubscriptionInvoice[0]/IdentityBand[1]/MetaPanel[1]/MetaRow_1[1]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 360.551, + "computedY" : 709.889, + "placementX" : 360.551, + "placementY" : 709.889, + "placementWidth" : 207.641, + "placementHeight" : 1.128, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 207.641, + "contentHeight" : 1.128, + "margin" : { + "top" : 1.693, + "right" : 0.0, + "bottom" : 0.0, + "left" : 3.385 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SubscriptionInvoice[0]/IdentityBand[1]/MetaPanel[1]/MetaRow_2[2]", + "entityName" : "MetaRow_2", + "entityKind" : "SectionNode", + "parentPath" : "SubscriptionInvoice[0]/IdentityBand[1]/MetaPanel[1]", + "childIndex" : 2, + "depth" : 4, + "layer" : 4, + "computedX" : 357.165, + "computedY" : 682.241, + "placementX" : 357.165, + "placementY" : 682.241, + "placementWidth" : 211.027, + "placementHeight" : 24.827, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 211.027, + "contentHeight" : 24.827, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SubscriptionInvoice[0]/IdentityBand[1]/MetaPanel[1]/MetaRow_2[2]/MetaLine_2Layer[0]", + "entityName" : "MetaLine_2Layer", + "entityKind" : "LayerStackNode", + "parentPath" : "SubscriptionInvoice[0]/IdentityBand[1]/MetaPanel[1]/MetaRow_2[2]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 357.165, + "computedY" : 685.062, + "placementX" : 357.165, + "placementY" : 685.062, + "placementWidth" : 211.027, + "placementHeight" : 22.005, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 211.027, + "contentHeight" : 22.005, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SubscriptionInvoice[0]/IdentityBand[1]/MetaPanel[1]/MetaRow_2[2]/MetaLine_2Layer[0]/MetaLine_2Holder[0]", + "entityName" : "MetaLine_2Holder", + "entityKind" : "SectionNode", + "parentPath" : "SubscriptionInvoice[0]/IdentityBand[1]/MetaPanel[1]/MetaRow_2[2]/MetaLine_2Layer[0]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 357.165, + "computedY" : 685.062, + "placementX" : 357.165, + "placementY" : 685.062, + "placementWidth" : 211.027, + "placementHeight" : 22.005, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 211.027, + "contentHeight" : 22.005, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SubscriptionInvoice[0]/IdentityBand[1]/MetaPanel[1]/MetaRow_2[2]/MetaLine_2Layer[0]/MetaLine_2Holder[0]/MetaLine_2[0]", + "entityName" : "MetaLine_2", + "entityKind" : "RowNode", + "parentPath" : "SubscriptionInvoice[0]/IdentityBand[1]/MetaPanel[1]/MetaRow_2[2]/MetaLine_2Layer[0]/MetaLine_2Holder[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 357.165, + "computedY" : 685.062, + "placementX" : 357.165, + "placementY" : 685.062, + "placementWidth" : 211.027, + "placementHeight" : 22.005, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 211.027, + "contentHeight" : 22.005, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 8.464, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SubscriptionInvoice[0]/IdentityBand[1]/MetaPanel[1]/MetaRow_2[2]/MetaLine_2Layer[0]/MetaLine_2Holder[0]/MetaLine_2[0]/MetaAccent_2[0]", + "entityName" : "MetaAccent_2", + "entityKind" : "ShapeNode", + "parentPath" : "SubscriptionInvoice[0]/IdentityBand[1]/MetaPanel[1]/MetaRow_2[2]/MetaLine_2Layer[0]/MetaLine_2Holder[0]/MetaLine_2[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 357.165, + "computedY" : 685.062, + "placementX" : 357.165, + "placementY" : 685.062, + "placementWidth" : 2.257, + "placementHeight" : 22.005, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 2.257, + "contentHeight" : 22.005, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SubscriptionInvoice[0]/IdentityBand[1]/MetaPanel[1]/MetaRow_2[2]/MetaLine_2Layer[0]/MetaLine_2Holder[0]/MetaLine_2[0]/MetaLabel_2[1]", + "entityName" : "MetaLabel_2", + "entityKind" : "ParagraphNode", + "parentPath" : "SubscriptionInvoice[0]/IdentityBand[1]/MetaPanel[1]/MetaRow_2[2]/MetaLine_2Layer[0]/MetaLine_2Holder[0]/MetaLine_2[0]", + "childIndex" : 1, + "depth" : 8, + "layer" : 8, + "computedX" : 359.422, + "computedY" : 689.47, + "placementX" : 359.422, + "placementY" : 689.47, + "placementWidth" : 51.141, + "placementHeight" : 13.19, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 51.141, + "contentHeight" : 13.19, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 9.592 + } + }, { + "path" : "SubscriptionInvoice[0]/IdentityBand[1]/MetaPanel[1]/MetaRow_2[2]/MetaLine_2Layer[0]/MetaLine_2Holder[0]/MetaLine_2[0]/MetaValue_2[2]", + "entityName" : "MetaValue_2", + "entityKind" : "ParagraphNode", + "parentPath" : "SubscriptionInvoice[0]/IdentityBand[1]/MetaPanel[1]/MetaRow_2[2]/MetaLine_2Layer[0]/MetaLine_2Holder[0]/MetaLine_2[0]", + "childIndex" : 2, + "depth" : 8, + "layer" : 8, + "computedX" : 459.575, + "computedY" : 689.422, + "placementX" : 459.575, + "placementY" : 689.422, + "placementWidth" : 100.153, + "placementHeight" : 13.286, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 100.153, + "contentHeight" : 13.286, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SubscriptionInvoice[0]/IdentityBand[1]/MetaPanel[1]/MetaRow_2[2]/MetaSeparator_2[1]", + "entityName" : "MetaSeparator_2", + "entityKind" : "LineNode", + "parentPath" : "SubscriptionInvoice[0]/IdentityBand[1]/MetaPanel[1]/MetaRow_2[2]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 360.551, + "computedY" : 682.241, + "placementX" : 360.551, + "placementY" : 682.241, + "placementWidth" : 207.641, + "placementHeight" : 1.128, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 207.641, + "contentHeight" : 1.128, + "margin" : { + "top" : 1.693, + "right" : 0.0, + "bottom" : 0.0, + "left" : 3.385 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SubscriptionInvoice[0]/IdentityBand[1]/MetaPanel[1]/MetaRow_3[3]", + "entityName" : "MetaRow_3", + "entityKind" : "SectionNode", + "parentPath" : "SubscriptionInvoice[0]/IdentityBand[1]/MetaPanel[1]", + "childIndex" : 3, + "depth" : 4, + "layer" : 4, + "computedX" : 357.165, + "computedY" : 654.593, + "placementX" : 357.165, + "placementY" : 654.593, + "placementWidth" : 211.027, + "placementHeight" : 24.827, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 211.027, + "contentHeight" : 24.827, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SubscriptionInvoice[0]/IdentityBand[1]/MetaPanel[1]/MetaRow_3[3]/MetaLine_3Layer[0]", + "entityName" : "MetaLine_3Layer", + "entityKind" : "LayerStackNode", + "parentPath" : "SubscriptionInvoice[0]/IdentityBand[1]/MetaPanel[1]/MetaRow_3[3]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 357.165, + "computedY" : 657.415, + "placementX" : 357.165, + "placementY" : 657.415, + "placementWidth" : 211.027, + "placementHeight" : 22.005, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 211.027, + "contentHeight" : 22.005, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SubscriptionInvoice[0]/IdentityBand[1]/MetaPanel[1]/MetaRow_3[3]/MetaLine_3Layer[0]/MetaLine_3Holder[0]", + "entityName" : "MetaLine_3Holder", + "entityKind" : "SectionNode", + "parentPath" : "SubscriptionInvoice[0]/IdentityBand[1]/MetaPanel[1]/MetaRow_3[3]/MetaLine_3Layer[0]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 357.165, + "computedY" : 657.415, + "placementX" : 357.165, + "placementY" : 657.415, + "placementWidth" : 211.027, + "placementHeight" : 22.005, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 211.027, + "contentHeight" : 22.005, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SubscriptionInvoice[0]/IdentityBand[1]/MetaPanel[1]/MetaRow_3[3]/MetaLine_3Layer[0]/MetaLine_3Holder[0]/MetaLine_3[0]", + "entityName" : "MetaLine_3", + "entityKind" : "RowNode", + "parentPath" : "SubscriptionInvoice[0]/IdentityBand[1]/MetaPanel[1]/MetaRow_3[3]/MetaLine_3Layer[0]/MetaLine_3Holder[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 357.165, + "computedY" : 657.415, + "placementX" : 357.165, + "placementY" : 657.415, + "placementWidth" : 211.027, + "placementHeight" : 22.005, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 211.027, + "contentHeight" : 22.005, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 8.464, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SubscriptionInvoice[0]/IdentityBand[1]/MetaPanel[1]/MetaRow_3[3]/MetaLine_3Layer[0]/MetaLine_3Holder[0]/MetaLine_3[0]/MetaAccent_3[0]", + "entityName" : "MetaAccent_3", + "entityKind" : "ShapeNode", + "parentPath" : "SubscriptionInvoice[0]/IdentityBand[1]/MetaPanel[1]/MetaRow_3[3]/MetaLine_3Layer[0]/MetaLine_3Holder[0]/MetaLine_3[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 357.165, + "computedY" : 657.415, + "placementX" : 357.165, + "placementY" : 657.415, + "placementWidth" : 2.257, + "placementHeight" : 22.005, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 2.257, + "contentHeight" : 22.005, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SubscriptionInvoice[0]/IdentityBand[1]/MetaPanel[1]/MetaRow_3[3]/MetaLine_3Layer[0]/MetaLine_3Holder[0]/MetaLine_3[0]/MetaLabel_3[1]", + "entityName" : "MetaLabel_3", + "entityKind" : "ParagraphNode", + "parentPath" : "SubscriptionInvoice[0]/IdentityBand[1]/MetaPanel[1]/MetaRow_3[3]/MetaLine_3Layer[0]/MetaLine_3Holder[0]/MetaLine_3[0]", + "childIndex" : 1, + "depth" : 8, + "layer" : 8, + "computedX" : 359.422, + "computedY" : 661.822, + "placementX" : 359.422, + "placementY" : 661.822, + "placementWidth" : 80.324, + "placementHeight" : 13.19, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 80.324, + "contentHeight" : 13.19, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 9.592 + } + }, { + "path" : "SubscriptionInvoice[0]/IdentityBand[1]/MetaPanel[1]/MetaRow_3[3]/MetaLine_3Layer[0]/MetaLine_3Holder[0]/MetaLine_3[0]/MetaValue_3[2]", + "entityName" : "MetaValue_3", + "entityKind" : "ParagraphNode", + "parentPath" : "SubscriptionInvoice[0]/IdentityBand[1]/MetaPanel[1]/MetaRow_3[3]/MetaLine_3Layer[0]/MetaLine_3Holder[0]/MetaLine_3[0]", + "childIndex" : 2, + "depth" : 8, + "layer" : 8, + "computedX" : 459.575, + "computedY" : 661.774, + "placementX" : 459.575, + "placementY" : 661.774, + "placementWidth" : 100.153, + "placementHeight" : 13.286, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 100.153, + "contentHeight" : 13.286, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SubscriptionInvoice[0]/IdentityBand[1]/MetaPanel[1]/MetaRow_3[3]/MetaSeparator_3[1]", + "entityName" : "MetaSeparator_3", + "entityKind" : "LineNode", + "parentPath" : "SubscriptionInvoice[0]/IdentityBand[1]/MetaPanel[1]/MetaRow_3[3]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 360.551, + "computedY" : 654.593, + "placementX" : 360.551, + "placementY" : 654.593, + "placementWidth" : 207.641, + "placementHeight" : 1.128, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 207.641, + "contentHeight" : 1.128, + "margin" : { + "top" : 1.693, + "right" : 0.0, + "bottom" : 0.0, + "left" : 3.385 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SubscriptionInvoice[0]/IdentityBand[1]/MetaPanel[1]/MetaRow_4[4]", + "entityName" : "MetaRow_4", + "entityKind" : "SectionNode", + "parentPath" : "SubscriptionInvoice[0]/IdentityBand[1]/MetaPanel[1]", + "childIndex" : 4, + "depth" : 4, + "layer" : 4, + "computedX" : 357.165, + "computedY" : 629.767, + "placementX" : 357.165, + "placementY" : 629.767, + "placementWidth" : 211.027, + "placementHeight" : 22.005, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 211.027, + "contentHeight" : 22.005, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SubscriptionInvoice[0]/IdentityBand[1]/MetaPanel[1]/MetaRow_4[4]/MetaLine_4Layer[0]", + "entityName" : "MetaLine_4Layer", + "entityKind" : "LayerStackNode", + "parentPath" : "SubscriptionInvoice[0]/IdentityBand[1]/MetaPanel[1]/MetaRow_4[4]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 357.165, + "computedY" : 629.767, + "placementX" : 357.165, + "placementY" : 629.767, + "placementWidth" : 211.027, + "placementHeight" : 22.005, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 211.027, + "contentHeight" : 22.005, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SubscriptionInvoice[0]/IdentityBand[1]/MetaPanel[1]/MetaRow_4[4]/MetaLine_4Layer[0]/MetaLine_4Holder[0]", + "entityName" : "MetaLine_4Holder", + "entityKind" : "SectionNode", + "parentPath" : "SubscriptionInvoice[0]/IdentityBand[1]/MetaPanel[1]/MetaRow_4[4]/MetaLine_4Layer[0]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 357.165, + "computedY" : 629.767, + "placementX" : 357.165, + "placementY" : 629.767, + "placementWidth" : 211.027, + "placementHeight" : 22.005, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 211.027, + "contentHeight" : 22.005, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SubscriptionInvoice[0]/IdentityBand[1]/MetaPanel[1]/MetaRow_4[4]/MetaLine_4Layer[0]/MetaLine_4Holder[0]/MetaLine_4[0]", + "entityName" : "MetaLine_4", + "entityKind" : "RowNode", + "parentPath" : "SubscriptionInvoice[0]/IdentityBand[1]/MetaPanel[1]/MetaRow_4[4]/MetaLine_4Layer[0]/MetaLine_4Holder[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 357.165, + "computedY" : 629.767, + "placementX" : 357.165, + "placementY" : 629.767, + "placementWidth" : 211.027, + "placementHeight" : 22.005, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 211.027, + "contentHeight" : 22.005, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 8.464, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SubscriptionInvoice[0]/IdentityBand[1]/MetaPanel[1]/MetaRow_4[4]/MetaLine_4Layer[0]/MetaLine_4Holder[0]/MetaLine_4[0]/MetaAccent_4[0]", + "entityName" : "MetaAccent_4", + "entityKind" : "ShapeNode", + "parentPath" : "SubscriptionInvoice[0]/IdentityBand[1]/MetaPanel[1]/MetaRow_4[4]/MetaLine_4Layer[0]/MetaLine_4Holder[0]/MetaLine_4[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 357.165, + "computedY" : 629.767, + "placementX" : 357.165, + "placementY" : 629.767, + "placementWidth" : 2.257, + "placementHeight" : 22.005, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 2.257, + "contentHeight" : 22.005, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SubscriptionInvoice[0]/IdentityBand[1]/MetaPanel[1]/MetaRow_4[4]/MetaLine_4Layer[0]/MetaLine_4Holder[0]/MetaLine_4[0]/MetaLabel_4[1]", + "entityName" : "MetaLabel_4", + "entityKind" : "ParagraphNode", + "parentPath" : "SubscriptionInvoice[0]/IdentityBand[1]/MetaPanel[1]/MetaRow_4[4]/MetaLine_4Layer[0]/MetaLine_4Holder[0]/MetaLine_4[0]", + "childIndex" : 1, + "depth" : 8, + "layer" : 8, + "computedX" : 359.422, + "computedY" : 634.174, + "placementX" : 359.422, + "placementY" : 634.174, + "placementWidth" : 50.459, + "placementHeight" : 13.19, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 50.459, + "contentHeight" : 13.19, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 9.592 + } + }, { + "path" : "SubscriptionInvoice[0]/IdentityBand[1]/MetaPanel[1]/MetaRow_4[4]/MetaLine_4Layer[0]/MetaLine_4Holder[0]/MetaLine_4[0]/MetaValue_4[2]", + "entityName" : "MetaValue_4", + "entityKind" : "ParagraphNode", + "parentPath" : "SubscriptionInvoice[0]/IdentityBand[1]/MetaPanel[1]/MetaRow_4[4]/MetaLine_4Layer[0]/MetaLine_4Holder[0]/MetaLine_4[0]", + "childIndex" : 2, + "depth" : 8, + "layer" : 8, + "computedX" : 459.575, + "computedY" : 634.126, + "placementX" : 459.575, + "placementY" : 634.126, + "placementWidth" : 100.153, + "placementHeight" : 13.286, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 100.153, + "contentHeight" : 13.286, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SubscriptionInvoice[0]/Parties[2]", + "entityName" : "Parties", + "entityKind" : "RowNode", + "parentPath" : "SubscriptionInvoice[0]", + "childIndex" : 2, + "depth" : 2, + "layer" : 2, + "computedX" : 0.0, + "computedY" : 502.861, + "placementX" : 0.0, + "placementY" : 502.861, + "placementWidth" : 595.276, + "placementHeight" : 105.634, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 595.276, + "contentHeight" : 105.634, + "margin" : { + "top" : 21.272, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 27.084, + "bottom" : 0.0, + "left" : 27.084 + } + }, { + "path" : "SubscriptionInvoice[0]/Parties[2]/Party_0[0]", + "entityName" : "Party_0", + "entityKind" : "SectionNode", + "parentPath" : "SubscriptionInvoice[0]/Parties[2]", + "childIndex" : 0, + "depth" : 3, + "layer" : 3, + "computedX" : 27.084, + "computedY" : 502.861, + "placementX" : 27.084, + "placementY" : 502.861, + "placementWidth" : 277.043, + "placementHeight" : 105.634, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 277.043, + "contentHeight" : 105.634, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SubscriptionInvoice[0]/Parties[2]/Party_0[0]/PartyHeading_0Layer[0]", + "entityName" : "PartyHeading_0Layer", + "entityKind" : "LayerStackNode", + "parentPath" : "SubscriptionInvoice[0]/Parties[2]/Party_0[0]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 27.084, + "computedY" : 589.401, + "placementX" : 27.084, + "placementY" : 589.401, + "placementWidth" : 277.043, + "placementHeight" : 19.094, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 277.043, + "contentHeight" : 19.094, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SubscriptionInvoice[0]/Parties[2]/Party_0[0]/PartyHeading_0Layer[0]/PartyHeading_0Holder[0]", + "entityName" : "PartyHeading_0Holder", + "entityKind" : "SectionNode", + "parentPath" : "SubscriptionInvoice[0]/Parties[2]/Party_0[0]/PartyHeading_0Layer[0]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 27.084, + "computedY" : 589.401, + "placementX" : 27.084, + "placementY" : 589.401, + "placementWidth" : 277.043, + "placementHeight" : 19.094, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 277.043, + "contentHeight" : 19.094, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SubscriptionInvoice[0]/Parties[2]/Party_0[0]/PartyHeading_0Layer[0]/PartyHeading_0Holder[0]/PartyHeading_0[0]", + "entityName" : "PartyHeading_0", + "entityKind" : "RowNode", + "parentPath" : "SubscriptionInvoice[0]/Parties[2]/Party_0[0]/PartyHeading_0Layer[0]/PartyHeading_0Holder[0]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 27.084, + "computedY" : 589.401, + "placementX" : 27.084, + "placementY" : 589.401, + "placementWidth" : 277.043, + "placementHeight" : 19.094, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 277.043, + "contentHeight" : 19.094, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SubscriptionInvoice[0]/Parties[2]/Party_0[0]/PartyHeading_0Layer[0]/PartyHeading_0Holder[0]/PartyHeading_0[0]/PartyHeading_0Rule[0]", + "entityName" : "PartyHeading_0Rule", + "entityKind" : "SectionNode", + "parentPath" : "SubscriptionInvoice[0]/Parties[2]/Party_0[0]/PartyHeading_0Layer[0]/PartyHeading_0Holder[0]/PartyHeading_0[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 27.084, + "computedY" : 589.401, + "placementX" : 27.084, + "placementY" : 589.401, + "placementWidth" : 38.946, + "placementHeight" : 19.094, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 38.946, + "contentHeight" : 19.094, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 3.95, + "left" : 0.0 + } + }, { + "path" : "SubscriptionInvoice[0]/Parties[2]/Party_0[0]/PartyHeading_0Layer[0]/PartyHeading_0Holder[0]/PartyHeading_0[0]/PartyHeading_0Rule[0]/PartyHeading_0Text[0]", + "entityName" : "PartyHeading_0Text", + "entityKind" : "ParagraphNode", + "parentPath" : "SubscriptionInvoice[0]/Parties[2]/Party_0[0]/PartyHeading_0Layer[0]/PartyHeading_0Holder[0]/PartyHeading_0[0]/PartyHeading_0Rule[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 27.084, + "computedY" : 593.351, + "placementX" : 27.084, + "placementY" : 593.351, + "placementWidth" : 38.946, + "placementHeight" : 15.144, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 38.946, + "contentHeight" : 15.144, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SubscriptionInvoice[0]/Parties[2]/Party_0[0]/PartyHeading_0Layer[0]/PartyHeading_0Holder[0]/PartyHeading_0[0]/SpacerNode[1]", + "entityName" : null, + "entityKind" : "SpacerNode", + "parentPath" : "SubscriptionInvoice[0]/Parties[2]/Party_0[0]/PartyHeading_0Layer[0]/PartyHeading_0Holder[0]/PartyHeading_0[0]", + "childIndex" : 1, + "depth" : 7, + "layer" : 7, + "computedX" : 66.029, + "computedY" : 608.495, + "placementX" : 66.029, + "placementY" : 608.495, + "placementWidth" : 0.564, + "placementHeight" : 0.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 0.564, + "contentHeight" : 0.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SubscriptionInvoice[0]/Parties[2]/Party_0[0]/PartyAddress_0[1]", + "entityName" : "PartyAddress_0", + "entityKind" : "SectionNode", + "parentPath" : "SubscriptionInvoice[0]/Parties[2]/Party_0[0]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 27.084, + "computedY" : 502.861, + "placementX" : 27.084, + "placementY" : 502.861, + "placementWidth" : 106.616, + "placementHeight" : 73.732, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 106.616, + "contentHeight" : 73.732, + "margin" : { + "top" : 12.808, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SubscriptionInvoice[0]/Parties[2]/Party_0[0]/PartyAddress_0[1]/PartyName_0[0]", + "entityName" : "PartyName_0", + "entityKind" : "ParagraphNode", + "parentPath" : "SubscriptionInvoice[0]/Parties[2]/Party_0[0]/PartyAddress_0[1]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 27.084, + "computedY" : 563.799, + "placementX" : 27.084, + "placementY" : 563.799, + "placementWidth" : 106.616, + "placementHeight" : 12.794, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 106.616, + "contentHeight" : 12.794, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SubscriptionInvoice[0]/Parties[2]/Party_0[0]/PartyAddress_0[1]/PartySubline_0[1]", + "entityName" : "PartySubline_0", + "entityKind" : "ParagraphNode", + "parentPath" : "SubscriptionInvoice[0]/Parties[2]/Party_0[0]/PartyAddress_0[1]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 27.084, + "computedY" : 548.564, + "placementX" : 27.084, + "placementY" : 548.564, + "placementWidth" : 85.378, + "placementHeight" : 12.794, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 85.378, + "contentHeight" : 12.794, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SubscriptionInvoice[0]/Parties[2]/Party_0[0]/PartyAddress_0[1]/PartyAddress_0_0[2]", + "entityName" : "PartyAddress_0_0", + "entityKind" : "ParagraphNode", + "parentPath" : "SubscriptionInvoice[0]/Parties[2]/Party_0[0]/PartyAddress_0[1]", + "childIndex" : 2, + "depth" : 5, + "layer" : 5, + "computedX" : 27.084, + "computedY" : 533.33, + "placementX" : 27.084, + "placementY" : 533.33, + "placementWidth" : 62.029, + "placementHeight" : 12.794, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 62.029, + "contentHeight" : 12.794, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SubscriptionInvoice[0]/Parties[2]/Party_0[0]/PartyAddress_0[1]/PartyAddress_0_1[3]", + "entityName" : "PartyAddress_0_1", + "entityKind" : "ParagraphNode", + "parentPath" : "SubscriptionInvoice[0]/Parties[2]/Party_0[0]/PartyAddress_0[1]", + "childIndex" : 3, + "depth" : 5, + "layer" : 5, + "computedX" : 27.084, + "computedY" : 518.095, + "placementX" : 27.084, + "placementY" : 518.095, + "placementWidth" : 67.733, + "placementHeight" : 12.794, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 67.733, + "contentHeight" : 12.794, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SubscriptionInvoice[0]/Parties[2]/Party_0[0]/PartyAddress_0[1]/PartyAddress_0_2[4]", + "entityName" : "PartyAddress_0_2", + "entityKind" : "ParagraphNode", + "parentPath" : "SubscriptionInvoice[0]/Parties[2]/Party_0[0]/PartyAddress_0[1]", + "childIndex" : 4, + "depth" : 5, + "layer" : 5, + "computedX" : 27.084, + "computedY" : 502.861, + "placementX" : 27.084, + "placementY" : 502.861, + "placementWidth" : 70.025, + "placementHeight" : 12.794, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 70.025, + "contentHeight" : 12.794, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SubscriptionInvoice[0]/Parties[2]/Party_1[1]", + "entityName" : "Party_1", + "entityKind" : "SectionNode", + "parentPath" : "SubscriptionInvoice[0]/Parties[2]", + "childIndex" : 1, + "depth" : 3, + "layer" : 3, + "computedX" : 304.127, + "computedY" : 502.861, + "placementX" : 304.127, + "placementY" : 502.861, + "placementWidth" : 264.065, + "placementHeight" : 105.634, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 264.065, + "contentHeight" : 105.634, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SubscriptionInvoice[0]/Parties[2]/Party_1[1]/PartyHeading_1Layer[0]", + "entityName" : "PartyHeading_1Layer", + "entityKind" : "LayerStackNode", + "parentPath" : "SubscriptionInvoice[0]/Parties[2]/Party_1[1]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 304.127, + "computedY" : 589.401, + "placementX" : 304.127, + "placementY" : 589.401, + "placementWidth" : 264.065, + "placementHeight" : 19.094, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 264.065, + "contentHeight" : 19.094, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SubscriptionInvoice[0]/Parties[2]/Party_1[1]/PartyHeading_1Layer[0]/PartyHeading_1Holder[0]", + "entityName" : "PartyHeading_1Holder", + "entityKind" : "SectionNode", + "parentPath" : "SubscriptionInvoice[0]/Parties[2]/Party_1[1]/PartyHeading_1Layer[0]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 304.127, + "computedY" : 589.401, + "placementX" : 304.127, + "placementY" : 589.401, + "placementWidth" : 264.065, + "placementHeight" : 19.094, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 264.065, + "contentHeight" : 19.094, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SubscriptionInvoice[0]/Parties[2]/Party_1[1]/PartyHeading_1Layer[0]/PartyHeading_1Holder[0]/PartyHeading_1[0]", + "entityName" : "PartyHeading_1", + "entityKind" : "RowNode", + "parentPath" : "SubscriptionInvoice[0]/Parties[2]/Party_1[1]/PartyHeading_1Layer[0]/PartyHeading_1Holder[0]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 304.127, + "computedY" : 589.401, + "placementX" : 304.127, + "placementY" : 589.401, + "placementWidth" : 264.065, + "placementHeight" : 19.094, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 264.065, + "contentHeight" : 19.094, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SubscriptionInvoice[0]/Parties[2]/Party_1[1]/PartyHeading_1Layer[0]/PartyHeading_1Holder[0]/PartyHeading_1[0]/PartyHeading_1Rule[0]", + "entityName" : "PartyHeading_1Rule", + "entityKind" : "SectionNode", + "parentPath" : "SubscriptionInvoice[0]/Parties[2]/Party_1[1]/PartyHeading_1Layer[0]/PartyHeading_1Holder[0]/PartyHeading_1[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 304.127, + "computedY" : 589.401, + "placementX" : 304.127, + "placementY" : 589.401, + "placementWidth" : 41.167, + "placementHeight" : 19.094, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 41.167, + "contentHeight" : 19.094, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 3.95, + "left" : 0.0 + } + }, { + "path" : "SubscriptionInvoice[0]/Parties[2]/Party_1[1]/PartyHeading_1Layer[0]/PartyHeading_1Holder[0]/PartyHeading_1[0]/PartyHeading_1Rule[0]/PartyHeading_1Text[0]", + "entityName" : "PartyHeading_1Text", + "entityKind" : "ParagraphNode", + "parentPath" : "SubscriptionInvoice[0]/Parties[2]/Party_1[1]/PartyHeading_1Layer[0]/PartyHeading_1Holder[0]/PartyHeading_1[0]/PartyHeading_1Rule[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 304.127, + "computedY" : 593.351, + "placementX" : 304.127, + "placementY" : 593.351, + "placementWidth" : 41.167, + "placementHeight" : 15.144, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 41.167, + "contentHeight" : 15.144, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SubscriptionInvoice[0]/Parties[2]/Party_1[1]/PartyHeading_1Layer[0]/PartyHeading_1Holder[0]/PartyHeading_1[0]/SpacerNode[1]", + "entityName" : null, + "entityKind" : "SpacerNode", + "parentPath" : "SubscriptionInvoice[0]/Parties[2]/Party_1[1]/PartyHeading_1Layer[0]/PartyHeading_1Holder[0]/PartyHeading_1[0]", + "childIndex" : 1, + "depth" : 7, + "layer" : 7, + "computedX" : 345.293, + "computedY" : 608.495, + "placementX" : 345.293, + "placementY" : 608.495, + "placementWidth" : 0.564, + "placementHeight" : 0.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 0.564, + "contentHeight" : 0.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SubscriptionInvoice[0]/Parties[2]/Party_1[1]/PartyAddress_1[1]", + "entityName" : "PartyAddress_1", + "entityKind" : "SectionNode", + "parentPath" : "SubscriptionInvoice[0]/Parties[2]/Party_1[1]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 304.127, + "computedY" : 502.861, + "placementX" : 304.127, + "placementY" : 502.861, + "placementWidth" : 110.368, + "placementHeight" : 73.732, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 110.368, + "contentHeight" : 73.732, + "margin" : { + "top" : 12.808, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SubscriptionInvoice[0]/Parties[2]/Party_1[1]/PartyAddress_1[1]/PartyName_1[0]", + "entityName" : "PartyName_1", + "entityKind" : "ParagraphNode", + "parentPath" : "SubscriptionInvoice[0]/Parties[2]/Party_1[1]/PartyAddress_1[1]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 304.127, + "computedY" : 563.799, + "placementX" : 304.127, + "placementY" : 563.799, + "placementWidth" : 106.616, + "placementHeight" : 12.794, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 106.616, + "contentHeight" : 12.794, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SubscriptionInvoice[0]/Parties[2]/Party_1[1]/PartyAddress_1[1]/PartySubline_1[1]", + "entityName" : "PartySubline_1", + "entityKind" : "ParagraphNode", + "parentPath" : "SubscriptionInvoice[0]/Parties[2]/Party_1[1]/PartyAddress_1[1]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 304.127, + "computedY" : 548.564, + "placementX" : 304.127, + "placementY" : 548.564, + "placementWidth" : 99.686, + "placementHeight" : 12.794, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 99.686, + "contentHeight" : 12.794, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SubscriptionInvoice[0]/Parties[2]/Party_1[1]/PartyAddress_1[1]/PartyAddress_1_0[2]", + "entityName" : "PartyAddress_1_0", + "entityKind" : "ParagraphNode", + "parentPath" : "SubscriptionInvoice[0]/Parties[2]/Party_1[1]/PartyAddress_1[1]", + "childIndex" : 2, + "depth" : 5, + "layer" : 5, + "computedX" : 304.127, + "computedY" : 533.33, + "placementX" : 304.127, + "placementY" : 533.33, + "placementWidth" : 110.368, + "placementHeight" : 12.794, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 110.368, + "contentHeight" : 12.794, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SubscriptionInvoice[0]/Parties[2]/Party_1[1]/PartyAddress_1[1]/PartyAddress_1_1[3]", + "entityName" : "PartyAddress_1_1", + "entityKind" : "ParagraphNode", + "parentPath" : "SubscriptionInvoice[0]/Parties[2]/Party_1[1]/PartyAddress_1[1]", + "childIndex" : 3, + "depth" : 5, + "layer" : 5, + "computedX" : 304.127, + "computedY" : 518.095, + "placementX" : 304.127, + "placementY" : 518.095, + "placementWidth" : 69.663, + "placementHeight" : 12.794, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 69.663, + "contentHeight" : 12.794, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SubscriptionInvoice[0]/Parties[2]/Party_1[1]/PartyAddress_1[1]/PartyAddress_1_2[4]", + "entityName" : "PartyAddress_1_2", + "entityKind" : "ParagraphNode", + "parentPath" : "SubscriptionInvoice[0]/Parties[2]/Party_1[1]/PartyAddress_1[1]", + "childIndex" : 4, + "depth" : 5, + "layer" : 5, + "computedX" : 304.127, + "computedY" : 502.861, + "placementX" : 304.127, + "placementY" : 502.861, + "placementWidth" : 70.025, + "placementHeight" : 12.794, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 70.025, + "contentHeight" : 12.794, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SubscriptionInvoice[0]/LineItems[3]", + "entityName" : "LineItems", + "entityKind" : "SectionNode", + "parentPath" : "SubscriptionInvoice[0]", + "childIndex" : 3, + "depth" : 2, + "layer" : 2, + "computedX" : 0.0, + "computedY" : 317.222, + "placementX" : 0.0, + "placementY" : 317.222, + "placementWidth" : 595.276, + "placementHeight" : 160.924, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 595.276, + "contentHeight" : 160.924, + "margin" : { + "top" : 24.714, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 27.084, + "bottom" : 0.0, + "left" : 27.084 + } + }, { + "path" : "SubscriptionInvoice[0]/LineItems[3]/LineItemsRuled[0]", + "entityName" : "LineItemsRuled", + "entityKind" : "SectionNode", + "parentPath" : "SubscriptionInvoice[0]/LineItems[3]", + "childIndex" : 0, + "depth" : 3, + "layer" : 3, + "computedX" : 27.084, + "computedY" : 317.222, + "placementX" : 27.084, + "placementY" : 317.222, + "placementWidth" : 541.108, + "placementHeight" : 160.924, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 541.108, + "contentHeight" : 160.924, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SubscriptionInvoice[0]/LineItems[3]/LineItemsRuled[0]/LineItemTable[0]", + "entityName" : "LineItemTable", + "entityKind" : "TableNode", + "parentPath" : "SubscriptionInvoice[0]/LineItems[3]/LineItemsRuled[0]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 27.084, + "computedY" : 317.222, + "placementX" : 27.084, + "placementY" : 317.222, + "placementWidth" : 541.108, + "placementHeight" : 160.924, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 541.108, + "contentHeight" : 160.924, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SubscriptionInvoice[0]/NotesAndTotals[4]", + "entityName" : "NotesAndTotals", + "entityKind" : "RowNode", + "parentPath" : "SubscriptionInvoice[0]", + "childIndex" : 4, + "depth" : 2, + "layer" : 2, + "computedX" : 0.0, + "computedY" : 228.249, + "placementX" : 0.0, + "placementY" : 228.249, + "placementWidth" : 595.276, + "placementHeight" : 87.788, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 595.276, + "contentHeight" : 87.788, + "margin" : { + "top" : 1.185, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 27.084, + "bottom" : 0.0, + "left" : 27.084 + } + }, { + "path" : "SubscriptionInvoice[0]/NotesAndTotals[4]/Notes[0]", + "entityName" : "Notes", + "entityKind" : "SectionNode", + "parentPath" : "SubscriptionInvoice[0]/NotesAndTotals[4]", + "childIndex" : 0, + "depth" : 3, + "layer" : 3, + "computedX" : 27.084, + "computedY" : 228.249, + "placementX" : 27.084, + "placementY" : 228.249, + "placementWidth" : 325.004, + "placementHeight" : 59.294, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 325.004, + "contentHeight" : 59.294, + "margin" : { + "top" : 28.494, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 99.307, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SubscriptionInvoice[0]/NotesAndTotals[4]/Notes[0]/NotesHeadingLayer[0]", + "entityName" : "NotesHeadingLayer", + "entityKind" : "LayerStackNode", + "parentPath" : "SubscriptionInvoice[0]/NotesAndTotals[4]/Notes[0]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 27.084, + "computedY" : 268.449, + "placementX" : 27.084, + "placementY" : 268.449, + "placementWidth" : 225.697, + "placementHeight" : 19.094, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 225.697, + "contentHeight" : 19.094, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SubscriptionInvoice[0]/NotesAndTotals[4]/Notes[0]/NotesHeadingLayer[0]/NotesHeadingHolder[0]", + "entityName" : "NotesHeadingHolder", + "entityKind" : "SectionNode", + "parentPath" : "SubscriptionInvoice[0]/NotesAndTotals[4]/Notes[0]/NotesHeadingLayer[0]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 27.084, + "computedY" : 268.449, + "placementX" : 27.084, + "placementY" : 268.449, + "placementWidth" : 225.697, + "placementHeight" : 19.094, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 225.697, + "contentHeight" : 19.094, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SubscriptionInvoice[0]/NotesAndTotals[4]/Notes[0]/NotesHeadingLayer[0]/NotesHeadingHolder[0]/NotesHeading[0]", + "entityName" : "NotesHeading", + "entityKind" : "RowNode", + "parentPath" : "SubscriptionInvoice[0]/NotesAndTotals[4]/Notes[0]/NotesHeadingLayer[0]/NotesHeadingHolder[0]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 27.084, + "computedY" : 268.449, + "placementX" : 27.084, + "placementY" : 268.449, + "placementWidth" : 225.697, + "placementHeight" : 19.094, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 225.697, + "contentHeight" : 19.094, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SubscriptionInvoice[0]/NotesAndTotals[4]/Notes[0]/NotesHeadingLayer[0]/NotesHeadingHolder[0]/NotesHeading[0]/NotesHeadingRule[0]", + "entityName" : "NotesHeadingRule", + "entityKind" : "SectionNode", + "parentPath" : "SubscriptionInvoice[0]/NotesAndTotals[4]/Notes[0]/NotesHeadingLayer[0]/NotesHeadingHolder[0]/NotesHeading[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 27.084, + "computedY" : 268.449, + "placementX" : 27.084, + "placementY" : 268.449, + "placementWidth" : 34.226, + "placementHeight" : 19.094, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 34.226, + "contentHeight" : 19.094, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 3.95, + "left" : 0.0 + } + }, { + "path" : "SubscriptionInvoice[0]/NotesAndTotals[4]/Notes[0]/NotesHeadingLayer[0]/NotesHeadingHolder[0]/NotesHeading[0]/NotesHeadingRule[0]/NotesHeadingText[0]", + "entityName" : "NotesHeadingText", + "entityKind" : "ParagraphNode", + "parentPath" : "SubscriptionInvoice[0]/NotesAndTotals[4]/Notes[0]/NotesHeadingLayer[0]/NotesHeadingHolder[0]/NotesHeading[0]/NotesHeadingRule[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 27.084, + "computedY" : 272.399, + "placementX" : 27.084, + "placementY" : 272.399, + "placementWidth" : 34.226, + "placementHeight" : 15.144, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 34.226, + "contentHeight" : 15.144, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SubscriptionInvoice[0]/NotesAndTotals[4]/Notes[0]/NotesHeadingLayer[0]/NotesHeadingHolder[0]/NotesHeading[0]/SpacerNode[1]", + "entityName" : null, + "entityKind" : "SpacerNode", + "parentPath" : "SubscriptionInvoice[0]/NotesAndTotals[4]/Notes[0]/NotesHeadingLayer[0]/NotesHeadingHolder[0]/NotesHeading[0]", + "childIndex" : 1, + "depth" : 7, + "layer" : 7, + "computedX" : 61.309, + "computedY" : 287.543, + "placementX" : 61.309, + "placementY" : 287.543, + "placementWidth" : 0.564, + "placementHeight" : 0.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 0.564, + "contentHeight" : 0.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SubscriptionInvoice[0]/NotesAndTotals[4]/Notes[0]/NotesBody_0[1]", + "entityName" : "NotesBody_0", + "entityKind" : "ParagraphNode", + "parentPath" : "SubscriptionInvoice[0]/NotesAndTotals[4]/Notes[0]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 27.084, + "computedY" : 228.249, + "placementX" : 27.084, + "placementY" : 228.249, + "placementWidth" : 211.39, + "placementHeight" : 28.52, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 211.39, + "contentHeight" : 28.52, + "margin" : { + "top" : 11.68, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SubscriptionInvoice[0]/NotesAndTotals[4]/Totals[1]", + "entityName" : "Totals", + "entityKind" : "SectionNode", + "parentPath" : "SubscriptionInvoice[0]/NotesAndTotals[4]", + "childIndex" : 1, + "depth" : 3, + "layer" : 3, + "computedX" : 352.087, + "computedY" : 230.939, + "placementX" : 352.087, + "placementY" : 230.939, + "placementWidth" : 216.105, + "placementHeight" : 85.099, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 216.105, + "contentHeight" : 85.099, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SubscriptionInvoice[0]/NotesAndTotals[4]/Totals[1]/TotalsTable[0]", + "entityName" : "TotalsTable", + "entityKind" : "TableNode", + "parentPath" : "SubscriptionInvoice[0]/NotesAndTotals[4]/Totals[1]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 352.087, + "computedY" : 230.939, + "placementX" : 352.087, + "placementY" : 230.939, + "placementWidth" : 216.105, + "placementHeight" : 85.099, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 216.105, + "contentHeight" : 85.099, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SubscriptionInvoice[0]/PaymentDetails[5]", + "entityName" : "PaymentDetails", + "entityKind" : "SectionNode", + "parentPath" : "SubscriptionInvoice[0]", + "childIndex" : 5, + "depth" : 2, + "layer" : 2, + "computedX" : 0.0, + "computedY" : 116.289, + "placementX" : 0.0, + "placementY" : 116.289, + "placementWidth" : 595.276, + "placementHeight" : 91.591, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 595.276, + "contentHeight" : 91.591, + "margin" : { + "top" : 20.369, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 27.084, + "bottom" : 0.0, + "left" : 27.084 + } + }, { + "path" : "SubscriptionInvoice[0]/PaymentDetails[5]/PaymentHeadingLayer[0]", + "entityName" : "PaymentHeadingLayer", + "entityKind" : "LayerStackNode", + "parentPath" : "SubscriptionInvoice[0]/PaymentDetails[5]", + "childIndex" : 0, + "depth" : 3, + "layer" : 3, + "computedX" : 27.084, + "computedY" : 188.786, + "placementX" : 27.084, + "placementY" : 188.786, + "placementWidth" : 541.108, + "placementHeight" : 19.094, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 541.108, + "contentHeight" : 19.094, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SubscriptionInvoice[0]/PaymentDetails[5]/PaymentHeadingLayer[0]/PaymentHeadingHolder[0]", + "entityName" : "PaymentHeadingHolder", + "entityKind" : "SectionNode", + "parentPath" : "SubscriptionInvoice[0]/PaymentDetails[5]/PaymentHeadingLayer[0]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 27.084, + "computedY" : 188.786, + "placementX" : 27.084, + "placementY" : 188.786, + "placementWidth" : 541.108, + "placementHeight" : 19.094, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 541.108, + "contentHeight" : 19.094, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SubscriptionInvoice[0]/PaymentDetails[5]/PaymentHeadingLayer[0]/PaymentHeadingHolder[0]/PaymentHeading[0]", + "entityName" : "PaymentHeading", + "entityKind" : "RowNode", + "parentPath" : "SubscriptionInvoice[0]/PaymentDetails[5]/PaymentHeadingLayer[0]/PaymentHeadingHolder[0]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 27.084, + "computedY" : 188.786, + "placementX" : 27.084, + "placementY" : 188.786, + "placementWidth" : 541.108, + "placementHeight" : 19.094, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 541.108, + "contentHeight" : 19.094, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SubscriptionInvoice[0]/PaymentDetails[5]/PaymentHeadingLayer[0]/PaymentHeadingHolder[0]/PaymentHeading[0]/PaymentHeadingRule[0]", + "entityName" : "PaymentHeadingRule", + "entityKind" : "SectionNode", + "parentPath" : "SubscriptionInvoice[0]/PaymentDetails[5]/PaymentHeadingLayer[0]/PaymentHeadingHolder[0]/PaymentHeading[0]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 27.084, + "computedY" : 188.786, + "placementX" : 27.084, + "placementY" : 188.786, + "placementWidth" : 94.336, + "placementHeight" : 19.094, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 94.336, + "contentHeight" : 19.094, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 3.95, + "left" : 0.0 + } + }, { + "path" : "SubscriptionInvoice[0]/PaymentDetails[5]/PaymentHeadingLayer[0]/PaymentHeadingHolder[0]/PaymentHeading[0]/PaymentHeadingRule[0]/PaymentHeadingText[0]", + "entityName" : "PaymentHeadingText", + "entityKind" : "ParagraphNode", + "parentPath" : "SubscriptionInvoice[0]/PaymentDetails[5]/PaymentHeadingLayer[0]/PaymentHeadingHolder[0]/PaymentHeading[0]/PaymentHeadingRule[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 27.084, + "computedY" : 192.736, + "placementX" : 27.084, + "placementY" : 192.736, + "placementWidth" : 94.336, + "placementHeight" : 15.144, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 94.336, + "contentHeight" : 15.144, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SubscriptionInvoice[0]/PaymentDetails[5]/PaymentHeadingLayer[0]/PaymentHeadingHolder[0]/PaymentHeading[0]/SpacerNode[1]", + "entityName" : null, + "entityKind" : "SpacerNode", + "parentPath" : "SubscriptionInvoice[0]/PaymentDetails[5]/PaymentHeadingLayer[0]/PaymentHeadingHolder[0]/PaymentHeading[0]", + "childIndex" : 1, + "depth" : 6, + "layer" : 6, + "computedX" : 121.419, + "computedY" : 207.88, + "placementX" : 121.419, + "placementY" : 207.88, + "placementWidth" : 0.564, + "placementHeight" : 0.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 0.564, + "contentHeight" : 0.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SubscriptionInvoice[0]/PaymentDetails[5]/PaymentColumns[1]", + "entityName" : "PaymentColumns", + "entityKind" : "RowNode", + "parentPath" : "SubscriptionInvoice[0]/PaymentDetails[5]", + "childIndex" : 1, + "depth" : 3, + "layer" : 3, + "computedX" : 27.084, + "computedY" : 116.289, + "placementX" : 27.084, + "placementY" : 116.289, + "placementWidth" : 541.108, + "placementHeight" : 61.833, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 541.108, + "contentHeight" : 61.833, + "margin" : { + "top" : 10.664, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SubscriptionInvoice[0]/PaymentDetails[5]/PaymentColumns[1]/PaymentColumn_0[0]", + "entityName" : "PaymentColumn_0", + "entityKind" : "SectionNode", + "parentPath" : "SubscriptionInvoice[0]/PaymentDetails[5]/PaymentColumns[1]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 27.084, + "computedY" : 116.289, + "placementX" : 27.084, + "placementY" : 116.289, + "placementWidth" : 117.08, + "placementHeight" : 61.833, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 117.08, + "contentHeight" : 61.833, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 11.285, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SubscriptionInvoice[0]/PaymentDetails[5]/PaymentColumns[1]/PaymentColumn_0[0]/Align[0]", + "entityName" : null, + "entityKind" : "Align", + "parentPath" : "SubscriptionInvoice[0]/PaymentDetails[5]/PaymentColumns[1]/PaymentColumn_0[0]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 27.084, + "computedY" : 154.424, + "placementX" : 27.084, + "placementY" : 154.424, + "placementWidth" : 105.795, + "placementHeight" : 23.698, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 105.795, + "contentHeight" : 23.698, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SubscriptionInvoice[0]/PaymentDetails[5]/PaymentColumns[1]/PaymentColumn_0[0]/Align[0]/SvgIcon[0]", + "entityName" : "SvgIcon", + "entityKind" : "LayerStackNode", + "parentPath" : "SubscriptionInvoice[0]/PaymentDetails[5]/PaymentColumns[1]/PaymentColumn_0[0]/Align[0]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 68.132, + "computedY" : 154.424, + "placementX" : 68.132, + "placementY" : 154.424, + "placementWidth" : 23.698, + "placementHeight" : 23.698, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 23.698, + "contentHeight" : 23.698, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SubscriptionInvoice[0]/PaymentDetails[5]/PaymentColumns[1]/PaymentColumn_0[0]/Align[0]/SvgIcon[0]/SvgLayer0[0]", + "entityName" : "SvgLayer0", + "entityKind" : "Path", + "parentPath" : "SubscriptionInvoice[0]/PaymentDetails[5]/PaymentColumns[1]/PaymentColumn_0[0]/Align[0]/SvgIcon[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 68.132, + "computedY" : 154.424, + "placementX" : 68.132, + "placementY" : 154.424, + "placementWidth" : 23.698, + "placementHeight" : 23.698, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 23.698, + "contentHeight" : 23.698, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SubscriptionInvoice[0]/PaymentDetails[5]/PaymentColumns[1]/PaymentColumn_0[0]/Align[0]/SvgIcon[0]/SvgLayer1[1]", + "entityName" : "SvgLayer1", + "entityKind" : "Path", + "parentPath" : "SubscriptionInvoice[0]/PaymentDetails[5]/PaymentColumns[1]/PaymentColumn_0[0]/Align[0]/SvgIcon[0]", + "childIndex" : 1, + "depth" : 7, + "layer" : 7, + "computedX" : 68.132, + "computedY" : 154.424, + "placementX" : 68.132, + "placementY" : 154.424, + "placementWidth" : 23.698, + "placementHeight" : 23.698, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 23.698, + "contentHeight" : 23.698, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SubscriptionInvoice[0]/PaymentDetails[5]/PaymentColumns[1]/PaymentColumn_0[0]/PaymentLabel_0[1]", + "entityName" : "PaymentLabel_0", + "entityKind" : "ParagraphNode", + "parentPath" : "SubscriptionInvoice[0]/PaymentDetails[5]/PaymentColumns[1]/PaymentColumn_0[0]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 27.084, + "computedY" : 132.807, + "placementX" : 27.084, + "placementY" : 132.807, + "placementWidth" : 105.795, + "placementHeight" : 12.702, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 105.795, + "contentHeight" : 12.702, + "margin" : { + "top" : 8.915, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SubscriptionInvoice[0]/PaymentDetails[5]/PaymentColumns[1]/PaymentColumn_0[0]/PaymentValue_0[2]", + "entityName" : "PaymentValue_0", + "entityKind" : "ParagraphNode", + "parentPath" : "SubscriptionInvoice[0]/PaymentDetails[5]/PaymentColumns[1]/PaymentColumn_0[0]", + "childIndex" : 2, + "depth" : 5, + "layer" : 5, + "computedX" : 27.084, + "computedY" : 116.289, + "placementX" : 27.084, + "placementY" : 116.289, + "placementWidth" : 105.795, + "placementHeight" : 12.794, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 105.795, + "contentHeight" : 12.794, + "margin" : { + "top" : 3.724, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SubscriptionInvoice[0]/PaymentDetails[5]/PaymentColumns[1]/PaymentColumn_1[1]", + "entityName" : "PaymentColumn_1", + "entityKind" : "SectionNode", + "parentPath" : "SubscriptionInvoice[0]/PaymentDetails[5]/PaymentColumns[1]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 144.164, + "computedY" : 116.289, + "placementX" : 144.164, + "placementY" : 116.289, + "placementWidth" : 100.435, + "placementHeight" : 61.833, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 100.435, + "contentHeight" : 61.833, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SubscriptionInvoice[0]/PaymentDetails[5]/PaymentColumns[1]/PaymentColumn_1[1]/Align[0]", + "entityName" : null, + "entityKind" : "Align", + "parentPath" : "SubscriptionInvoice[0]/PaymentDetails[5]/PaymentColumns[1]/PaymentColumn_1[1]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 144.164, + "computedY" : 154.424, + "placementX" : 144.164, + "placementY" : 154.424, + "placementWidth" : 100.435, + "placementHeight" : 23.698, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 100.435, + "contentHeight" : 23.698, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SubscriptionInvoice[0]/PaymentDetails[5]/PaymentColumns[1]/PaymentColumn_1[1]/Align[0]/SvgIcon[0]", + "entityName" : "SvgIcon", + "entityKind" : "LayerStackNode", + "parentPath" : "SubscriptionInvoice[0]/PaymentDetails[5]/PaymentColumns[1]/PaymentColumn_1[1]/Align[0]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 182.532, + "computedY" : 154.424, + "placementX" : 182.532, + "placementY" : 154.424, + "placementWidth" : 23.698, + "placementHeight" : 23.698, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 23.698, + "contentHeight" : 23.698, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SubscriptionInvoice[0]/PaymentDetails[5]/PaymentColumns[1]/PaymentColumn_1[1]/Align[0]/SvgIcon[0]/SvgLayer0[0]", + "entityName" : "SvgLayer0", + "entityKind" : "Path", + "parentPath" : "SubscriptionInvoice[0]/PaymentDetails[5]/PaymentColumns[1]/PaymentColumn_1[1]/Align[0]/SvgIcon[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 182.532, + "computedY" : 154.424, + "placementX" : 182.532, + "placementY" : 154.424, + "placementWidth" : 23.698, + "placementHeight" : 23.698, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 23.698, + "contentHeight" : 23.698, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SubscriptionInvoice[0]/PaymentDetails[5]/PaymentColumns[1]/PaymentColumn_1[1]/PaymentLabel_1[1]", + "entityName" : "PaymentLabel_1", + "entityKind" : "ParagraphNode", + "parentPath" : "SubscriptionInvoice[0]/PaymentDetails[5]/PaymentColumns[1]/PaymentColumn_1[1]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 144.164, + "computedY" : 132.807, + "placementX" : 144.164, + "placementY" : 132.807, + "placementWidth" : 100.435, + "placementHeight" : 12.702, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 100.435, + "contentHeight" : 12.702, + "margin" : { + "top" : 8.915, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SubscriptionInvoice[0]/PaymentDetails[5]/PaymentColumns[1]/PaymentColumn_1[1]/PaymentValue_1[2]", + "entityName" : "PaymentValue_1", + "entityKind" : "ParagraphNode", + "parentPath" : "SubscriptionInvoice[0]/PaymentDetails[5]/PaymentColumns[1]/PaymentColumn_1[1]", + "childIndex" : 2, + "depth" : 5, + "layer" : 5, + "computedX" : 144.164, + "computedY" : 116.289, + "placementX" : 144.164, + "placementY" : 116.289, + "placementWidth" : 100.435, + "placementHeight" : 12.794, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 100.435, + "contentHeight" : 12.794, + "margin" : { + "top" : 3.724, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SubscriptionInvoice[0]/PaymentDetails[5]/PaymentColumns[1]/PaymentColumn_2[2]", + "entityName" : "PaymentColumn_2", + "entityKind" : "SectionNode", + "parentPath" : "SubscriptionInvoice[0]/PaymentDetails[5]/PaymentColumns[1]", + "childIndex" : 2, + "depth" : 4, + "layer" : 4, + "computedX" : 244.599, + "computedY" : 116.289, + "placementX" : 244.599, + "placementY" : 116.289, + "placementWidth" : 103.821, + "placementHeight" : 61.833, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 103.821, + "contentHeight" : 61.833, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SubscriptionInvoice[0]/PaymentDetails[5]/PaymentColumns[1]/PaymentColumn_2[2]/Align[0]", + "entityName" : null, + "entityKind" : "Align", + "parentPath" : "SubscriptionInvoice[0]/PaymentDetails[5]/PaymentColumns[1]/PaymentColumn_2[2]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 244.599, + "computedY" : 154.424, + "placementX" : 244.599, + "placementY" : 154.424, + "placementWidth" : 103.821, + "placementHeight" : 23.698, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 103.821, + "contentHeight" : 23.698, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SubscriptionInvoice[0]/PaymentDetails[5]/PaymentColumns[1]/PaymentColumn_2[2]/Align[0]/SvgIcon[0]", + "entityName" : "SvgIcon", + "entityKind" : "LayerStackNode", + "parentPath" : "SubscriptionInvoice[0]/PaymentDetails[5]/PaymentColumns[1]/PaymentColumn_2[2]/Align[0]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 284.66, + "computedY" : 154.424, + "placementX" : 284.66, + "placementY" : 154.424, + "placementWidth" : 23.698, + "placementHeight" : 23.698, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 23.698, + "contentHeight" : 23.698, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SubscriptionInvoice[0]/PaymentDetails[5]/PaymentColumns[1]/PaymentColumn_2[2]/Align[0]/SvgIcon[0]/SvgLayer0[0]", + "entityName" : "SvgLayer0", + "entityKind" : "Path", + "parentPath" : "SubscriptionInvoice[0]/PaymentDetails[5]/PaymentColumns[1]/PaymentColumn_2[2]/Align[0]/SvgIcon[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 284.66, + "computedY" : 154.424, + "placementX" : 284.66, + "placementY" : 154.424, + "placementWidth" : 23.698, + "placementHeight" : 23.698, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 23.698, + "contentHeight" : 23.698, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SubscriptionInvoice[0]/PaymentDetails[5]/PaymentColumns[1]/PaymentColumn_2[2]/PaymentLabel_2[1]", + "entityName" : "PaymentLabel_2", + "entityKind" : "ParagraphNode", + "parentPath" : "SubscriptionInvoice[0]/PaymentDetails[5]/PaymentColumns[1]/PaymentColumn_2[2]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 244.599, + "computedY" : 132.807, + "placementX" : 244.599, + "placementY" : 132.807, + "placementWidth" : 103.821, + "placementHeight" : 12.702, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 103.821, + "contentHeight" : 12.702, + "margin" : { + "top" : 8.915, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SubscriptionInvoice[0]/PaymentDetails[5]/PaymentColumns[1]/PaymentColumn_2[2]/PaymentValue_2[2]", + "entityName" : "PaymentValue_2", + "entityKind" : "ParagraphNode", + "parentPath" : "SubscriptionInvoice[0]/PaymentDetails[5]/PaymentColumns[1]/PaymentColumn_2[2]", + "childIndex" : 2, + "depth" : 5, + "layer" : 5, + "computedX" : 244.599, + "computedY" : 116.289, + "placementX" : 244.599, + "placementY" : 116.289, + "placementWidth" : 103.821, + "placementHeight" : 12.794, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 103.821, + "contentHeight" : 12.794, + "margin" : { + "top" : 3.724, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SubscriptionInvoice[0]/PaymentDetails[5]/PaymentColumns[1]/PaymentColumn_3[3]", + "entityName" : "PaymentColumn_3", + "entityKind" : "SectionNode", + "parentPath" : "SubscriptionInvoice[0]/PaymentDetails[5]/PaymentColumns[1]", + "childIndex" : 3, + "depth" : 4, + "layer" : 4, + "computedX" : 348.42, + "computedY" : 116.289, + "placementX" : 348.42, + "placementY" : 116.289, + "placementWidth" : 104.385, + "placementHeight" : 61.833, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 104.385, + "contentHeight" : 61.833, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SubscriptionInvoice[0]/PaymentDetails[5]/PaymentColumns[1]/PaymentColumn_3[3]/Align[0]", + "entityName" : null, + "entityKind" : "Align", + "parentPath" : "SubscriptionInvoice[0]/PaymentDetails[5]/PaymentColumns[1]/PaymentColumn_3[3]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 348.42, + "computedY" : 154.424, + "placementX" : 348.42, + "placementY" : 154.424, + "placementWidth" : 104.385, + "placementHeight" : 23.698, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 104.385, + "contentHeight" : 23.698, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SubscriptionInvoice[0]/PaymentDetails[5]/PaymentColumns[1]/PaymentColumn_3[3]/Align[0]/SvgIcon[0]", + "entityName" : "SvgIcon", + "entityKind" : "LayerStackNode", + "parentPath" : "SubscriptionInvoice[0]/PaymentDetails[5]/PaymentColumns[1]/PaymentColumn_3[3]/Align[0]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 388.763, + "computedY" : 154.424, + "placementX" : 388.763, + "placementY" : 154.424, + "placementWidth" : 23.698, + "placementHeight" : 23.698, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 23.698, + "contentHeight" : 23.698, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SubscriptionInvoice[0]/PaymentDetails[5]/PaymentColumns[1]/PaymentColumn_3[3]/Align[0]/SvgIcon[0]/SvgLayer0[0]", + "entityName" : "SvgLayer0", + "entityKind" : "Path", + "parentPath" : "SubscriptionInvoice[0]/PaymentDetails[5]/PaymentColumns[1]/PaymentColumn_3[3]/Align[0]/SvgIcon[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 388.763, + "computedY" : 154.424, + "placementX" : 388.763, + "placementY" : 154.424, + "placementWidth" : 23.698, + "placementHeight" : 23.698, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 23.698, + "contentHeight" : 23.698, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SubscriptionInvoice[0]/PaymentDetails[5]/PaymentColumns[1]/PaymentColumn_3[3]/Align[0]/SvgIcon[0]/SvgLayer1[1]", + "entityName" : "SvgLayer1", + "entityKind" : "Path", + "parentPath" : "SubscriptionInvoice[0]/PaymentDetails[5]/PaymentColumns[1]/PaymentColumn_3[3]/Align[0]/SvgIcon[0]", + "childIndex" : 1, + "depth" : 7, + "layer" : 7, + "computedX" : 388.763, + "computedY" : 154.424, + "placementX" : 388.763, + "placementY" : 154.424, + "placementWidth" : 23.698, + "placementHeight" : 23.698, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 23.698, + "contentHeight" : 23.698, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SubscriptionInvoice[0]/PaymentDetails[5]/PaymentColumns[1]/PaymentColumn_3[3]/PaymentLabel_3[1]", + "entityName" : "PaymentLabel_3", + "entityKind" : "ParagraphNode", + "parentPath" : "SubscriptionInvoice[0]/PaymentDetails[5]/PaymentColumns[1]/PaymentColumn_3[3]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 348.42, + "computedY" : 132.807, + "placementX" : 348.42, + "placementY" : 132.807, + "placementWidth" : 104.385, + "placementHeight" : 12.702, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 104.385, + "contentHeight" : 12.702, + "margin" : { + "top" : 8.915, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SubscriptionInvoice[0]/PaymentDetails[5]/PaymentColumns[1]/PaymentColumn_3[3]/PaymentValue_3[2]", + "entityName" : "PaymentValue_3", + "entityKind" : "ParagraphNode", + "parentPath" : "SubscriptionInvoice[0]/PaymentDetails[5]/PaymentColumns[1]/PaymentColumn_3[3]", + "childIndex" : 2, + "depth" : 5, + "layer" : 5, + "computedX" : 348.42, + "computedY" : 116.289, + "placementX" : 348.42, + "placementY" : 116.289, + "placementWidth" : 104.385, + "placementHeight" : 12.794, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 104.385, + "contentHeight" : 12.794, + "margin" : { + "top" : 3.724, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SubscriptionInvoice[0]/PaymentDetails[5]/PaymentColumns[1]/PaymentColumn_4[4]", + "entityName" : "PaymentColumn_4", + "entityKind" : "SectionNode", + "parentPath" : "SubscriptionInvoice[0]/PaymentDetails[5]/PaymentColumns[1]", + "childIndex" : 4, + "depth" : 4, + "layer" : 4, + "computedX" : 452.804, + "computedY" : 116.289, + "placementX" : 452.804, + "placementY" : 116.289, + "placementWidth" : 115.388, + "placementHeight" : 61.833, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 115.388, + "contentHeight" : 61.833, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 11.285, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SubscriptionInvoice[0]/PaymentDetails[5]/PaymentColumns[1]/PaymentColumn_4[4]/Align[0]", + "entityName" : null, + "entityKind" : "Align", + "parentPath" : "SubscriptionInvoice[0]/PaymentDetails[5]/PaymentColumns[1]/PaymentColumn_4[4]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 452.804, + "computedY" : 154.424, + "placementX" : 452.804, + "placementY" : 154.424, + "placementWidth" : 104.103, + "placementHeight" : 23.698, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 104.103, + "contentHeight" : 23.698, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SubscriptionInvoice[0]/PaymentDetails[5]/PaymentColumns[1]/PaymentColumn_4[4]/Align[0]/SvgIcon[0]", + "entityName" : "SvgIcon", + "entityKind" : "LayerStackNode", + "parentPath" : "SubscriptionInvoice[0]/PaymentDetails[5]/PaymentColumns[1]/PaymentColumn_4[4]/Align[0]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 493.007, + "computedY" : 154.424, + "placementX" : 493.007, + "placementY" : 154.424, + "placementWidth" : 23.698, + "placementHeight" : 23.698, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 23.698, + "contentHeight" : 23.698, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SubscriptionInvoice[0]/PaymentDetails[5]/PaymentColumns[1]/PaymentColumn_4[4]/Align[0]/SvgIcon[0]/SvgLayer0[0]", + "entityName" : "SvgLayer0", + "entityKind" : "Path", + "parentPath" : "SubscriptionInvoice[0]/PaymentDetails[5]/PaymentColumns[1]/PaymentColumn_4[4]/Align[0]/SvgIcon[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 493.007, + "computedY" : 154.424, + "placementX" : 493.007, + "placementY" : 154.424, + "placementWidth" : 23.698, + "placementHeight" : 23.698, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 23.698, + "contentHeight" : 23.698, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SubscriptionInvoice[0]/PaymentDetails[5]/PaymentColumns[1]/PaymentColumn_4[4]/Align[0]/SvgIcon[0]/SvgLayer1[1]", + "entityName" : "SvgLayer1", + "entityKind" : "Path", + "parentPath" : "SubscriptionInvoice[0]/PaymentDetails[5]/PaymentColumns[1]/PaymentColumn_4[4]/Align[0]/SvgIcon[0]", + "childIndex" : 1, + "depth" : 7, + "layer" : 7, + "computedX" : 493.007, + "computedY" : 154.424, + "placementX" : 493.007, + "placementY" : 154.424, + "placementWidth" : 23.698, + "placementHeight" : 23.698, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 23.698, + "contentHeight" : 23.698, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SubscriptionInvoice[0]/PaymentDetails[5]/PaymentColumns[1]/PaymentColumn_4[4]/PaymentLabel_4[1]", + "entityName" : "PaymentLabel_4", + "entityKind" : "ParagraphNode", + "parentPath" : "SubscriptionInvoice[0]/PaymentDetails[5]/PaymentColumns[1]/PaymentColumn_4[4]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 452.804, + "computedY" : 132.807, + "placementX" : 452.804, + "placementY" : 132.807, + "placementWidth" : 104.103, + "placementHeight" : 12.702, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 104.103, + "contentHeight" : 12.702, + "margin" : { + "top" : 8.915, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SubscriptionInvoice[0]/PaymentDetails[5]/PaymentColumns[1]/PaymentColumn_4[4]/PaymentValue_4[2]", + "entityName" : "PaymentValue_4", + "entityKind" : "ParagraphNode", + "parentPath" : "SubscriptionInvoice[0]/PaymentDetails[5]/PaymentColumns[1]/PaymentColumn_4[4]", + "childIndex" : 2, + "depth" : 5, + "layer" : 5, + "computedX" : 452.804, + "computedY" : 116.289, + "placementX" : 452.804, + "placementY" : 116.289, + "placementWidth" : 104.103, + "placementHeight" : 12.794, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 104.103, + "contentHeight" : 12.794, + "margin" : { + "top" : 3.724, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SubscriptionInvoice[0]/PaymentNote[6]", + "entityName" : "PaymentNote", + "entityKind" : "SectionNode", + "parentPath" : "SubscriptionInvoice[0]", + "childIndex" : 6, + "depth" : 2, + "layer" : 2, + "computedX" : 0.0, + "computedY" : 91.674, + "placementX" : 0.0, + "placementY" : 91.674, + "placementWidth" : 435.354, + "placementHeight" : 12.597, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 435.354, + "contentHeight" : 12.597, + "margin" : { + "top" : 12.018, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 27.084, + "bottom" : 0.0, + "left" : 27.084 + } + }, { + "path" : "SubscriptionInvoice[0]/PaymentNote[6]/PaymentNote[0]", + "entityName" : "PaymentNote", + "entityKind" : "ParagraphNode", + "parentPath" : "SubscriptionInvoice[0]/PaymentNote[6]", + "childIndex" : 0, + "depth" : 3, + "layer" : 3, + "computedX" : 27.084, + "computedY" : 91.674, + "placementX" : 27.084, + "placementY" : 91.674, + "placementWidth" : 381.186, + "placementHeight" : 12.597, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 381.186, + "contentHeight" : 12.597, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SubscriptionInvoice[0]/BrandStrip[7]", + "entityName" : "BrandStrip", + "entityKind" : "RowNode", + "parentPath" : "SubscriptionInvoice[0]", + "childIndex" : 7, + "depth" : 2, + "layer" : 2, + "computedX" : 0.0, + "computedY" : 70.176, + "placementX" : 0.0, + "placementY" : 70.176, + "placementWidth" : 595.276, + "placementHeight" : 3.385, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 595.276, + "contentHeight" : 3.385, + "margin" : { + "top" : 18.112, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SubscriptionInvoice[0]/BrandStrip[7]/BrandStripSegment_0[0]", + "entityName" : "BrandStripSegment_0", + "entityKind" : "ShapeNode", + "parentPath" : "SubscriptionInvoice[0]/BrandStrip[7]", + "childIndex" : 0, + "depth" : 3, + "layer" : 3, + "computedX" : 0.0, + "computedY" : 70.176, + "placementX" : 0.0, + "placementY" : 70.176, + "placementWidth" : 153.474, + "placementHeight" : 3.385, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 153.474, + "contentHeight" : 3.385, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SubscriptionInvoice[0]/BrandStrip[7]/BrandStripSegment_1[1]", + "entityName" : "BrandStripSegment_1", + "entityKind" : "ShapeNode", + "parentPath" : "SubscriptionInvoice[0]/BrandStrip[7]", + "childIndex" : 1, + "depth" : 3, + "layer" : 3, + "computedX" : 153.474, + "computedY" : 70.176, + "placementX" : 153.474, + "placementY" : 70.176, + "placementWidth" : 143.882, + "placementHeight" : 3.385, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 143.882, + "contentHeight" : 3.385, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SubscriptionInvoice[0]/BrandStrip[7]/BrandStripSegment_2[2]", + "entityName" : "BrandStripSegment_2", + "entityKind" : "ShapeNode", + "parentPath" : "SubscriptionInvoice[0]/BrandStrip[7]", + "childIndex" : 2, + "depth" : 3, + "layer" : 3, + "computedX" : 297.356, + "computedY" : 70.176, + "placementX" : 297.356, + "placementY" : 70.176, + "placementWidth" : 142.189, + "placementHeight" : 3.385, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 142.189, + "contentHeight" : 3.385, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SubscriptionInvoice[0]/BrandStrip[7]/BrandStripSegment_3[3]", + "entityName" : "BrandStripSegment_3", + "entityKind" : "ShapeNode", + "parentPath" : "SubscriptionInvoice[0]/BrandStrip[7]", + "childIndex" : 3, + "depth" : 3, + "layer" : 3, + "computedX" : 439.545, + "computedY" : 70.176, + "placementX" : 439.545, + "placementY" : 70.176, + "placementWidth" : 155.731, + "placementHeight" : 3.385, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 155.731, + "contentHeight" : 3.385, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SubscriptionInvoice[0]/ClosingBand[8]", + "entityName" : "ClosingBand", + "entityKind" : "SectionNode", + "parentPath" : "SubscriptionInvoice[0]", + "childIndex" : 8, + "depth" : 2, + "layer" : 2, + "computedX" : 0.0, + "computedY" : 15.283, + "placementX" : 0.0, + "placementY" : 15.283, + "placementWidth" : 595.276, + "placementHeight" : 54.329, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 595.276, + "contentHeight" : 54.329, + "margin" : { + "top" : 0.564, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 13.993, + "right" : 27.084, + "bottom" : 1.128, + "left" : 23.698 + } + }, { + "path" : "SubscriptionInvoice[0]/ClosingBand[8]/ClosingLockup[0]", + "entityName" : "ClosingLockup", + "entityKind" : "RowNode", + "parentPath" : "SubscriptionInvoice[0]/ClosingBand[8]", + "childIndex" : 0, + "depth" : 3, + "layer" : 3, + "computedX" : 23.698, + "computedY" : 16.411, + "placementX" : 23.698, + "placementY" : 16.411, + "placementWidth" : 544.494, + "placementHeight" : 39.208, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 544.494, + "contentHeight" : 39.208, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SubscriptionInvoice[0]/ClosingBand[8]/ClosingLockup[0]/ClosingMark[0]", + "entityName" : "ClosingMark", + "entityKind" : "SectionNode", + "parentPath" : "SubscriptionInvoice[0]/ClosingBand[8]/ClosingLockup[0]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 23.698, + "computedY" : 21.627, + "placementX" : 23.698, + "placementY" : 21.627, + "placementWidth" : 44.575, + "placementHeight" : 31.598, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 44.575, + "contentHeight" : 31.598, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 2.821, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SubscriptionInvoice[0]/ClosingBand[8]/ClosingLockup[0]/ClosingMark[0]/Align[0]", + "entityName" : null, + "entityKind" : "Align", + "parentPath" : "SubscriptionInvoice[0]/ClosingBand[8]/ClosingLockup[0]/ClosingMark[0]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 23.698, + "computedY" : 21.627, + "placementX" : 23.698, + "placementY" : 21.627, + "placementWidth" : 44.575, + "placementHeight" : 31.598, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 44.575, + "contentHeight" : 31.598, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SubscriptionInvoice[0]/ClosingBand[8]/ClosingLockup[0]/ClosingMark[0]/Align[0]/SvgIcon[0]", + "entityName" : "SvgIcon", + "entityKind" : "LayerStackNode", + "parentPath" : "SubscriptionInvoice[0]/ClosingBand[8]/ClosingLockup[0]/ClosingMark[0]/Align[0]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 23.698, + "computedY" : 21.627, + "placementX" : 23.698, + "placementY" : 21.627, + "placementWidth" : 31.598, + "placementHeight" : 31.598, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 31.598, + "contentHeight" : 31.598, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SubscriptionInvoice[0]/ClosingBand[8]/ClosingLockup[0]/ClosingMark[0]/Align[0]/SvgIcon[0]/SvgLayer0[0]", + "entityName" : "SvgLayer0", + "entityKind" : "Path", + "parentPath" : "SubscriptionInvoice[0]/ClosingBand[8]/ClosingLockup[0]/ClosingMark[0]/Align[0]/SvgIcon[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 23.698, + "computedY" : 21.627, + "placementX" : 23.698, + "placementY" : 21.627, + "placementWidth" : 31.598, + "placementHeight" : 31.598, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 31.598, + "contentHeight" : 31.598, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SubscriptionInvoice[0]/ClosingBand[8]/ClosingLockup[0]/ClosingMark[0]/Align[0]/SvgIcon[0]/SvgLayer1[1]", + "entityName" : "SvgLayer1", + "entityKind" : "Path", + "parentPath" : "SubscriptionInvoice[0]/ClosingBand[8]/ClosingLockup[0]/ClosingMark[0]/Align[0]/SvgIcon[0]", + "childIndex" : 1, + "depth" : 7, + "layer" : 7, + "computedX" : 23.698, + "computedY" : 21.627, + "placementX" : 23.698, + "placementY" : 21.627, + "placementWidth" : 31.598, + "placementHeight" : 31.598, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 31.598, + "contentHeight" : 31.598, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SubscriptionInvoice[0]/ClosingBand[8]/ClosingLockup[0]/ClosingMessage[1]", + "entityName" : "ClosingMessage", + "entityKind" : "SectionNode", + "parentPath" : "SubscriptionInvoice[0]/ClosingBand[8]/ClosingLockup[0]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 68.273, + "computedY" : 16.411, + "placementX" : 68.273, + "placementY" : 16.411, + "placementWidth" : 415.505, + "placementHeight" : 39.208, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 415.505, + "contentHeight" : 39.208, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 3.385 + } + }, { + "path" : "SubscriptionInvoice[0]/ClosingBand[8]/ClosingLockup[0]/ClosingMessage[1]/ClosingHeadline[0]", + "entityName" : "ClosingHeadline", + "entityKind" : "ParagraphNode", + "parentPath" : "SubscriptionInvoice[0]/ClosingBand[8]/ClosingLockup[0]/ClosingMessage[1]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 71.659, + "computedY" : 43.406, + "placementX" : 71.659, + "placementY" : 43.406, + "placementWidth" : 121.357, + "placementHeight" : 12.213, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 121.357, + "contentHeight" : 12.213, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SubscriptionInvoice[0]/ClosingBand[8]/ClosingLockup[0]/ClosingMessage[1]/ClosingLine_0[1]", + "entityName" : "ClosingLine_0", + "entityKind" : "ParagraphNode", + "parentPath" : "SubscriptionInvoice[0]/ClosingBand[8]/ClosingLockup[0]/ClosingMessage[1]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 71.659, + "computedY" : 29.953, + "placementX" : 71.659, + "placementY" : 29.953, + "placementWidth" : 412.12, + "placementHeight" : 11.81, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 412.12, + "contentHeight" : 11.81, + "margin" : { + "top" : 1.643, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "SubscriptionInvoice[0]/ClosingBand[8]/ClosingLockup[0]/ClosingMessage[1]/ClosingLine_1[2]", + "entityName" : "ClosingLine_1", + "entityKind" : "ParagraphNode", + "parentPath" : "SubscriptionInvoice[0]/ClosingBand[8]/ClosingLockup[0]/ClosingMessage[1]", + "childIndex" : 2, + "depth" : 5, + "layer" : 5, + "computedX" : 71.659, + "computedY" : 16.411, + "placementX" : 71.659, + "placementY" : 16.411, + "placementWidth" : 235.545, + "placementHeight" : 11.81, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 235.545, + "contentHeight" : 11.81, + "margin" : { + "top" : 1.732, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + } ] +} diff --git a/qa/src/test/resources/layout-snapshots/canonical-templates/invoice/workspace_invoice_layout.json b/qa/src/test/resources/layout-snapshots/canonical-templates/invoice/workspace_invoice_layout.json new file mode 100644 index 000000000..848c437e6 --- /dev/null +++ b/qa/src/test/resources/layout-snapshots/canonical-templates/invoice/workspace_invoice_layout.json @@ -0,0 +1,6197 @@ +{ + "formatVersion" : "2.0", + "canvas" : { + "pageWidth" : 595.276, + "pageHeight" : 841.89, + "innerWidth" : 543.365, + "innerHeight" : 800.136, + "margin" : { + "top" : 25.955, + "right" : 26.519, + "bottom" : 15.799, + "left" : 25.391 + } + }, + "totalPages" : 1, + "nodes" : [ { + "path" : "WorkspaceInvoice[0]", + "entityName" : "WorkspaceInvoice", + "entityKind" : "ContainerNode", + "parentPath" : null, + "childIndex" : 0, + "depth" : 1, + "layer" : 1, + "computedX" : 25.391, + "computedY" : 18.885, + "placementX" : 25.391, + "placementY" : 18.885, + "placementWidth" : 543.365, + "placementHeight" : 797.05, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 543.365, + "contentHeight" : 797.05, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/Masthead[0]", + "entityName" : "Masthead", + "entityKind" : "RowNode", + "parentPath" : "WorkspaceInvoice[0]", + "childIndex" : 0, + "depth" : 2, + "layer" : 2, + "computedX" : 25.391, + "computedY" : 776.74, + "placementX" : 25.391, + "placementY" : 776.74, + "placementWidth" : 543.365, + "placementHeight" : 39.195, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 543.365, + "contentHeight" : 39.195, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/Masthead[0]/BrandLockup[0]", + "entityName" : "BrandLockup", + "entityKind" : "SectionNode", + "parentPath" : "WorkspaceInvoice[0]/Masthead[0]", + "childIndex" : 0, + "depth" : 3, + "layer" : 3, + "computedX" : 25.391, + "computedY" : 792.782, + "placementX" : 25.391, + "placementY" : 792.782, + "placementWidth" : 58.595, + "placementHeight" : 23.152, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 58.595, + "contentHeight" : 23.152, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/Masthead[0]/BrandLockup[0]/BrandWordmark[0]", + "entityName" : "BrandWordmark", + "entityKind" : "ParagraphNode", + "parentPath" : "WorkspaceInvoice[0]/Masthead[0]/BrandLockup[0]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 25.391, + "computedY" : 792.782, + "placementX" : 25.391, + "placementY" : 792.782, + "placementWidth" : 58.595, + "placementHeight" : 23.152, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 58.595, + "contentHeight" : 23.152, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/Masthead[0]/InvoiceTitle[1]", + "entityName" : "InvoiceTitle", + "entityKind" : "SectionNode", + "parentPath" : "WorkspaceInvoice[0]/Masthead[0]", + "childIndex" : 1, + "depth" : 3, + "layer" : 3, + "computedX" : 145.01, + "computedY" : 776.74, + "placementX" : 145.01, + "placementY" : 776.74, + "placementWidth" : 423.746, + "placementHeight" : 39.195, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 423.746, + "contentHeight" : 39.195, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : -4.119, + "right" : 7.335, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/Masthead[0]/InvoiceTitle[1]/InvoiceTitleText[0]", + "entityName" : "InvoiceTitleText", + "entityKind" : "ParagraphNode", + "parentPath" : "WorkspaceInvoice[0]/Masthead[0]/InvoiceTitle[1]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 145.01, + "computedY" : 776.74, + "placementX" : 145.01, + "placementY" : 776.74, + "placementWidth" : 416.411, + "placementHeight" : 43.314, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 416.411, + "contentHeight" : 43.314, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/BrandAccentRule[1]", + "entityName" : "BrandAccentRule", + "entityKind" : "LineNode", + "parentPath" : "WorkspaceInvoice[0]", + "childIndex" : 1, + "depth" : 2, + "layer" : 2, + "computedX" : 25.391, + "computedY" : 763.46, + "placementX" : 25.391, + "placementY" : 763.46, + "placementWidth" : 23.134, + "placementHeight" : 2.257, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 23.134, + "contentHeight" : 2.257, + "margin" : { + "top" : 11.023, + "right" : 0.0, + "bottom" : 8.592, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/IssuerMetaRow[2]", + "entityName" : "IssuerMetaRow", + "entityKind" : "RowNode", + "parentPath" : "WorkspaceInvoice[0]", + "childIndex" : 2, + "depth" : 2, + "layer" : 2, + "computedX" : 25.391, + "computedY" : 641.4, + "placementX" : 25.391, + "placementY" : 641.4, + "placementWidth" : 543.365, + "placementHeight" : 113.468, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 543.365, + "contentHeight" : 113.468, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/IssuerMetaRow[2]/IssuerAddress[0]", + "entityName" : "IssuerAddress", + "entityKind" : "SectionNode", + "parentPath" : "WorkspaceInvoice[0]/IssuerMetaRow[2]", + "childIndex" : 0, + "depth" : 3, + "layer" : 3, + "computedX" : 25.391, + "computedY" : 662.841, + "placementX" : 25.391, + "placementY" : 662.841, + "placementWidth" : 271.683, + "placementHeight" : 92.027, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 271.683, + "contentHeight" : 92.027, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 6.771, + "right" : 0.0, + "bottom" : 0.0, + "left" : 1.128 + } + }, { + "path" : "WorkspaceInvoice[0]/IssuerMetaRow[2]/IssuerAddress[0]/IssuerName[0]", + "entityName" : "IssuerName", + "entityKind" : "ParagraphNode", + "parentPath" : "WorkspaceInvoice[0]/IssuerMetaRow[2]/IssuerAddress[0]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 26.519, + "computedY" : 734.805, + "placementX" : 26.519, + "placementY" : 734.805, + "placementWidth" : 127.608, + "placementHeight" : 13.291, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 127.608, + "contentHeight" : 13.291, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/IssuerMetaRow[2]/IssuerAddress[0]/IssuerAddressLines[1]", + "entityName" : "IssuerAddressLines", + "entityKind" : "SectionNode", + "parentPath" : "WorkspaceInvoice[0]/IssuerMetaRow[2]/IssuerAddress[0]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 26.519, + "computedY" : 684.282, + "placementX" : 26.519, + "placementY" : 684.282, + "placementWidth" : 122.916, + "placementHeight" : 42.373, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 122.916, + "contentHeight" : 42.373, + "margin" : { + "top" : 8.15, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/IssuerMetaRow[2]/IssuerAddress[0]/IssuerAddressLines[1]/IssuerAddressLine1[0]", + "entityName" : "IssuerAddressLine1", + "entityKind" : "ParagraphNode", + "parentPath" : "WorkspaceInvoice[0]/IssuerMetaRow[2]/IssuerAddress[0]/IssuerAddressLines[1]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 26.519, + "computedY" : 715.316, + "placementX" : 26.519, + "placementY" : 715.316, + "placementWidth" : 122.916, + "placementHeight" : 11.34, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 122.916, + "contentHeight" : 11.34, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/IssuerMetaRow[2]/IssuerAddress[0]/IssuerAddressLines[1]/IssuerAddressLine2[1]", + "entityName" : "IssuerAddressLine2", + "entityKind" : "ParagraphNode", + "parentPath" : "WorkspaceInvoice[0]/IssuerMetaRow[2]/IssuerAddress[0]/IssuerAddressLines[1]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 26.519, + "computedY" : 699.799, + "placementX" : 26.519, + "placementY" : 699.799, + "placementWidth" : 104.129, + "placementHeight" : 11.34, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 104.129, + "contentHeight" : 11.34, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/IssuerMetaRow[2]/IssuerAddress[0]/IssuerAddressLines[1]/IssuerAddressLine3[2]", + "entityName" : "IssuerAddressLine3", + "entityKind" : "ParagraphNode", + "parentPath" : "WorkspaceInvoice[0]/IssuerMetaRow[2]/IssuerAddress[0]/IssuerAddressLines[1]", + "childIndex" : 2, + "depth" : 5, + "layer" : 5, + "computedX" : 26.519, + "computedY" : 684.282, + "placementX" : 26.519, + "placementY" : 684.282, + "placementWidth" : 56.284, + "placementHeight" : 11.34, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 56.284, + "contentHeight" : 11.34, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/IssuerMetaRow[2]/IssuerAddress[0]/IssuerTaxLineLayer[2]", + "entityName" : "IssuerTaxLineLayer", + "entityKind" : "LayerStackNode", + "parentPath" : "WorkspaceInvoice[0]/IssuerMetaRow[2]/IssuerAddress[0]", + "childIndex" : 2, + "depth" : 4, + "layer" : 4, + "computedX" : 26.519, + "computedY" : 662.841, + "placementX" : 26.519, + "placementY" : 662.841, + "placementWidth" : 270.554, + "placementHeight" : 21.441, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 270.554, + "contentHeight" : 21.441, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/IssuerMetaRow[2]/IssuerAddress[0]/IssuerTaxLineLayer[2]/IssuerTaxLineHolder[0]", + "entityName" : "IssuerTaxLineHolder", + "entityKind" : "SectionNode", + "parentPath" : "WorkspaceInvoice[0]/IssuerMetaRow[2]/IssuerAddress[0]/IssuerTaxLineLayer[2]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 26.519, + "computedY" : 662.841, + "placementX" : 26.519, + "placementY" : 662.841, + "placementWidth" : 270.554, + "placementHeight" : 21.441, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 270.554, + "contentHeight" : 21.441, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/IssuerMetaRow[2]/IssuerAddress[0]/IssuerTaxLineLayer[2]/IssuerTaxLineHolder[0]/IssuerTaxLine[0]", + "entityName" : "IssuerTaxLine", + "entityKind" : "RowNode", + "parentPath" : "WorkspaceInvoice[0]/IssuerMetaRow[2]/IssuerAddress[0]/IssuerTaxLineLayer[2]/IssuerTaxLineHolder[0]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 26.519, + "computedY" : 662.841, + "placementX" : 26.519, + "placementY" : 662.841, + "placementWidth" : 270.554, + "placementHeight" : 11.34, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 270.554, + "contentHeight" : 11.34, + "margin" : { + "top" : 10.101, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/IssuerMetaRow[2]/IssuerAddress[0]/IssuerTaxLineLayer[2]/IssuerTaxLineHolder[0]/IssuerTaxLine[0]/IssuerTaxLabel[0]", + "entityName" : "IssuerTaxLabel", + "entityKind" : "ParagraphNode", + "parentPath" : "WorkspaceInvoice[0]/IssuerMetaRow[2]/IssuerAddress[0]/IssuerTaxLineLayer[2]/IssuerTaxLineHolder[0]/IssuerTaxLine[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 26.519, + "computedY" : 662.841, + "placementX" : 26.519, + "placementY" : 662.841, + "placementWidth" : 32.999, + "placementHeight" : 11.34, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 32.999, + "contentHeight" : 11.34, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/IssuerMetaRow[2]/IssuerAddress[0]/IssuerTaxLineLayer[2]/IssuerTaxLineHolder[0]/IssuerTaxLine[0]/IssuerTaxValue[1]", + "entityName" : "IssuerTaxValue", + "entityKind" : "ParagraphNode", + "parentPath" : "WorkspaceInvoice[0]/IssuerMetaRow[2]/IssuerAddress[0]/IssuerTaxLineLayer[2]/IssuerTaxLineHolder[0]/IssuerTaxLine[0]", + "childIndex" : 1, + "depth" : 7, + "layer" : 7, + "computedX" : 62.067, + "computedY" : 662.841, + "placementX" : 62.067, + "placementY" : 662.841, + "placementWidth" : 67.35, + "placementHeight" : 11.34, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 67.35, + "contentHeight" : 11.34, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/IssuerMetaRow[2]/InvoiceMeta[1]", + "entityName" : "InvoiceMeta", + "entityKind" : "SectionNode", + "parentPath" : "WorkspaceInvoice[0]/IssuerMetaRow[2]", + "childIndex" : 1, + "depth" : 3, + "layer" : 3, + "computedX" : 297.074, + "computedY" : 641.4, + "placementX" : 297.074, + "placementY" : 641.4, + "placementWidth" : 271.683, + "placementHeight" : 113.468, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 271.683, + "contentHeight" : 113.468, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 45.139 + } + }, { + "path" : "WorkspaceInvoice[0]/IssuerMetaRow[2]/InvoiceMeta[1]/InvoiceMetaRows[0]", + "entityName" : "InvoiceMetaRows", + "entityKind" : "SectionNode", + "parentPath" : "WorkspaceInvoice[0]/IssuerMetaRow[2]/InvoiceMeta[1]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 342.213, + "computedY" : 641.4, + "placementX" : 342.213, + "placementY" : 641.4, + "placementWidth" : 226.543, + "placementHeight" : 113.468, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 226.543, + "contentHeight" : 113.468, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/IssuerMetaRow[2]/InvoiceMeta[1]/InvoiceMetaRows[0]/MetaRow1Layer[0]", + "entityName" : "MetaRow1Layer", + "entityKind" : "LayerStackNode", + "parentPath" : "WorkspaceInvoice[0]/IssuerMetaRow[2]/InvoiceMeta[1]/InvoiceMetaRows[0]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 342.213, + "computedY" : 743.528, + "placementX" : 342.213, + "placementY" : 743.528, + "placementWidth" : 226.543, + "placementHeight" : 11.34, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 226.543, + "contentHeight" : 11.34, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/IssuerMetaRow[2]/InvoiceMeta[1]/InvoiceMetaRows[0]/MetaRow1Layer[0]/MetaRow1Holder[0]", + "entityName" : "MetaRow1Holder", + "entityKind" : "SectionNode", + "parentPath" : "WorkspaceInvoice[0]/IssuerMetaRow[2]/InvoiceMeta[1]/InvoiceMetaRows[0]/MetaRow1Layer[0]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 342.213, + "computedY" : 743.528, + "placementX" : 342.213, + "placementY" : 743.528, + "placementWidth" : 226.543, + "placementHeight" : 11.34, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 226.543, + "contentHeight" : 11.34, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/IssuerMetaRow[2]/InvoiceMeta[1]/InvoiceMetaRows[0]/MetaRow1Layer[0]/MetaRow1Holder[0]/MetaRow1[0]", + "entityName" : "MetaRow1", + "entityKind" : "RowNode", + "parentPath" : "WorkspaceInvoice[0]/IssuerMetaRow[2]/InvoiceMeta[1]/InvoiceMetaRows[0]/MetaRow1Layer[0]/MetaRow1Holder[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 342.213, + "computedY" : 743.528, + "placementX" : 342.213, + "placementY" : 743.528, + "placementWidth" : 226.543, + "placementHeight" : 11.34, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 226.543, + "contentHeight" : 11.34, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/IssuerMetaRow[2]/InvoiceMeta[1]/InvoiceMetaRows[0]/MetaRow1Layer[0]/MetaRow1Holder[0]/MetaRow1[0]/MetaRow1Label[0]", + "entityName" : "MetaRow1Label", + "entityKind" : "ParagraphNode", + "parentPath" : "WorkspaceInvoice[0]/IssuerMetaRow[2]/InvoiceMeta[1]/InvoiceMetaRows[0]/MetaRow1Layer[0]/MetaRow1Holder[0]/MetaRow1[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 342.213, + "computedY" : 744.0, + "placementX" : 342.213, + "placementY" : 744.0, + "placementWidth" : 65.839, + "placementHeight" : 10.867, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 65.839, + "contentHeight" : 10.867, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/IssuerMetaRow[2]/InvoiceMeta[1]/InvoiceMetaRows[0]/MetaRow1Layer[0]/MetaRow1Holder[0]/MetaRow1[0]/MetaRow1Value[1]", + "entityName" : "MetaRow1Value", + "entityKind" : "ParagraphNode", + "parentPath" : "WorkspaceInvoice[0]/IssuerMetaRow[2]/InvoiceMeta[1]/InvoiceMetaRows[0]/MetaRow1Layer[0]/MetaRow1Holder[0]/MetaRow1[0]", + "childIndex" : 1, + "depth" : 8, + "layer" : 8, + "computedX" : 443.777, + "computedY" : 743.528, + "placementX" : 443.777, + "placementY" : 743.528, + "placementWidth" : 84.001, + "placementHeight" : 11.34, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 84.001, + "contentHeight" : 11.34, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/IssuerMetaRow[2]/InvoiceMeta[1]/InvoiceMetaRows[0]/MetaRow2Layer[1]", + "entityName" : "MetaRow2Layer", + "entityKind" : "LayerStackNode", + "parentPath" : "WorkspaceInvoice[0]/IssuerMetaRow[2]/InvoiceMeta[1]/InvoiceMetaRows[0]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 342.213, + "computedY" : 723.102, + "placementX" : 342.213, + "placementY" : 723.102, + "placementWidth" : 226.543, + "placementHeight" : 11.34, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 226.543, + "contentHeight" : 11.34, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/IssuerMetaRow[2]/InvoiceMeta[1]/InvoiceMetaRows[0]/MetaRow2Layer[1]/MetaRow2Holder[0]", + "entityName" : "MetaRow2Holder", + "entityKind" : "SectionNode", + "parentPath" : "WorkspaceInvoice[0]/IssuerMetaRow[2]/InvoiceMeta[1]/InvoiceMetaRows[0]/MetaRow2Layer[1]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 342.213, + "computedY" : 723.102, + "placementX" : 342.213, + "placementY" : 723.102, + "placementWidth" : 226.543, + "placementHeight" : 11.34, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 226.543, + "contentHeight" : 11.34, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/IssuerMetaRow[2]/InvoiceMeta[1]/InvoiceMetaRows[0]/MetaRow2Layer[1]/MetaRow2Holder[0]/MetaRow2[0]", + "entityName" : "MetaRow2", + "entityKind" : "RowNode", + "parentPath" : "WorkspaceInvoice[0]/IssuerMetaRow[2]/InvoiceMeta[1]/InvoiceMetaRows[0]/MetaRow2Layer[1]/MetaRow2Holder[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 342.213, + "computedY" : 723.102, + "placementX" : 342.213, + "placementY" : 723.102, + "placementWidth" : 226.543, + "placementHeight" : 11.34, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 226.543, + "contentHeight" : 11.34, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/IssuerMetaRow[2]/InvoiceMeta[1]/InvoiceMetaRows[0]/MetaRow2Layer[1]/MetaRow2Holder[0]/MetaRow2[0]/MetaRow2Label[0]", + "entityName" : "MetaRow2Label", + "entityKind" : "ParagraphNode", + "parentPath" : "WorkspaceInvoice[0]/IssuerMetaRow[2]/InvoiceMeta[1]/InvoiceMetaRows[0]/MetaRow2Layer[1]/MetaRow2Holder[0]/MetaRow2[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 342.213, + "computedY" : 723.575, + "placementX" : 342.213, + "placementY" : 723.575, + "placementWidth" : 52.689, + "placementHeight" : 10.867, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 52.689, + "contentHeight" : 10.867, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/IssuerMetaRow[2]/InvoiceMeta[1]/InvoiceMetaRows[0]/MetaRow2Layer[1]/MetaRow2Holder[0]/MetaRow2[0]/MetaRow2Value[1]", + "entityName" : "MetaRow2Value", + "entityKind" : "ParagraphNode", + "parentPath" : "WorkspaceInvoice[0]/IssuerMetaRow[2]/InvoiceMeta[1]/InvoiceMetaRows[0]/MetaRow2Layer[1]/MetaRow2Holder[0]/MetaRow2[0]", + "childIndex" : 1, + "depth" : 8, + "layer" : 8, + "computedX" : 443.777, + "computedY" : 723.102, + "placementX" : 443.777, + "placementY" : 723.102, + "placementWidth" : 56.076, + "placementHeight" : 11.34, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 56.076, + "contentHeight" : 11.34, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/IssuerMetaRow[2]/InvoiceMeta[1]/InvoiceMetaRows[0]/MetaRow3Layer[2]", + "entityName" : "MetaRow3Layer", + "entityKind" : "LayerStackNode", + "parentPath" : "WorkspaceInvoice[0]/IssuerMetaRow[2]/InvoiceMeta[1]/InvoiceMetaRows[0]", + "childIndex" : 2, + "depth" : 5, + "layer" : 5, + "computedX" : 342.213, + "computedY" : 702.677, + "placementX" : 342.213, + "placementY" : 702.677, + "placementWidth" : 226.543, + "placementHeight" : 11.34, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 226.543, + "contentHeight" : 11.34, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/IssuerMetaRow[2]/InvoiceMeta[1]/InvoiceMetaRows[0]/MetaRow3Layer[2]/MetaRow3Holder[0]", + "entityName" : "MetaRow3Holder", + "entityKind" : "SectionNode", + "parentPath" : "WorkspaceInvoice[0]/IssuerMetaRow[2]/InvoiceMeta[1]/InvoiceMetaRows[0]/MetaRow3Layer[2]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 342.213, + "computedY" : 702.677, + "placementX" : 342.213, + "placementY" : 702.677, + "placementWidth" : 226.543, + "placementHeight" : 11.34, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 226.543, + "contentHeight" : 11.34, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/IssuerMetaRow[2]/InvoiceMeta[1]/InvoiceMetaRows[0]/MetaRow3Layer[2]/MetaRow3Holder[0]/MetaRow3[0]", + "entityName" : "MetaRow3", + "entityKind" : "RowNode", + "parentPath" : "WorkspaceInvoice[0]/IssuerMetaRow[2]/InvoiceMeta[1]/InvoiceMetaRows[0]/MetaRow3Layer[2]/MetaRow3Holder[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 342.213, + "computedY" : 702.677, + "placementX" : 342.213, + "placementY" : 702.677, + "placementWidth" : 226.543, + "placementHeight" : 11.34, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 226.543, + "contentHeight" : 11.34, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/IssuerMetaRow[2]/InvoiceMeta[1]/InvoiceMetaRows[0]/MetaRow3Layer[2]/MetaRow3Holder[0]/MetaRow3[0]/MetaRow3Label[0]", + "entityName" : "MetaRow3Label", + "entityKind" : "ParagraphNode", + "parentPath" : "WorkspaceInvoice[0]/IssuerMetaRow[2]/InvoiceMeta[1]/InvoiceMetaRows[0]/MetaRow3Layer[2]/MetaRow3Holder[0]/MetaRow3[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 342.213, + "computedY" : 703.149, + "placementX" : 342.213, + "placementY" : 703.149, + "placementWidth" : 54.727, + "placementHeight" : 10.867, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 54.727, + "contentHeight" : 10.867, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/IssuerMetaRow[2]/InvoiceMeta[1]/InvoiceMetaRows[0]/MetaRow3Layer[2]/MetaRow3Holder[0]/MetaRow3[0]/MetaRow3Value[1]", + "entityName" : "MetaRow3Value", + "entityKind" : "ParagraphNode", + "parentPath" : "WorkspaceInvoice[0]/IssuerMetaRow[2]/InvoiceMeta[1]/InvoiceMetaRows[0]/MetaRow3Layer[2]/MetaRow3Holder[0]/MetaRow3[0]", + "childIndex" : 1, + "depth" : 8, + "layer" : 8, + "computedX" : 443.777, + "computedY" : 702.677, + "placementX" : 443.777, + "placementY" : 702.677, + "placementWidth" : 69.911, + "placementHeight" : 11.34, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 69.911, + "contentHeight" : 11.34, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/IssuerMetaRow[2]/InvoiceMeta[1]/InvoiceMetaRows[0]/MetaRow4Layer[3]", + "entityName" : "MetaRow4Layer", + "entityKind" : "LayerStackNode", + "parentPath" : "WorkspaceInvoice[0]/IssuerMetaRow[2]/InvoiceMeta[1]/InvoiceMetaRows[0]", + "childIndex" : 3, + "depth" : 5, + "layer" : 5, + "computedX" : 342.213, + "computedY" : 682.251, + "placementX" : 342.213, + "placementY" : 682.251, + "placementWidth" : 226.543, + "placementHeight" : 11.34, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 226.543, + "contentHeight" : 11.34, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/IssuerMetaRow[2]/InvoiceMeta[1]/InvoiceMetaRows[0]/MetaRow4Layer[3]/MetaRow4Holder[0]", + "entityName" : "MetaRow4Holder", + "entityKind" : "SectionNode", + "parentPath" : "WorkspaceInvoice[0]/IssuerMetaRow[2]/InvoiceMeta[1]/InvoiceMetaRows[0]/MetaRow4Layer[3]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 342.213, + "computedY" : 682.251, + "placementX" : 342.213, + "placementY" : 682.251, + "placementWidth" : 226.543, + "placementHeight" : 11.34, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 226.543, + "contentHeight" : 11.34, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/IssuerMetaRow[2]/InvoiceMeta[1]/InvoiceMetaRows[0]/MetaRow4Layer[3]/MetaRow4Holder[0]/MetaRow4[0]", + "entityName" : "MetaRow4", + "entityKind" : "RowNode", + "parentPath" : "WorkspaceInvoice[0]/IssuerMetaRow[2]/InvoiceMeta[1]/InvoiceMetaRows[0]/MetaRow4Layer[3]/MetaRow4Holder[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 342.213, + "computedY" : 682.251, + "placementX" : 342.213, + "placementY" : 682.251, + "placementWidth" : 226.543, + "placementHeight" : 11.34, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 226.543, + "contentHeight" : 11.34, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/IssuerMetaRow[2]/InvoiceMeta[1]/InvoiceMetaRows[0]/MetaRow4Layer[3]/MetaRow4Holder[0]/MetaRow4[0]/MetaRow4Label[0]", + "entityName" : "MetaRow4Label", + "entityKind" : "ParagraphNode", + "parentPath" : "WorkspaceInvoice[0]/IssuerMetaRow[2]/InvoiceMeta[1]/InvoiceMetaRows[0]/MetaRow4Layer[3]/MetaRow4Holder[0]/MetaRow4[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 342.213, + "computedY" : 682.724, + "placementX" : 342.213, + "placementY" : 682.724, + "placementWidth" : 59.038, + "placementHeight" : 10.867, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 59.038, + "contentHeight" : 10.867, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/IssuerMetaRow[2]/InvoiceMeta[1]/InvoiceMetaRows[0]/MetaRow4Layer[3]/MetaRow4Holder[0]/MetaRow4[0]/MetaRow4Value[1]", + "entityName" : "MetaRow4Value", + "entityKind" : "ParagraphNode", + "parentPath" : "WorkspaceInvoice[0]/IssuerMetaRow[2]/InvoiceMeta[1]/InvoiceMetaRows[0]/MetaRow4Layer[3]/MetaRow4Holder[0]/MetaRow4[0]", + "childIndex" : 1, + "depth" : 8, + "layer" : 8, + "computedX" : 443.777, + "computedY" : 682.251, + "placementX" : 443.777, + "placementY" : 682.251, + "placementWidth" : 54.063, + "placementHeight" : 11.34, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 54.063, + "contentHeight" : 11.34, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/IssuerMetaRow[2]/InvoiceMeta[1]/InvoiceMetaRows[0]/MetaRow5Layer[4]", + "entityName" : "MetaRow5Layer", + "entityKind" : "LayerStackNode", + "parentPath" : "WorkspaceInvoice[0]/IssuerMetaRow[2]/InvoiceMeta[1]/InvoiceMetaRows[0]", + "childIndex" : 4, + "depth" : 5, + "layer" : 5, + "computedX" : 342.213, + "computedY" : 661.825, + "placementX" : 342.213, + "placementY" : 661.825, + "placementWidth" : 226.543, + "placementHeight" : 11.34, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 226.543, + "contentHeight" : 11.34, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/IssuerMetaRow[2]/InvoiceMeta[1]/InvoiceMetaRows[0]/MetaRow5Layer[4]/MetaRow5Holder[0]", + "entityName" : "MetaRow5Holder", + "entityKind" : "SectionNode", + "parentPath" : "WorkspaceInvoice[0]/IssuerMetaRow[2]/InvoiceMeta[1]/InvoiceMetaRows[0]/MetaRow5Layer[4]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 342.213, + "computedY" : 661.825, + "placementX" : 342.213, + "placementY" : 661.825, + "placementWidth" : 226.543, + "placementHeight" : 11.34, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 226.543, + "contentHeight" : 11.34, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/IssuerMetaRow[2]/InvoiceMeta[1]/InvoiceMetaRows[0]/MetaRow5Layer[4]/MetaRow5Holder[0]/MetaRow5[0]", + "entityName" : "MetaRow5", + "entityKind" : "RowNode", + "parentPath" : "WorkspaceInvoice[0]/IssuerMetaRow[2]/InvoiceMeta[1]/InvoiceMetaRows[0]/MetaRow5Layer[4]/MetaRow5Holder[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 342.213, + "computedY" : 661.825, + "placementX" : 342.213, + "placementY" : 661.825, + "placementWidth" : 226.543, + "placementHeight" : 11.34, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 226.543, + "contentHeight" : 11.34, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/IssuerMetaRow[2]/InvoiceMeta[1]/InvoiceMetaRows[0]/MetaRow5Layer[4]/MetaRow5Holder[0]/MetaRow5[0]/MetaRow5Label[0]", + "entityName" : "MetaRow5Label", + "entityKind" : "ParagraphNode", + "parentPath" : "WorkspaceInvoice[0]/IssuerMetaRow[2]/InvoiceMeta[1]/InvoiceMetaRows[0]/MetaRow5Layer[4]/MetaRow5Holder[0]/MetaRow5[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 342.213, + "computedY" : 662.298, + "placementX" : 342.213, + "placementY" : 662.298, + "placementWidth" : 64.544, + "placementHeight" : 10.867, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 64.544, + "contentHeight" : 10.867, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/IssuerMetaRow[2]/InvoiceMeta[1]/InvoiceMetaRows[0]/MetaRow5Layer[4]/MetaRow5Holder[0]/MetaRow5[0]/MetaRow5Value[1]", + "entityName" : "MetaRow5Value", + "entityKind" : "ParagraphNode", + "parentPath" : "WorkspaceInvoice[0]/IssuerMetaRow[2]/InvoiceMeta[1]/InvoiceMetaRows[0]/MetaRow5Layer[4]/MetaRow5Holder[0]/MetaRow5[0]", + "childIndex" : 1, + "depth" : 8, + "layer" : 8, + "computedX" : 443.777, + "computedY" : 661.825, + "placementX" : 443.777, + "placementY" : 661.825, + "placementWidth" : 28.983, + "placementHeight" : 11.34, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 28.983, + "contentHeight" : 11.34, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/IssuerMetaRow[2]/InvoiceMeta[1]/InvoiceMetaRows[0]/MetaRow6Layer[5]", + "entityName" : "MetaRow6Layer", + "entityKind" : "LayerStackNode", + "parentPath" : "WorkspaceInvoice[0]/IssuerMetaRow[2]/InvoiceMeta[1]/InvoiceMetaRows[0]", + "childIndex" : 5, + "depth" : 5, + "layer" : 5, + "computedX" : 342.213, + "computedY" : 641.4, + "placementX" : 342.213, + "placementY" : 641.4, + "placementWidth" : 226.543, + "placementHeight" : 11.34, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 226.543, + "contentHeight" : 11.34, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/IssuerMetaRow[2]/InvoiceMeta[1]/InvoiceMetaRows[0]/MetaRow6Layer[5]/MetaRow6Holder[0]", + "entityName" : "MetaRow6Holder", + "entityKind" : "SectionNode", + "parentPath" : "WorkspaceInvoice[0]/IssuerMetaRow[2]/InvoiceMeta[1]/InvoiceMetaRows[0]/MetaRow6Layer[5]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 342.213, + "computedY" : 641.4, + "placementX" : 342.213, + "placementY" : 641.4, + "placementWidth" : 226.543, + "placementHeight" : 11.34, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 226.543, + "contentHeight" : 11.34, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/IssuerMetaRow[2]/InvoiceMeta[1]/InvoiceMetaRows[0]/MetaRow6Layer[5]/MetaRow6Holder[0]/MetaRow6[0]", + "entityName" : "MetaRow6", + "entityKind" : "RowNode", + "parentPath" : "WorkspaceInvoice[0]/IssuerMetaRow[2]/InvoiceMeta[1]/InvoiceMetaRows[0]/MetaRow6Layer[5]/MetaRow6Holder[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 342.213, + "computedY" : 641.4, + "placementX" : 342.213, + "placementY" : 641.4, + "placementWidth" : 226.543, + "placementHeight" : 11.34, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 226.543, + "contentHeight" : 11.34, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/IssuerMetaRow[2]/InvoiceMeta[1]/InvoiceMetaRows[0]/MetaRow6Layer[5]/MetaRow6Holder[0]/MetaRow6[0]/MetaRow6Label[0]", + "entityName" : "MetaRow6Label", + "entityKind" : "ParagraphNode", + "parentPath" : "WorkspaceInvoice[0]/IssuerMetaRow[2]/InvoiceMeta[1]/InvoiceMetaRows[0]/MetaRow6Layer[5]/MetaRow6Holder[0]/MetaRow6[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 342.213, + "computedY" : 641.872, + "placementX" : 342.213, + "placementY" : 641.872, + "placementWidth" : 40.735, + "placementHeight" : 10.867, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 40.735, + "contentHeight" : 10.867, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/IssuerMetaRow[2]/InvoiceMeta[1]/InvoiceMetaRows[0]/MetaRow6Layer[5]/MetaRow6Holder[0]/MetaRow6[0]/MetaRow6Value[1]", + "entityName" : "MetaRow6Value", + "entityKind" : "ParagraphNode", + "parentPath" : "WorkspaceInvoice[0]/IssuerMetaRow[2]/InvoiceMeta[1]/InvoiceMetaRows[0]/MetaRow6Layer[5]/MetaRow6Holder[0]/MetaRow6[0]", + "childIndex" : 1, + "depth" : 8, + "layer" : 8, + "computedX" : 443.777, + "computedY" : 641.4, + "placementX" : 443.777, + "placementY" : 641.4, + "placementWidth" : 57.257, + "placementHeight" : 11.34, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 57.257, + "contentHeight" : 11.34, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/PartiesRule[3]", + "entityName" : "PartiesRule", + "entityKind" : "LineNode", + "parentPath" : "WorkspaceInvoice[0]", + "childIndex" : 3, + "depth" : 2, + "layer" : 2, + "computedX" : 25.391, + "computedY" : 626.667, + "placementX" : 25.391, + "placementY" : 626.667, + "placementWidth" : 543.365, + "placementHeight" : 1.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 543.365, + "contentHeight" : 1.0, + "margin" : { + "top" : 13.733, + "right" : 0.0, + "bottom" : 13.542, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/PartiesRow[4]", + "entityName" : "PartiesRow", + "entityKind" : "RowNode", + "parentPath" : "WorkspaceInvoice[0]", + "childIndex" : 4, + "depth" : 2, + "layer" : 2, + "computedX" : 25.391, + "computedY" : 499.093, + "placementX" : 25.391, + "placementY" : 499.093, + "placementWidth" : 543.365, + "placementHeight" : 114.032, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 543.365, + "contentHeight" : 114.032, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/PartiesRow[4]/Party1[0]", + "entityName" : "Party1", + "entityKind" : "SectionNode", + "parentPath" : "WorkspaceInvoice[0]/PartiesRow[4]", + "childIndex" : 0, + "depth" : 3, + "layer" : 3, + "computedX" : 25.391, + "computedY" : 499.093, + "placementX" : 25.391, + "placementY" : 499.093, + "placementWidth" : 271.683, + "placementHeight" : 114.032, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 271.683, + "contentHeight" : 114.032, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/PartiesRow[4]/Party1[0]/PartyHeadLayer[0]", + "entityName" : "PartyHeadLayer", + "entityKind" : "LayerStackNode", + "parentPath" : "WorkspaceInvoice[0]/PartiesRow[4]/Party1[0]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 25.391, + "computedY" : 589.427, + "placementX" : 25.391, + "placementY" : 589.427, + "placementWidth" : 271.683, + "placementHeight" : 23.698, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 271.683, + "contentHeight" : 23.698, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/PartiesRow[4]/Party1[0]/PartyHeadLayer[0]/PartyHeadHolder[0]", + "entityName" : "PartyHeadHolder", + "entityKind" : "SectionNode", + "parentPath" : "WorkspaceInvoice[0]/PartiesRow[4]/Party1[0]/PartyHeadLayer[0]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 25.391, + "computedY" : 589.427, + "placementX" : 25.391, + "placementY" : 589.427, + "placementWidth" : 271.683, + "placementHeight" : 23.698, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 271.683, + "contentHeight" : 23.698, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/PartiesRow[4]/Party1[0]/PartyHeadLayer[0]/PartyHeadHolder[0]/PartyHead[0]", + "entityName" : "PartyHead", + "entityKind" : "RowNode", + "parentPath" : "WorkspaceInvoice[0]/PartiesRow[4]/Party1[0]/PartyHeadLayer[0]/PartyHeadHolder[0]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 25.391, + "computedY" : 589.427, + "placementX" : 25.391, + "placementY" : 589.427, + "placementWidth" : 271.683, + "placementHeight" : 23.698, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 271.683, + "contentHeight" : 23.698, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/PartiesRow[4]/Party1[0]/PartyHeadLayer[0]/PartyHeadHolder[0]/PartyHead[0]/PartyDisc[0]", + "entityName" : "PartyDisc", + "entityKind" : "SectionNode", + "parentPath" : "WorkspaceInvoice[0]/PartiesRow[4]/Party1[0]/PartyHeadLayer[0]/PartyHeadHolder[0]/PartyHead[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 25.391, + "computedY" : 589.427, + "placementX" : 25.391, + "placementY" : 589.427, + "placementWidth" : 23.698, + "placementHeight" : 23.698, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 23.698, + "contentHeight" : 23.698, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/PartiesRow[4]/Party1[0]/PartyHeadLayer[0]/PartyHeadHolder[0]/PartyHead[0]/PartyDisc[0]/Discbill-to[0]", + "entityName" : "Discbill-to", + "entityKind" : "ShapeContainerNode", + "parentPath" : "WorkspaceInvoice[0]/PartiesRow[4]/Party1[0]/PartyHeadLayer[0]/PartyHeadHolder[0]/PartyHead[0]/PartyDisc[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 25.391, + "computedY" : 589.427, + "placementX" : 25.391, + "placementY" : 589.427, + "placementWidth" : 23.698, + "placementHeight" : 23.698, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 23.698, + "contentHeight" : 23.698, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/PartiesRow[4]/Party1[0]/PartyHeadLayer[0]/PartyHeadHolder[0]/PartyHead[0]/PartyDisc[0]/Discbill-to[0]/Glyphbill-to[0]", + "entityName" : "Glyphbill-to", + "entityKind" : "SectionNode", + "parentPath" : "WorkspaceInvoice[0]/PartiesRow[4]/Party1[0]/PartyHeadLayer[0]/PartyHeadHolder[0]/PartyHead[0]/PartyDisc[0]/Discbill-to[0]", + "childIndex" : 0, + "depth" : 9, + "layer" : 9, + "computedX" : 25.391, + "computedY" : 595.634, + "placementX" : 25.391, + "placementY" : 595.634, + "placementWidth" : 23.698, + "placementHeight" : 11.285, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 23.698, + "contentHeight" : 11.285, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/PartiesRow[4]/Party1[0]/PartyHeadLayer[0]/PartyHeadHolder[0]/PartyHead[0]/PartyDisc[0]/Discbill-to[0]/Glyphbill-to[0]/Align[0]", + "entityName" : null, + "entityKind" : "Align", + "parentPath" : "WorkspaceInvoice[0]/PartiesRow[4]/Party1[0]/PartyHeadLayer[0]/PartyHeadHolder[0]/PartyHead[0]/PartyDisc[0]/Discbill-to[0]/Glyphbill-to[0]", + "childIndex" : 0, + "depth" : 10, + "layer" : 10, + "computedX" : 25.391, + "computedY" : 595.634, + "placementX" : 25.391, + "placementY" : 595.634, + "placementWidth" : 23.698, + "placementHeight" : 11.285, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 23.698, + "contentHeight" : 11.285, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/PartiesRow[4]/Party1[0]/PartyHeadLayer[0]/PartyHeadHolder[0]/PartyHead[0]/PartyDisc[0]/Discbill-to[0]/Glyphbill-to[0]/Align[0]/SvgIcon[0]", + "entityName" : "SvgIcon", + "entityKind" : "LayerStackNode", + "parentPath" : "WorkspaceInvoice[0]/PartiesRow[4]/Party1[0]/PartyHeadLayer[0]/PartyHeadHolder[0]/PartyHead[0]/PartyDisc[0]/Discbill-to[0]/Glyphbill-to[0]/Align[0]", + "childIndex" : 0, + "depth" : 11, + "layer" : 11, + "computedX" : 31.598, + "computedY" : 595.634, + "placementX" : 31.598, + "placementY" : 595.634, + "placementWidth" : 11.285, + "placementHeight" : 11.285, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 11.285, + "contentHeight" : 11.285, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/PartiesRow[4]/Party1[0]/PartyHeadLayer[0]/PartyHeadHolder[0]/PartyHead[0]/PartyDisc[0]/Discbill-to[0]/Glyphbill-to[0]/Align[0]/SvgIcon[0]/SvgLayer0[0]", + "entityName" : "SvgLayer0", + "entityKind" : "Path", + "parentPath" : "WorkspaceInvoice[0]/PartiesRow[4]/Party1[0]/PartyHeadLayer[0]/PartyHeadHolder[0]/PartyHead[0]/PartyDisc[0]/Discbill-to[0]/Glyphbill-to[0]/Align[0]/SvgIcon[0]", + "childIndex" : 0, + "depth" : 12, + "layer" : 12, + "computedX" : 31.598, + "computedY" : 595.634, + "placementX" : 31.598, + "placementY" : 595.634, + "placementWidth" : 11.285, + "placementHeight" : 11.285, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 11.285, + "contentHeight" : 11.285, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/PartiesRow[4]/Party1[0]/PartyHeadLayer[0]/PartyHeadHolder[0]/PartyHead[0]/PartyLabel[1]", + "entityName" : "PartyLabel", + "entityKind" : "ParagraphNode", + "parentPath" : "WorkspaceInvoice[0]/PartiesRow[4]/Party1[0]/PartyHeadLayer[0]/PartyHeadHolder[0]/PartyHead[0]", + "childIndex" : 1, + "depth" : 7, + "layer" : 7, + "computedX" : 59.245, + "computedY" : 599.492, + "placementX" : 59.245, + "placementY" : 599.492, + "placementWidth" : 34.464, + "placementHeight" : 11.34, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 34.464, + "contentHeight" : 11.34, + "margin" : { + "top" : 2.293, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/PartiesRow[4]/Party1[0]/PartyBody[1]", + "entityName" : "PartyBody", + "entityKind" : "SectionNode", + "parentPath" : "WorkspaceInvoice[0]/PartiesRow[4]/Party1[0]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 25.391, + "computedY" : 499.093, + "placementX" : 25.391, + "placementY" : 499.093, + "placementWidth" : 271.683, + "placementHeight" : 90.334, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 271.683, + "contentHeight" : 90.334, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 33.855 + } + }, { + "path" : "WorkspaceInvoice[0]/PartiesRow[4]/Party1[0]/PartyBody[1]/PartyName[0]", + "entityName" : "PartyName", + "entityKind" : "ParagraphNode", + "parentPath" : "WorkspaceInvoice[0]/PartiesRow[4]/Party1[0]/PartyBody[1]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 59.245, + "computedY" : 575.007, + "placementX" : 59.245, + "placementY" : 575.007, + "placementWidth" : 87.579, + "placementHeight" : 13.291, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 87.579, + "contentHeight" : 13.291, + "margin" : { + "top" : 1.128, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/PartiesRow[4]/Party1[0]/PartyBody[1]/PartyAddressLines[1]", + "entityName" : "PartyAddressLines", + "entityKind" : "SectionNode", + "parentPath" : "WorkspaceInvoice[0]/PartiesRow[4]/Party1[0]/PartyBody[1]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 59.245, + "computedY" : 525.613, + "placementX" : 59.245, + "placementY" : 525.613, + "placementWidth" : 86.108, + "placementHeight" : 42.938, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 86.108, + "contentHeight" : 42.938, + "margin" : { + "top" : 6.457, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/PartiesRow[4]/Party1[0]/PartyBody[1]/PartyAddressLines[1]/PartyAddressLine1[0]", + "entityName" : "PartyAddressLine1", + "entityKind" : "ParagraphNode", + "parentPath" : "WorkspaceInvoice[0]/PartiesRow[4]/Party1[0]/PartyBody[1]/PartyAddressLines[1]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 59.245, + "computedY" : 557.21, + "placementX" : 59.245, + "placementY" : 557.21, + "placementWidth" : 59.856, + "placementHeight" : 11.34, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 59.856, + "contentHeight" : 11.34, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/PartiesRow[4]/Party1[0]/PartyBody[1]/PartyAddressLines[1]/PartyAddressLine2[1]", + "entityName" : "PartyAddressLine2", + "entityKind" : "ParagraphNode", + "parentPath" : "WorkspaceInvoice[0]/PartiesRow[4]/Party1[0]/PartyBody[1]/PartyAddressLines[1]", + "childIndex" : 1, + "depth" : 6, + "layer" : 6, + "computedX" : 59.245, + "computedY" : 541.412, + "placementX" : 59.245, + "placementY" : 541.412, + "placementWidth" : 86.108, + "placementHeight" : 11.34, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 86.108, + "contentHeight" : 11.34, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/PartiesRow[4]/Party1[0]/PartyBody[1]/PartyAddressLines[1]/PartyAddressLine3[2]", + "entityName" : "PartyAddressLine3", + "entityKind" : "ParagraphNode", + "parentPath" : "WorkspaceInvoice[0]/PartiesRow[4]/Party1[0]/PartyBody[1]/PartyAddressLines[1]", + "childIndex" : 2, + "depth" : 6, + "layer" : 6, + "computedX" : 59.245, + "computedY" : 525.613, + "placementX" : 59.245, + "placementY" : 525.613, + "placementWidth" : 67.737, + "placementHeight" : 11.34, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 67.737, + "contentHeight" : 11.34, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/PartiesRow[4]/Party1[0]/PartyBody[1]/PartyTaxLineLayer[2]", + "entityName" : "PartyTaxLineLayer", + "entityKind" : "LayerStackNode", + "parentPath" : "WorkspaceInvoice[0]/PartiesRow[4]/Party1[0]/PartyBody[1]", + "childIndex" : 2, + "depth" : 5, + "layer" : 5, + "computedX" : 59.245, + "computedY" : 499.093, + "placementX" : 59.245, + "placementY" : 499.093, + "placementWidth" : 237.828, + "placementHeight" : 26.519, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 237.828, + "contentHeight" : 26.519, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/PartiesRow[4]/Party1[0]/PartyBody[1]/PartyTaxLineLayer[2]/PartyTaxLineHolder[0]", + "entityName" : "PartyTaxLineHolder", + "entityKind" : "SectionNode", + "parentPath" : "WorkspaceInvoice[0]/PartiesRow[4]/Party1[0]/PartyBody[1]/PartyTaxLineLayer[2]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 59.245, + "computedY" : 499.093, + "placementX" : 59.245, + "placementY" : 499.093, + "placementWidth" : 237.828, + "placementHeight" : 26.519, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 237.828, + "contentHeight" : 26.519, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/PartiesRow[4]/Party1[0]/PartyBody[1]/PartyTaxLineLayer[2]/PartyTaxLineHolder[0]/PartyTaxLine[0]", + "entityName" : "PartyTaxLine", + "entityKind" : "RowNode", + "parentPath" : "WorkspaceInvoice[0]/PartiesRow[4]/Party1[0]/PartyBody[1]/PartyTaxLineLayer[2]/PartyTaxLineHolder[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 59.245, + "computedY" : 499.093, + "placementX" : 59.245, + "placementY" : 499.093, + "placementWidth" : 237.828, + "placementHeight" : 11.34, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 237.828, + "contentHeight" : 11.34, + "margin" : { + "top" : 15.179, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/PartiesRow[4]/Party1[0]/PartyBody[1]/PartyTaxLineLayer[2]/PartyTaxLineHolder[0]/PartyTaxLine[0]/PartyTaxLabel[0]", + "entityName" : "PartyTaxLabel", + "entityKind" : "ParagraphNode", + "parentPath" : "WorkspaceInvoice[0]/PartiesRow[4]/Party1[0]/PartyBody[1]/PartyTaxLineLayer[2]/PartyTaxLineHolder[0]/PartyTaxLine[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 59.245, + "computedY" : 499.093, + "placementX" : 59.245, + "placementY" : 499.093, + "placementWidth" : 32.999, + "placementHeight" : 11.34, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 32.999, + "contentHeight" : 11.34, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/PartiesRow[4]/Party1[0]/PartyBody[1]/PartyTaxLineLayer[2]/PartyTaxLineHolder[0]/PartyTaxLine[0]/PartyTaxValue[1]", + "entityName" : "PartyTaxValue", + "entityKind" : "ParagraphNode", + "parentPath" : "WorkspaceInvoice[0]/PartiesRow[4]/Party1[0]/PartyBody[1]/PartyTaxLineLayer[2]/PartyTaxLineHolder[0]/PartyTaxLine[0]", + "childIndex" : 1, + "depth" : 8, + "layer" : 8, + "computedX" : 103.821, + "computedY" : 499.093, + "placementX" : 103.821, + "placementY" : 499.093, + "placementWidth" : 69.609, + "placementHeight" : 11.34, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 69.609, + "contentHeight" : 11.34, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/PartiesRow[4]/Party2[1]", + "entityName" : "Party2", + "entityKind" : "SectionNode", + "parentPath" : "WorkspaceInvoice[0]/PartiesRow[4]", + "childIndex" : 1, + "depth" : 3, + "layer" : 3, + "computedX" : 297.074, + "computedY" : 525.613, + "placementX" : 297.074, + "placementY" : 525.613, + "placementWidth" : 271.683, + "placementHeight" : 87.513, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 271.683, + "contentHeight" : 87.513, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 15.799 + } + }, { + "path" : "WorkspaceInvoice[0]/PartiesRow[4]/Party2[1]/PartyHeadLayer[0]", + "entityName" : "PartyHeadLayer", + "entityKind" : "LayerStackNode", + "parentPath" : "WorkspaceInvoice[0]/PartiesRow[4]/Party2[1]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 312.872, + "computedY" : 589.427, + "placementX" : 312.872, + "placementY" : 589.427, + "placementWidth" : 255.884, + "placementHeight" : 23.698, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 255.884, + "contentHeight" : 23.698, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/PartiesRow[4]/Party2[1]/PartyHeadLayer[0]/PartyHeadHolder[0]", + "entityName" : "PartyHeadHolder", + "entityKind" : "SectionNode", + "parentPath" : "WorkspaceInvoice[0]/PartiesRow[4]/Party2[1]/PartyHeadLayer[0]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 312.872, + "computedY" : 589.427, + "placementX" : 312.872, + "placementY" : 589.427, + "placementWidth" : 255.884, + "placementHeight" : 23.698, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 255.884, + "contentHeight" : 23.698, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/PartiesRow[4]/Party2[1]/PartyHeadLayer[0]/PartyHeadHolder[0]/PartyHead[0]", + "entityName" : "PartyHead", + "entityKind" : "RowNode", + "parentPath" : "WorkspaceInvoice[0]/PartiesRow[4]/Party2[1]/PartyHeadLayer[0]/PartyHeadHolder[0]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 312.872, + "computedY" : 589.427, + "placementX" : 312.872, + "placementY" : 589.427, + "placementWidth" : 255.884, + "placementHeight" : 23.698, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 255.884, + "contentHeight" : 23.698, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/PartiesRow[4]/Party2[1]/PartyHeadLayer[0]/PartyHeadHolder[0]/PartyHead[0]/PartyDisc[0]", + "entityName" : "PartyDisc", + "entityKind" : "SectionNode", + "parentPath" : "WorkspaceInvoice[0]/PartiesRow[4]/Party2[1]/PartyHeadLayer[0]/PartyHeadHolder[0]/PartyHead[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 312.872, + "computedY" : 589.427, + "placementX" : 312.872, + "placementY" : 589.427, + "placementWidth" : 23.698, + "placementHeight" : 23.698, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 23.698, + "contentHeight" : 23.698, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/PartiesRow[4]/Party2[1]/PartyHeadLayer[0]/PartyHeadHolder[0]/PartyHead[0]/PartyDisc[0]/Discship-to[0]", + "entityName" : "Discship-to", + "entityKind" : "ShapeContainerNode", + "parentPath" : "WorkspaceInvoice[0]/PartiesRow[4]/Party2[1]/PartyHeadLayer[0]/PartyHeadHolder[0]/PartyHead[0]/PartyDisc[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 312.872, + "computedY" : 589.427, + "placementX" : 312.872, + "placementY" : 589.427, + "placementWidth" : 23.698, + "placementHeight" : 23.698, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 23.698, + "contentHeight" : 23.698, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/PartiesRow[4]/Party2[1]/PartyHeadLayer[0]/PartyHeadHolder[0]/PartyHead[0]/PartyDisc[0]/Discship-to[0]/Glyphship-to[0]", + "entityName" : "Glyphship-to", + "entityKind" : "SectionNode", + "parentPath" : "WorkspaceInvoice[0]/PartiesRow[4]/Party2[1]/PartyHeadLayer[0]/PartyHeadHolder[0]/PartyHead[0]/PartyDisc[0]/Discship-to[0]", + "childIndex" : 0, + "depth" : 9, + "layer" : 9, + "computedX" : 312.872, + "computedY" : 595.634, + "placementX" : 312.872, + "placementY" : 595.634, + "placementWidth" : 23.698, + "placementHeight" : 11.285, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 23.698, + "contentHeight" : 11.285, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/PartiesRow[4]/Party2[1]/PartyHeadLayer[0]/PartyHeadHolder[0]/PartyHead[0]/PartyDisc[0]/Discship-to[0]/Glyphship-to[0]/Align[0]", + "entityName" : null, + "entityKind" : "Align", + "parentPath" : "WorkspaceInvoice[0]/PartiesRow[4]/Party2[1]/PartyHeadLayer[0]/PartyHeadHolder[0]/PartyHead[0]/PartyDisc[0]/Discship-to[0]/Glyphship-to[0]", + "childIndex" : 0, + "depth" : 10, + "layer" : 10, + "computedX" : 312.872, + "computedY" : 595.634, + "placementX" : 312.872, + "placementY" : 595.634, + "placementWidth" : 23.698, + "placementHeight" : 11.285, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 23.698, + "contentHeight" : 11.285, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/PartiesRow[4]/Party2[1]/PartyHeadLayer[0]/PartyHeadHolder[0]/PartyHead[0]/PartyDisc[0]/Discship-to[0]/Glyphship-to[0]/Align[0]/SvgIcon[0]", + "entityName" : "SvgIcon", + "entityKind" : "LayerStackNode", + "parentPath" : "WorkspaceInvoice[0]/PartiesRow[4]/Party2[1]/PartyHeadLayer[0]/PartyHeadHolder[0]/PartyHead[0]/PartyDisc[0]/Discship-to[0]/Glyphship-to[0]/Align[0]", + "childIndex" : 0, + "depth" : 11, + "layer" : 11, + "computedX" : 319.079, + "computedY" : 595.634, + "placementX" : 319.079, + "placementY" : 595.634, + "placementWidth" : 11.285, + "placementHeight" : 11.285, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 11.285, + "contentHeight" : 11.285, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/PartiesRow[4]/Party2[1]/PartyHeadLayer[0]/PartyHeadHolder[0]/PartyHead[0]/PartyDisc[0]/Discship-to[0]/Glyphship-to[0]/Align[0]/SvgIcon[0]/SvgLayer0[0]", + "entityName" : "SvgLayer0", + "entityKind" : "Path", + "parentPath" : "WorkspaceInvoice[0]/PartiesRow[4]/Party2[1]/PartyHeadLayer[0]/PartyHeadHolder[0]/PartyHead[0]/PartyDisc[0]/Discship-to[0]/Glyphship-to[0]/Align[0]/SvgIcon[0]", + "childIndex" : 0, + "depth" : 12, + "layer" : 12, + "computedX" : 319.079, + "computedY" : 595.634, + "placementX" : 319.079, + "placementY" : 595.634, + "placementWidth" : 11.285, + "placementHeight" : 11.285, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 11.285, + "contentHeight" : 11.285, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/PartiesRow[4]/Party2[1]/PartyHeadLayer[0]/PartyHeadHolder[0]/PartyHead[0]/PartyLabel[1]", + "entityName" : "PartyLabel", + "entityKind" : "ParagraphNode", + "parentPath" : "WorkspaceInvoice[0]/PartiesRow[4]/Party2[1]/PartyHeadLayer[0]/PartyHeadHolder[0]/PartyHead[0]", + "childIndex" : 1, + "depth" : 7, + "layer" : 7, + "computedX" : 346.727, + "computedY" : 599.492, + "placementX" : 346.727, + "placementY" : 599.492, + "placementWidth" : 36.732, + "placementHeight" : 11.34, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 36.732, + "contentHeight" : 11.34, + "margin" : { + "top" : 2.293, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/PartiesRow[4]/Party2[1]/PartyBody[1]", + "entityName" : "PartyBody", + "entityKind" : "SectionNode", + "parentPath" : "WorkspaceInvoice[0]/PartiesRow[4]/Party2[1]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 312.872, + "computedY" : 525.613, + "placementX" : 312.872, + "placementY" : 525.613, + "placementWidth" : 123.384, + "placementHeight" : 63.815, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 123.384, + "contentHeight" : 63.815, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 33.855 + } + }, { + "path" : "WorkspaceInvoice[0]/PartiesRow[4]/Party2[1]/PartyBody[1]/PartyName[0]", + "entityName" : "PartyName", + "entityKind" : "ParagraphNode", + "parentPath" : "WorkspaceInvoice[0]/PartiesRow[4]/Party2[1]/PartyBody[1]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 346.727, + "computedY" : 575.007, + "placementX" : 346.727, + "placementY" : 575.007, + "placementWidth" : 87.579, + "placementHeight" : 13.291, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 87.579, + "contentHeight" : 13.291, + "margin" : { + "top" : 1.128, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/PartiesRow[4]/Party2[1]/PartyBody[1]/PartyAddressLines[1]", + "entityName" : "PartyAddressLines", + "entityKind" : "SectionNode", + "parentPath" : "WorkspaceInvoice[0]/PartiesRow[4]/Party2[1]/PartyBody[1]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 346.727, + "computedY" : 525.613, + "placementX" : 346.727, + "placementY" : 525.613, + "placementWidth" : 89.529, + "placementHeight" : 42.938, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 89.529, + "contentHeight" : 42.938, + "margin" : { + "top" : 6.457, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/PartiesRow[4]/Party2[1]/PartyBody[1]/PartyAddressLines[1]/PartyAddressLine1[0]", + "entityName" : "PartyAddressLine1", + "entityKind" : "ParagraphNode", + "parentPath" : "WorkspaceInvoice[0]/PartiesRow[4]/Party2[1]/PartyBody[1]/PartyAddressLines[1]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 346.727, + "computedY" : 557.21, + "placementX" : 346.727, + "placementY" : 557.21, + "placementWidth" : 89.529, + "placementHeight" : 11.34, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 89.529, + "contentHeight" : 11.34, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/PartiesRow[4]/Party2[1]/PartyBody[1]/PartyAddressLines[1]/PartyAddressLine2[1]", + "entityName" : "PartyAddressLine2", + "entityKind" : "ParagraphNode", + "parentPath" : "WorkspaceInvoice[0]/PartiesRow[4]/Party2[1]/PartyBody[1]/PartyAddressLines[1]", + "childIndex" : 1, + "depth" : 6, + "layer" : 6, + "computedX" : 346.727, + "computedY" : 541.412, + "placementX" : 346.727, + "placementY" : 541.412, + "placementWidth" : 62.086, + "placementHeight" : 11.34, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 62.086, + "contentHeight" : 11.34, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/PartiesRow[4]/Party2[1]/PartyBody[1]/PartyAddressLines[1]/PartyAddressLine3[2]", + "entityName" : "PartyAddressLine3", + "entityKind" : "ParagraphNode", + "parentPath" : "WorkspaceInvoice[0]/PartiesRow[4]/Party2[1]/PartyBody[1]/PartyAddressLines[1]", + "childIndex" : 2, + "depth" : 6, + "layer" : 6, + "computedX" : 346.727, + "computedY" : 525.613, + "placementX" : 346.727, + "placementY" : 525.613, + "placementWidth" : 67.737, + "placementHeight" : 11.34, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 67.737, + "contentHeight" : 11.34, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/LineItemsBox[5]", + "entityName" : "LineItemsBox", + "entityKind" : "SectionNode", + "parentPath" : "WorkspaceInvoice[0]", + "childIndex" : 5, + "depth" : 2, + "layer" : 2, + "computedX" : 25.391, + "computedY" : 279.879, + "placementX" : 25.391, + "placementY" : 279.879, + "placementWidth" : 543.365, + "placementHeight" : 196.171, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 543.365, + "contentHeight" : 196.171, + "margin" : { + "top" : 23.043, + "right" : 0.0, + "bottom" : 15.235, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/LineItemsBox[5]/LineItems[0]", + "entityName" : "LineItems", + "entityKind" : "TableNode", + "parentPath" : "WorkspaceInvoice[0]/LineItemsBox[5]", + "childIndex" : 0, + "depth" : 3, + "layer" : 3, + "computedX" : 25.391, + "computedY" : 279.879, + "placementX" : 25.391, + "placementY" : 279.879, + "placementWidth" : 543.365, + "placementHeight" : 196.171, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 543.365, + "contentHeight" : 196.171, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/SettlementRow[6]", + "entityName" : "SettlementRow", + "entityKind" : "RowNode", + "parentPath" : "WorkspaceInvoice[0]", + "childIndex" : 6, + "depth" : 2, + "layer" : 2, + "computedX" : 25.391, + "computedY" : 87.384, + "placementX" : 25.391, + "placementY" : 87.384, + "placementWidth" : 543.365, + "placementHeight" : 177.261, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 543.365, + "contentHeight" : 177.261, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/SettlementRow[6]/PaymentDetails[0]", + "entityName" : "PaymentDetails", + "entityKind" : "SectionNode", + "parentPath" : "WorkspaceInvoice[0]/SettlementRow[6]", + "childIndex" : 0, + "depth" : 3, + "layer" : 3, + "computedX" : 25.391, + "computedY" : 87.384, + "placementX" : 25.391, + "placementY" : 87.384, + "placementWidth" : 236.982, + "placementHeight" : 177.261, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 236.982, + "contentHeight" : 177.261, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 9.592, + "right" : 13.542, + "bottom" : 12.413, + "left" : 13.542 + } + }, { + "path" : "WorkspaceInvoice[0]/SettlementRow[6]/PaymentDetails[0]/CardHeadLayer[0]", + "entityName" : "CardHeadLayer", + "entityKind" : "LayerStackNode", + "parentPath" : "WorkspaceInvoice[0]/SettlementRow[6]/PaymentDetails[0]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 38.933, + "computedY" : 239.818, + "placementX" : 38.933, + "placementY" : 239.818, + "placementWidth" : 209.898, + "placementHeight" : 15.235, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 209.898, + "contentHeight" : 15.235, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/SettlementRow[6]/PaymentDetails[0]/CardHeadLayer[0]/CardHeadHolder[0]", + "entityName" : "CardHeadHolder", + "entityKind" : "SectionNode", + "parentPath" : "WorkspaceInvoice[0]/SettlementRow[6]/PaymentDetails[0]/CardHeadLayer[0]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 38.933, + "computedY" : 239.818, + "placementX" : 38.933, + "placementY" : 239.818, + "placementWidth" : 209.898, + "placementHeight" : 15.235, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 209.898, + "contentHeight" : 15.235, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/SettlementRow[6]/PaymentDetails[0]/CardHeadLayer[0]/CardHeadHolder[0]/CardHead[0]", + "entityName" : "CardHead", + "entityKind" : "RowNode", + "parentPath" : "WorkspaceInvoice[0]/SettlementRow[6]/PaymentDetails[0]/CardHeadLayer[0]/CardHeadHolder[0]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 38.933, + "computedY" : 239.818, + "placementX" : 38.933, + "placementY" : 239.818, + "placementWidth" : 209.898, + "placementHeight" : 15.235, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 209.898, + "contentHeight" : 15.235, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/SettlementRow[6]/PaymentDetails[0]/CardHeadLayer[0]/CardHeadHolder[0]/CardHead[0]/CardIcon[0]", + "entityName" : "CardIcon", + "entityKind" : "SectionNode", + "parentPath" : "WorkspaceInvoice[0]/SettlementRow[6]/PaymentDetails[0]/CardHeadLayer[0]/CardHeadHolder[0]/CardHead[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 38.933, + "computedY" : 239.818, + "placementX" : 38.933, + "placementY" : 239.818, + "placementWidth" : 15.235, + "placementHeight" : 15.235, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 15.235, + "contentHeight" : 15.235, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/SettlementRow[6]/PaymentDetails[0]/CardHeadLayer[0]/CardHeadHolder[0]/CardHead[0]/CardIcon[0]/Glyphbank[0]", + "entityName" : "Glyphbank", + "entityKind" : "SectionNode", + "parentPath" : "WorkspaceInvoice[0]/SettlementRow[6]/PaymentDetails[0]/CardHeadLayer[0]/CardHeadHolder[0]/CardHead[0]/CardIcon[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 38.933, + "computedY" : 239.818, + "placementX" : 38.933, + "placementY" : 239.818, + "placementWidth" : 15.235, + "placementHeight" : 15.235, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 15.235, + "contentHeight" : 15.235, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/SettlementRow[6]/PaymentDetails[0]/CardHeadLayer[0]/CardHeadHolder[0]/CardHead[0]/CardIcon[0]/Glyphbank[0]/Align[0]", + "entityName" : null, + "entityKind" : "Align", + "parentPath" : "WorkspaceInvoice[0]/SettlementRow[6]/PaymentDetails[0]/CardHeadLayer[0]/CardHeadHolder[0]/CardHead[0]/CardIcon[0]/Glyphbank[0]", + "childIndex" : 0, + "depth" : 9, + "layer" : 9, + "computedX" : 38.933, + "computedY" : 239.818, + "placementX" : 38.933, + "placementY" : 239.818, + "placementWidth" : 15.235, + "placementHeight" : 15.235, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 15.235, + "contentHeight" : 15.235, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/SettlementRow[6]/PaymentDetails[0]/CardHeadLayer[0]/CardHeadHolder[0]/CardHead[0]/CardIcon[0]/Glyphbank[0]/Align[0]/SvgIcon[0]", + "entityName" : "SvgIcon", + "entityKind" : "LayerStackNode", + "parentPath" : "WorkspaceInvoice[0]/SettlementRow[6]/PaymentDetails[0]/CardHeadLayer[0]/CardHeadHolder[0]/CardHead[0]/CardIcon[0]/Glyphbank[0]/Align[0]", + "childIndex" : 0, + "depth" : 10, + "layer" : 10, + "computedX" : 38.933, + "computedY" : 239.818, + "placementX" : 38.933, + "placementY" : 239.818, + "placementWidth" : 15.235, + "placementHeight" : 15.235, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 15.235, + "contentHeight" : 15.235, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/SettlementRow[6]/PaymentDetails[0]/CardHeadLayer[0]/CardHeadHolder[0]/CardHead[0]/CardIcon[0]/Glyphbank[0]/Align[0]/SvgIcon[0]/SvgLayer0[0]", + "entityName" : "SvgLayer0", + "entityKind" : "Path", + "parentPath" : "WorkspaceInvoice[0]/SettlementRow[6]/PaymentDetails[0]/CardHeadLayer[0]/CardHeadHolder[0]/CardHead[0]/CardIcon[0]/Glyphbank[0]/Align[0]/SvgIcon[0]", + "childIndex" : 0, + "depth" : 11, + "layer" : 11, + "computedX" : 38.933, + "computedY" : 239.818, + "placementX" : 38.933, + "placementY" : 239.818, + "placementWidth" : 15.235, + "placementHeight" : 15.235, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 15.235, + "contentHeight" : 15.235, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/SettlementRow[6]/PaymentDetails[0]/CardHeadLayer[0]/CardHeadHolder[0]/CardHead[0]/CardLabel[1]", + "entityName" : "CardLabel", + "entityKind" : "ParagraphNode", + "parentPath" : "WorkspaceInvoice[0]/SettlementRow[6]/PaymentDetails[0]/CardHeadLayer[0]/CardHeadHolder[0]/CardHead[0]", + "childIndex" : 1, + "depth" : 7, + "layer" : 7, + "computedX" : 66.016, + "computedY" : 241.195, + "placementX" : 66.016, + "placementY" : 241.195, + "placementWidth" : 77.407, + "placementHeight" : 10.215, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 77.407, + "contentHeight" : 10.215, + "margin" : { + "top" : 3.642, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/SettlementRow[6]/PaymentDetails[0]/CardFields[1]", + "entityName" : "CardFields", + "entityKind" : "SectionNode", + "parentPath" : "WorkspaceInvoice[0]/SettlementRow[6]/PaymentDetails[0]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 38.933, + "computedY" : 135.216, + "placementX" : 38.933, + "placementY" : 135.216, + "placementWidth" : 209.898, + "placementHeight" : 98.396, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 209.898, + "contentHeight" : 98.396, + "margin" : { + "top" : 6.207, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/SettlementRow[6]/PaymentDetails[0]/CardFields[1]/CardField1Layer[0]", + "entityName" : "CardField1Layer", + "entityKind" : "LayerStackNode", + "parentPath" : "WorkspaceInvoice[0]/SettlementRow[6]/PaymentDetails[0]/CardFields[1]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 38.933, + "computedY" : 223.689, + "placementX" : 38.933, + "placementY" : 223.689, + "placementWidth" : 209.898, + "placementHeight" : 9.922, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 209.898, + "contentHeight" : 9.922, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/SettlementRow[6]/PaymentDetails[0]/CardFields[1]/CardField1Layer[0]/CardField1Holder[0]", + "entityName" : "CardField1Holder", + "entityKind" : "SectionNode", + "parentPath" : "WorkspaceInvoice[0]/SettlementRow[6]/PaymentDetails[0]/CardFields[1]/CardField1Layer[0]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 38.933, + "computedY" : 223.689, + "placementX" : 38.933, + "placementY" : 223.689, + "placementWidth" : 209.898, + "placementHeight" : 9.922, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 209.898, + "contentHeight" : 9.922, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/SettlementRow[6]/PaymentDetails[0]/CardFields[1]/CardField1Layer[0]/CardField1Holder[0]/CardField1[0]", + "entityName" : "CardField1", + "entityKind" : "RowNode", + "parentPath" : "WorkspaceInvoice[0]/SettlementRow[6]/PaymentDetails[0]/CardFields[1]/CardField1Layer[0]/CardField1Holder[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 38.933, + "computedY" : 223.689, + "placementX" : 38.933, + "placementY" : 223.689, + "placementWidth" : 209.898, + "placementHeight" : 9.922, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 209.898, + "contentHeight" : 9.922, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/SettlementRow[6]/PaymentDetails[0]/CardFields[1]/CardField1Layer[0]/CardField1Holder[0]/CardField1[0]/CardField1Label[0]", + "entityName" : "CardField1Label", + "entityKind" : "ParagraphNode", + "parentPath" : "WorkspaceInvoice[0]/SettlementRow[6]/PaymentDetails[0]/CardFields[1]/CardField1Layer[0]/CardField1Holder[0]/CardField1[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 38.933, + "computedY" : 223.689, + "placementX" : 38.933, + "placementY" : 223.689, + "placementWidth" : 44.056, + "placementHeight" : 9.922, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 44.056, + "contentHeight" : 9.922, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/SettlementRow[6]/PaymentDetails[0]/CardFields[1]/CardField1Layer[0]/CardField1Holder[0]/CardField1[0]/CardField1Value[1]", + "entityName" : "CardField1Value", + "entityKind" : "ParagraphNode", + "parentPath" : "WorkspaceInvoice[0]/SettlementRow[6]/PaymentDetails[0]/CardFields[1]/CardField1Layer[0]/CardField1Holder[0]/CardField1[0]", + "childIndex" : 1, + "depth" : 8, + "layer" : 8, + "computedX" : 130.34, + "computedY" : 223.689, + "placementX" : 130.34, + "placementY" : 223.689, + "placementWidth" : 78.032, + "placementHeight" : 9.922, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 78.032, + "contentHeight" : 9.922, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/SettlementRow[6]/PaymentDetails[0]/CardFields[1]/CardField2Layer[1]", + "entityName" : "CardField2Layer", + "entityKind" : "LayerStackNode", + "parentPath" : "WorkspaceInvoice[0]/SettlementRow[6]/PaymentDetails[0]/CardFields[1]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 38.933, + "computedY" : 211.05, + "placementX" : 38.933, + "placementY" : 211.05, + "placementWidth" : 209.898, + "placementHeight" : 9.922, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 209.898, + "contentHeight" : 9.922, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/SettlementRow[6]/PaymentDetails[0]/CardFields[1]/CardField2Layer[1]/CardField2Holder[0]", + "entityName" : "CardField2Holder", + "entityKind" : "SectionNode", + "parentPath" : "WorkspaceInvoice[0]/SettlementRow[6]/PaymentDetails[0]/CardFields[1]/CardField2Layer[1]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 38.933, + "computedY" : 211.05, + "placementX" : 38.933, + "placementY" : 211.05, + "placementWidth" : 209.898, + "placementHeight" : 9.922, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 209.898, + "contentHeight" : 9.922, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/SettlementRow[6]/PaymentDetails[0]/CardFields[1]/CardField2Layer[1]/CardField2Holder[0]/CardField2[0]", + "entityName" : "CardField2", + "entityKind" : "RowNode", + "parentPath" : "WorkspaceInvoice[0]/SettlementRow[6]/PaymentDetails[0]/CardFields[1]/CardField2Layer[1]/CardField2Holder[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 38.933, + "computedY" : 211.05, + "placementX" : 38.933, + "placementY" : 211.05, + "placementWidth" : 209.898, + "placementHeight" : 9.922, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 209.898, + "contentHeight" : 9.922, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/SettlementRow[6]/PaymentDetails[0]/CardFields[1]/CardField2Layer[1]/CardField2Holder[0]/CardField2[0]/CardField2Label[0]", + "entityName" : "CardField2Label", + "entityKind" : "ParagraphNode", + "parentPath" : "WorkspaceInvoice[0]/SettlementRow[6]/PaymentDetails[0]/CardFields[1]/CardField2Layer[1]/CardField2Holder[0]/CardField2[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 38.933, + "computedY" : 211.05, + "placementX" : 38.933, + "placementY" : 211.05, + "placementWidth" : 56.169, + "placementHeight" : 9.922, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 56.169, + "contentHeight" : 9.922, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/SettlementRow[6]/PaymentDetails[0]/CardFields[1]/CardField2Layer[1]/CardField2Holder[0]/CardField2[0]/CardField2Value[1]", + "entityName" : "CardField2Value", + "entityKind" : "ParagraphNode", + "parentPath" : "WorkspaceInvoice[0]/SettlementRow[6]/PaymentDetails[0]/CardFields[1]/CardField2Layer[1]/CardField2Holder[0]/CardField2[0]", + "childIndex" : 1, + "depth" : 8, + "layer" : 8, + "computedX" : 130.34, + "computedY" : 211.05, + "placementX" : 130.34, + "placementY" : 211.05, + "placementWidth" : 93.585, + "placementHeight" : 9.922, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 93.585, + "contentHeight" : 9.922, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/SettlementRow[6]/PaymentDetails[0]/CardFields[1]/CardField3Layer[2]", + "entityName" : "CardField3Layer", + "entityKind" : "LayerStackNode", + "parentPath" : "WorkspaceInvoice[0]/SettlementRow[6]/PaymentDetails[0]/CardFields[1]", + "childIndex" : 2, + "depth" : 5, + "layer" : 5, + "computedX" : 38.933, + "computedY" : 198.411, + "placementX" : 38.933, + "placementY" : 198.411, + "placementWidth" : 209.898, + "placementHeight" : 9.922, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 209.898, + "contentHeight" : 9.922, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/SettlementRow[6]/PaymentDetails[0]/CardFields[1]/CardField3Layer[2]/CardField3Holder[0]", + "entityName" : "CardField3Holder", + "entityKind" : "SectionNode", + "parentPath" : "WorkspaceInvoice[0]/SettlementRow[6]/PaymentDetails[0]/CardFields[1]/CardField3Layer[2]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 38.933, + "computedY" : 198.411, + "placementX" : 38.933, + "placementY" : 198.411, + "placementWidth" : 209.898, + "placementHeight" : 9.922, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 209.898, + "contentHeight" : 9.922, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/SettlementRow[6]/PaymentDetails[0]/CardFields[1]/CardField3Layer[2]/CardField3Holder[0]/CardField3[0]", + "entityName" : "CardField3", + "entityKind" : "RowNode", + "parentPath" : "WorkspaceInvoice[0]/SettlementRow[6]/PaymentDetails[0]/CardFields[1]/CardField3Layer[2]/CardField3Holder[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 38.933, + "computedY" : 198.411, + "placementX" : 38.933, + "placementY" : 198.411, + "placementWidth" : 209.898, + "placementHeight" : 9.922, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 209.898, + "contentHeight" : 9.922, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/SettlementRow[6]/PaymentDetails[0]/CardFields[1]/CardField3Layer[2]/CardField3Holder[0]/CardField3[0]/CardField3Label[0]", + "entityName" : "CardField3Label", + "entityKind" : "ParagraphNode", + "parentPath" : "WorkspaceInvoice[0]/SettlementRow[6]/PaymentDetails[0]/CardFields[1]/CardField3Layer[2]/CardField3Holder[0]/CardField3[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 38.933, + "computedY" : 198.411, + "placementX" : 38.933, + "placementY" : 198.411, + "placementWidth" : 64.314, + "placementHeight" : 9.922, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 64.314, + "contentHeight" : 9.922, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/SettlementRow[6]/PaymentDetails[0]/CardFields[1]/CardField3Layer[2]/CardField3Holder[0]/CardField3[0]/CardField3Value[1]", + "entityName" : "CardField3Value", + "entityKind" : "ParagraphNode", + "parentPath" : "WorkspaceInvoice[0]/SettlementRow[6]/PaymentDetails[0]/CardFields[1]/CardField3Layer[2]/CardField3Holder[0]/CardField3[0]", + "childIndex" : 1, + "depth" : 8, + "layer" : 8, + "computedX" : 130.34, + "computedY" : 198.411, + "placementX" : 130.34, + "placementY" : 198.411, + "placementWidth" : 71.938, + "placementHeight" : 9.922, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 71.938, + "contentHeight" : 9.922, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/SettlementRow[6]/PaymentDetails[0]/CardFields[1]/CardField4Layer[3]", + "entityName" : "CardField4Layer", + "entityKind" : "LayerStackNode", + "parentPath" : "WorkspaceInvoice[0]/SettlementRow[6]/PaymentDetails[0]/CardFields[1]", + "childIndex" : 3, + "depth" : 5, + "layer" : 5, + "computedX" : 38.933, + "computedY" : 185.772, + "placementX" : 38.933, + "placementY" : 185.772, + "placementWidth" : 209.898, + "placementHeight" : 9.922, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 209.898, + "contentHeight" : 9.922, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/SettlementRow[6]/PaymentDetails[0]/CardFields[1]/CardField4Layer[3]/CardField4Holder[0]", + "entityName" : "CardField4Holder", + "entityKind" : "SectionNode", + "parentPath" : "WorkspaceInvoice[0]/SettlementRow[6]/PaymentDetails[0]/CardFields[1]/CardField4Layer[3]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 38.933, + "computedY" : 185.772, + "placementX" : 38.933, + "placementY" : 185.772, + "placementWidth" : 209.898, + "placementHeight" : 9.922, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 209.898, + "contentHeight" : 9.922, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/SettlementRow[6]/PaymentDetails[0]/CardFields[1]/CardField4Layer[3]/CardField4Holder[0]/CardField4[0]", + "entityName" : "CardField4", + "entityKind" : "RowNode", + "parentPath" : "WorkspaceInvoice[0]/SettlementRow[6]/PaymentDetails[0]/CardFields[1]/CardField4Layer[3]/CardField4Holder[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 38.933, + "computedY" : 185.772, + "placementX" : 38.933, + "placementY" : 185.772, + "placementWidth" : 209.898, + "placementHeight" : 9.922, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 209.898, + "contentHeight" : 9.922, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/SettlementRow[6]/PaymentDetails[0]/CardFields[1]/CardField4Layer[3]/CardField4Holder[0]/CardField4[0]/CardField4Label[0]", + "entityName" : "CardField4Label", + "entityKind" : "ParagraphNode", + "parentPath" : "WorkspaceInvoice[0]/SettlementRow[6]/PaymentDetails[0]/CardFields[1]/CardField4Layer[3]/CardField4Holder[0]/CardField4[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 38.933, + "computedY" : 185.772, + "placementX" : 38.933, + "placementY" : 185.772, + "placementWidth" : 53.499, + "placementHeight" : 9.922, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 53.499, + "contentHeight" : 9.922, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/SettlementRow[6]/PaymentDetails[0]/CardFields[1]/CardField4Layer[3]/CardField4Holder[0]/CardField4[0]/CardField4Value[1]", + "entityName" : "CardField4Value", + "entityKind" : "ParagraphNode", + "parentPath" : "WorkspaceInvoice[0]/SettlementRow[6]/PaymentDetails[0]/CardFields[1]/CardField4Layer[3]/CardField4Holder[0]/CardField4[0]", + "childIndex" : 1, + "depth" : 8, + "layer" : 8, + "computedX" : 130.34, + "computedY" : 185.772, + "placementX" : 130.34, + "placementY" : 185.772, + "placementWidth" : 43.163, + "placementHeight" : 9.922, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 43.163, + "contentHeight" : 9.922, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/SettlementRow[6]/PaymentDetails[0]/CardFields[1]/CardField5Layer[4]", + "entityName" : "CardField5Layer", + "entityKind" : "LayerStackNode", + "parentPath" : "WorkspaceInvoice[0]/SettlementRow[6]/PaymentDetails[0]/CardFields[1]", + "childIndex" : 4, + "depth" : 5, + "layer" : 5, + "computedX" : 38.933, + "computedY" : 173.133, + "placementX" : 38.933, + "placementY" : 173.133, + "placementWidth" : 209.898, + "placementHeight" : 9.922, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 209.898, + "contentHeight" : 9.922, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/SettlementRow[6]/PaymentDetails[0]/CardFields[1]/CardField5Layer[4]/CardField5Holder[0]", + "entityName" : "CardField5Holder", + "entityKind" : "SectionNode", + "parentPath" : "WorkspaceInvoice[0]/SettlementRow[6]/PaymentDetails[0]/CardFields[1]/CardField5Layer[4]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 38.933, + "computedY" : 173.133, + "placementX" : 38.933, + "placementY" : 173.133, + "placementWidth" : 209.898, + "placementHeight" : 9.922, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 209.898, + "contentHeight" : 9.922, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/SettlementRow[6]/PaymentDetails[0]/CardFields[1]/CardField5Layer[4]/CardField5Holder[0]/CardField5[0]", + "entityName" : "CardField5", + "entityKind" : "RowNode", + "parentPath" : "WorkspaceInvoice[0]/SettlementRow[6]/PaymentDetails[0]/CardFields[1]/CardField5Layer[4]/CardField5Holder[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 38.933, + "computedY" : 173.133, + "placementX" : 38.933, + "placementY" : 173.133, + "placementWidth" : 209.898, + "placementHeight" : 9.922, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 209.898, + "contentHeight" : 9.922, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/SettlementRow[6]/PaymentDetails[0]/CardFields[1]/CardField5Layer[4]/CardField5Holder[0]/CardField5[0]/CardField5Label[0]", + "entityName" : "CardField5Label", + "entityKind" : "ParagraphNode", + "parentPath" : "WorkspaceInvoice[0]/SettlementRow[6]/PaymentDetails[0]/CardFields[1]/CardField5Layer[4]/CardField5Holder[0]/CardField5[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 38.933, + "computedY" : 173.133, + "placementX" : 38.933, + "placementY" : 173.133, + "placementWidth" : 48.165, + "placementHeight" : 9.922, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 48.165, + "contentHeight" : 9.922, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/SettlementRow[6]/PaymentDetails[0]/CardFields[1]/CardField5Layer[4]/CardField5Holder[0]/CardField5[0]/CardField5Value[1]", + "entityName" : "CardField5Value", + "entityKind" : "ParagraphNode", + "parentPath" : "WorkspaceInvoice[0]/SettlementRow[6]/PaymentDetails[0]/CardFields[1]/CardField5Layer[4]/CardField5Holder[0]/CardField5[0]", + "childIndex" : 1, + "depth" : 8, + "layer" : 8, + "computedX" : 130.34, + "computedY" : 173.133, + "placementX" : 130.34, + "placementY" : 173.133, + "placementWidth" : 42.361, + "placementHeight" : 9.922, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 42.361, + "contentHeight" : 9.922, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/SettlementRow[6]/PaymentDetails[0]/CardFields[1]/CardField6Layer[5]", + "entityName" : "CardField6Layer", + "entityKind" : "LayerStackNode", + "parentPath" : "WorkspaceInvoice[0]/SettlementRow[6]/PaymentDetails[0]/CardFields[1]", + "childIndex" : 5, + "depth" : 5, + "layer" : 5, + "computedX" : 38.933, + "computedY" : 160.494, + "placementX" : 38.933, + "placementY" : 160.494, + "placementWidth" : 209.898, + "placementHeight" : 9.922, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 209.898, + "contentHeight" : 9.922, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/SettlementRow[6]/PaymentDetails[0]/CardFields[1]/CardField6Layer[5]/CardField6Holder[0]", + "entityName" : "CardField6Holder", + "entityKind" : "SectionNode", + "parentPath" : "WorkspaceInvoice[0]/SettlementRow[6]/PaymentDetails[0]/CardFields[1]/CardField6Layer[5]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 38.933, + "computedY" : 160.494, + "placementX" : 38.933, + "placementY" : 160.494, + "placementWidth" : 209.898, + "placementHeight" : 9.922, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 209.898, + "contentHeight" : 9.922, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/SettlementRow[6]/PaymentDetails[0]/CardFields[1]/CardField6Layer[5]/CardField6Holder[0]/CardField6[0]", + "entityName" : "CardField6", + "entityKind" : "RowNode", + "parentPath" : "WorkspaceInvoice[0]/SettlementRow[6]/PaymentDetails[0]/CardFields[1]/CardField6Layer[5]/CardField6Holder[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 38.933, + "computedY" : 160.494, + "placementX" : 38.933, + "placementY" : 160.494, + "placementWidth" : 209.898, + "placementHeight" : 9.922, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 209.898, + "contentHeight" : 9.922, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/SettlementRow[6]/PaymentDetails[0]/CardFields[1]/CardField6Layer[5]/CardField6Holder[0]/CardField6[0]/CardField6Label[0]", + "entityName" : "CardField6Label", + "entityKind" : "ParagraphNode", + "parentPath" : "WorkspaceInvoice[0]/SettlementRow[6]/PaymentDetails[0]/CardFields[1]/CardField6Layer[5]/CardField6Holder[0]/CardField6[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 38.933, + "computedY" : 160.494, + "placementX" : 38.933, + "placementY" : 160.494, + "placementWidth" : 35.423, + "placementHeight" : 9.922, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 35.423, + "contentHeight" : 9.922, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/SettlementRow[6]/PaymentDetails[0]/CardFields[1]/CardField6Layer[5]/CardField6Holder[0]/CardField6[0]/CardField6Value[1]", + "entityName" : "CardField6Value", + "entityKind" : "ParagraphNode", + "parentPath" : "WorkspaceInvoice[0]/SettlementRow[6]/PaymentDetails[0]/CardFields[1]/CardField6Layer[5]/CardField6Holder[0]/CardField6[0]", + "childIndex" : 1, + "depth" : 8, + "layer" : 8, + "computedX" : 130.34, + "computedY" : 160.494, + "placementX" : 130.34, + "placementY" : 160.494, + "placementWidth" : 16.868, + "placementHeight" : 9.922, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 16.868, + "contentHeight" : 9.922, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/SettlementRow[6]/PaymentDetails[0]/CardFields[1]/CardField7Layer[6]", + "entityName" : "CardField7Layer", + "entityKind" : "LayerStackNode", + "parentPath" : "WorkspaceInvoice[0]/SettlementRow[6]/PaymentDetails[0]/CardFields[1]", + "childIndex" : 6, + "depth" : 5, + "layer" : 5, + "computedX" : 38.933, + "computedY" : 147.855, + "placementX" : 38.933, + "placementY" : 147.855, + "placementWidth" : 209.898, + "placementHeight" : 9.922, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 209.898, + "contentHeight" : 9.922, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/SettlementRow[6]/PaymentDetails[0]/CardFields[1]/CardField7Layer[6]/CardField7Holder[0]", + "entityName" : "CardField7Holder", + "entityKind" : "SectionNode", + "parentPath" : "WorkspaceInvoice[0]/SettlementRow[6]/PaymentDetails[0]/CardFields[1]/CardField7Layer[6]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 38.933, + "computedY" : 147.855, + "placementX" : 38.933, + "placementY" : 147.855, + "placementWidth" : 209.898, + "placementHeight" : 9.922, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 209.898, + "contentHeight" : 9.922, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/SettlementRow[6]/PaymentDetails[0]/CardFields[1]/CardField7Layer[6]/CardField7Holder[0]/CardField7[0]", + "entityName" : "CardField7", + "entityKind" : "RowNode", + "parentPath" : "WorkspaceInvoice[0]/SettlementRow[6]/PaymentDetails[0]/CardFields[1]/CardField7Layer[6]/CardField7Holder[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 38.933, + "computedY" : 147.855, + "placementX" : 38.933, + "placementY" : 147.855, + "placementWidth" : 209.898, + "placementHeight" : 9.922, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 209.898, + "contentHeight" : 9.922, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/SettlementRow[6]/PaymentDetails[0]/CardFields[1]/CardField7Layer[6]/CardField7Holder[0]/CardField7[0]/CardField7Label[0]", + "entityName" : "CardField7Label", + "entityKind" : "ParagraphNode", + "parentPath" : "WorkspaceInvoice[0]/SettlementRow[6]/PaymentDetails[0]/CardFields[1]/CardField7Layer[6]/CardField7Holder[0]/CardField7[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 38.933, + "computedY" : 147.855, + "placementX" : 38.933, + "placementY" : 147.855, + "placementWidth" : 39.194, + "placementHeight" : 9.922, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 39.194, + "contentHeight" : 9.922, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/SettlementRow[6]/PaymentDetails[0]/CardFields[1]/CardField7Layer[6]/CardField7Holder[0]/CardField7[0]/CardField7Value[1]", + "entityName" : "CardField7Value", + "entityKind" : "ParagraphNode", + "parentPath" : "WorkspaceInvoice[0]/SettlementRow[6]/PaymentDetails[0]/CardFields[1]/CardField7Layer[6]/CardField7Holder[0]/CardField7[0]", + "childIndex" : 1, + "depth" : 8, + "layer" : 8, + "computedX" : 130.34, + "computedY" : 147.855, + "placementX" : 130.34, + "placementY" : 147.855, + "placementWidth" : 73.137, + "placementHeight" : 9.922, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 73.137, + "contentHeight" : 9.922, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/SettlementRow[6]/PaymentDetails[0]/CardFields[1]/CardField8Layer[7]", + "entityName" : "CardField8Layer", + "entityKind" : "LayerStackNode", + "parentPath" : "WorkspaceInvoice[0]/SettlementRow[6]/PaymentDetails[0]/CardFields[1]", + "childIndex" : 7, + "depth" : 5, + "layer" : 5, + "computedX" : 38.933, + "computedY" : 135.216, + "placementX" : 38.933, + "placementY" : 135.216, + "placementWidth" : 209.898, + "placementHeight" : 9.922, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 209.898, + "contentHeight" : 9.922, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/SettlementRow[6]/PaymentDetails[0]/CardFields[1]/CardField8Layer[7]/CardField8Holder[0]", + "entityName" : "CardField8Holder", + "entityKind" : "SectionNode", + "parentPath" : "WorkspaceInvoice[0]/SettlementRow[6]/PaymentDetails[0]/CardFields[1]/CardField8Layer[7]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 38.933, + "computedY" : 135.216, + "placementX" : 38.933, + "placementY" : 135.216, + "placementWidth" : 209.898, + "placementHeight" : 9.922, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 209.898, + "contentHeight" : 9.922, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/SettlementRow[6]/PaymentDetails[0]/CardFields[1]/CardField8Layer[7]/CardField8Holder[0]/CardField8[0]", + "entityName" : "CardField8", + "entityKind" : "RowNode", + "parentPath" : "WorkspaceInvoice[0]/SettlementRow[6]/PaymentDetails[0]/CardFields[1]/CardField8Layer[7]/CardField8Holder[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 38.933, + "computedY" : 135.216, + "placementX" : 38.933, + "placementY" : 135.216, + "placementWidth" : 209.898, + "placementHeight" : 9.922, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 209.898, + "contentHeight" : 9.922, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/SettlementRow[6]/PaymentDetails[0]/CardFields[1]/CardField8Layer[7]/CardField8Holder[0]/CardField8[0]/CardField8Label[0]", + "entityName" : "CardField8Label", + "entityKind" : "ParagraphNode", + "parentPath" : "WorkspaceInvoice[0]/SettlementRow[6]/PaymentDetails[0]/CardFields[1]/CardField8Layer[7]/CardField8Holder[0]/CardField8[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 38.933, + "computedY" : 135.216, + "placementX" : 38.933, + "placementY" : 135.216, + "placementWidth" : 51.853, + "placementHeight" : 9.922, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 51.853, + "contentHeight" : 9.922, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/SettlementRow[6]/PaymentDetails[0]/CardFields[1]/CardField8Layer[7]/CardField8Holder[0]/CardField8[0]/CardField8Value[1]", + "entityName" : "CardField8Value", + "entityKind" : "ParagraphNode", + "parentPath" : "WorkspaceInvoice[0]/SettlementRow[6]/PaymentDetails[0]/CardFields[1]/CardField8Layer[7]/CardField8Holder[0]/CardField8[0]", + "childIndex" : 1, + "depth" : 8, + "layer" : 8, + "computedX" : 130.34, + "computedY" : 135.216, + "placementX" : 130.34, + "placementY" : 135.216, + "placementWidth" : 108.138, + "placementHeight" : 9.922, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 108.138, + "contentHeight" : 9.922, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/SettlementRow[6]/PaymentDetails[0]/CardRule[2]", + "entityName" : "CardRule", + "entityKind" : "LineNode", + "parentPath" : "WorkspaceInvoice[0]/SettlementRow[6]/PaymentDetails[0]", + "childIndex" : 2, + "depth" : 4, + "layer" : 4, + "computedX" : 38.933, + "computedY" : 131.161, + "placementX" : 38.933, + "placementY" : 131.161, + "placementWidth" : 209.898, + "placementHeight" : 1.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 209.898, + "contentHeight" : 1.0, + "margin" : { + "top" : 3.055, + "right" : 0.0, + "bottom" : 9.028, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/SettlementRow[6]/PaymentDetails[0]/CardNoteLayer[3]", + "entityName" : "CardNoteLayer", + "entityKind" : "LayerStackNode", + "parentPath" : "WorkspaceInvoice[0]/SettlementRow[6]/PaymentDetails[0]", + "childIndex" : 3, + "depth" : 4, + "layer" : 4, + "computedX" : 38.933, + "computedY" : 99.797, + "placementX" : 38.933, + "placementY" : 99.797, + "placementWidth" : 209.898, + "placementHeight" : 22.336, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 209.898, + "contentHeight" : 22.336, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/SettlementRow[6]/PaymentDetails[0]/CardNoteLayer[3]/CardNoteHolder[0]", + "entityName" : "CardNoteHolder", + "entityKind" : "SectionNode", + "parentPath" : "WorkspaceInvoice[0]/SettlementRow[6]/PaymentDetails[0]/CardNoteLayer[3]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 38.933, + "computedY" : 99.797, + "placementX" : 38.933, + "placementY" : 99.797, + "placementWidth" : 209.898, + "placementHeight" : 22.336, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 209.898, + "contentHeight" : 22.336, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/SettlementRow[6]/PaymentDetails[0]/CardNoteLayer[3]/CardNoteHolder[0]/CardNote[0]", + "entityName" : "CardNote", + "entityKind" : "RowNode", + "parentPath" : "WorkspaceInvoice[0]/SettlementRow[6]/PaymentDetails[0]/CardNoteLayer[3]/CardNoteHolder[0]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 38.933, + "computedY" : 99.797, + "placementX" : 38.933, + "placementY" : 99.797, + "placementWidth" : 209.898, + "placementHeight" : 22.336, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 209.898, + "contentHeight" : 22.336, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/SettlementRow[6]/PaymentDetails[0]/CardNoteLayer[3]/CardNoteHolder[0]/CardNote[0]/CardNoteIcon[0]", + "entityName" : "CardNoteIcon", + "entityKind" : "SectionNode", + "parentPath" : "WorkspaceInvoice[0]/SettlementRow[6]/PaymentDetails[0]/CardNoteLayer[3]/CardNoteHolder[0]/CardNote[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 38.933, + "computedY" : 110.848, + "placementX" : 38.933, + "placementY" : 110.848, + "placementWidth" : 16.927, + "placementHeight" : 11.285, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 16.927, + "contentHeight" : 11.285, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/SettlementRow[6]/PaymentDetails[0]/CardNoteLayer[3]/CardNoteHolder[0]/CardNote[0]/CardNoteIcon[0]/Glyphinfo[0]", + "entityName" : "Glyphinfo", + "entityKind" : "SectionNode", + "parentPath" : "WorkspaceInvoice[0]/SettlementRow[6]/PaymentDetails[0]/CardNoteLayer[3]/CardNoteHolder[0]/CardNote[0]/CardNoteIcon[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 38.933, + "computedY" : 110.848, + "placementX" : 38.933, + "placementY" : 110.848, + "placementWidth" : 16.927, + "placementHeight" : 11.285, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 16.927, + "contentHeight" : 11.285, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/SettlementRow[6]/PaymentDetails[0]/CardNoteLayer[3]/CardNoteHolder[0]/CardNote[0]/CardNoteIcon[0]/Glyphinfo[0]/Align[0]", + "entityName" : null, + "entityKind" : "Align", + "parentPath" : "WorkspaceInvoice[0]/SettlementRow[6]/PaymentDetails[0]/CardNoteLayer[3]/CardNoteHolder[0]/CardNote[0]/CardNoteIcon[0]/Glyphinfo[0]", + "childIndex" : 0, + "depth" : 9, + "layer" : 9, + "computedX" : 38.933, + "computedY" : 110.848, + "placementX" : 38.933, + "placementY" : 110.848, + "placementWidth" : 16.927, + "placementHeight" : 11.285, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 16.927, + "contentHeight" : 11.285, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/SettlementRow[6]/PaymentDetails[0]/CardNoteLayer[3]/CardNoteHolder[0]/CardNote[0]/CardNoteIcon[0]/Glyphinfo[0]/Align[0]/SvgIcon[0]", + "entityName" : "SvgIcon", + "entityKind" : "LayerStackNode", + "parentPath" : "WorkspaceInvoice[0]/SettlementRow[6]/PaymentDetails[0]/CardNoteLayer[3]/CardNoteHolder[0]/CardNote[0]/CardNoteIcon[0]/Glyphinfo[0]/Align[0]", + "childIndex" : 0, + "depth" : 10, + "layer" : 10, + "computedX" : 38.933, + "computedY" : 110.848, + "placementX" : 38.933, + "placementY" : 110.848, + "placementWidth" : 11.285, + "placementHeight" : 11.285, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 11.285, + "contentHeight" : 11.285, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/SettlementRow[6]/PaymentDetails[0]/CardNoteLayer[3]/CardNoteHolder[0]/CardNote[0]/CardNoteIcon[0]/Glyphinfo[0]/Align[0]/SvgIcon[0]/SvgLayer0[0]", + "entityName" : "SvgLayer0", + "entityKind" : "Path", + "parentPath" : "WorkspaceInvoice[0]/SettlementRow[6]/PaymentDetails[0]/CardNoteLayer[3]/CardNoteHolder[0]/CardNote[0]/CardNoteIcon[0]/Glyphinfo[0]/Align[0]/SvgIcon[0]", + "childIndex" : 0, + "depth" : 11, + "layer" : 11, + "computedX" : 38.933, + "computedY" : 110.848, + "placementX" : 38.933, + "placementY" : 110.848, + "placementWidth" : 11.285, + "placementHeight" : 11.285, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 11.285, + "contentHeight" : 11.285, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/SettlementRow[6]/PaymentDetails[0]/CardNoteLayer[3]/CardNoteHolder[0]/CardNote[0]/CardNoteText[1]", + "entityName" : "CardNoteText", + "entityKind" : "ParagraphNode", + "parentPath" : "WorkspaceInvoice[0]/SettlementRow[6]/PaymentDetails[0]/CardNoteLayer[3]/CardNoteHolder[0]/CardNote[0]", + "childIndex" : 1, + "depth" : 7, + "layer" : 7, + "computedX" : 55.86, + "computedY" : 99.797, + "placementX" : 55.86, + "placementY" : 99.797, + "placementWidth" : 185.062, + "placementHeight" : 22.336, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 185.062, + "contentHeight" : 22.336, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/SettlementRow[6]/Settlement[1]", + "entityName" : "Settlement", + "entityKind" : "SectionNode", + "parentPath" : "WorkspaceInvoice[0]/SettlementRow[6]", + "childIndex" : 1, + "depth" : 3, + "layer" : 3, + "computedX" : 324.439, + "computedY" : 104.36, + "placementX" : 324.439, + "placementY" : 104.36, + "placementWidth" : 244.317, + "placementHeight" : 160.284, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 244.317, + "contentHeight" : 160.284, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 16.363, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/SettlementRow[6]/Settlement[1]/Totals[0]", + "entityName" : "Totals", + "entityKind" : "SectionNode", + "parentPath" : "WorkspaceInvoice[0]/SettlementRow[6]/Settlement[1]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 324.439, + "computedY" : 228.217, + "placementX" : 324.439, + "placementY" : 228.217, + "placementWidth" : 227.954, + "placementHeight" : 30.616, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 227.954, + "contentHeight" : 30.616, + "margin" : { + "top" : 5.812, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 5.642, + "bottom" : 0.0, + "left" : 3.385 + } + }, { + "path" : "WorkspaceInvoice[0]/SettlementRow[6]/Settlement[1]/Totals[0]/Total1Layer[0]", + "entityName" : "Total1Layer", + "entityKind" : "LayerStackNode", + "parentPath" : "WorkspaceInvoice[0]/SettlementRow[6]/Settlement[1]/Totals[0]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 327.825, + "computedY" : 247.966, + "placementX" : 327.825, + "placementY" : 247.966, + "placementWidth" : 218.926, + "placementHeight" : 10.867, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 218.926, + "contentHeight" : 10.867, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/SettlementRow[6]/Settlement[1]/Totals[0]/Total1Layer[0]/Total1Holder[0]", + "entityName" : "Total1Holder", + "entityKind" : "SectionNode", + "parentPath" : "WorkspaceInvoice[0]/SettlementRow[6]/Settlement[1]/Totals[0]/Total1Layer[0]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 327.825, + "computedY" : 247.966, + "placementX" : 327.825, + "placementY" : 247.966, + "placementWidth" : 218.926, + "placementHeight" : 10.867, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 218.926, + "contentHeight" : 10.867, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/SettlementRow[6]/Settlement[1]/Totals[0]/Total1Layer[0]/Total1Holder[0]/Total1[0]", + "entityName" : "Total1", + "entityKind" : "RowNode", + "parentPath" : "WorkspaceInvoice[0]/SettlementRow[6]/Settlement[1]/Totals[0]/Total1Layer[0]/Total1Holder[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 327.825, + "computedY" : 247.966, + "placementX" : 327.825, + "placementY" : 247.966, + "placementWidth" : 218.926, + "placementHeight" : 10.867, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 218.926, + "contentHeight" : 10.867, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/SettlementRow[6]/Settlement[1]/Totals[0]/Total1Layer[0]/Total1Holder[0]/Total1[0]/Total1Label[0]", + "entityName" : "Total1Label", + "entityKind" : "ParagraphNode", + "parentPath" : "WorkspaceInvoice[0]/SettlementRow[6]/Settlement[1]/Totals[0]/Total1Layer[0]/Total1Holder[0]/Total1[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 327.825, + "computedY" : 247.966, + "placementX" : 327.825, + "placementY" : 247.966, + "placementWidth" : 33.318, + "placementHeight" : 10.867, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 33.318, + "contentHeight" : 10.867, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/SettlementRow[6]/Settlement[1]/Totals[0]/Total1Layer[0]/Total1Holder[0]/Total1[0]/Total1Value[1]", + "entityName" : "Total1Value", + "entityKind" : "ParagraphNode", + "parentPath" : "WorkspaceInvoice[0]/SettlementRow[6]/Settlement[1]/Totals[0]/Total1Layer[0]/Total1Holder[0]/Total1[0]", + "childIndex" : 1, + "depth" : 8, + "layer" : 8, + "computedX" : 437.288, + "computedY" : 247.966, + "placementX" : 437.288, + "placementY" : 247.966, + "placementWidth" : 109.463, + "placementHeight" : 10.867, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 109.463, + "contentHeight" : 10.867, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/SettlementRow[6]/Settlement[1]/Totals[0]/Total2Layer[1]", + "entityName" : "Total2Layer", + "entityKind" : "LayerStackNode", + "parentPath" : "WorkspaceInvoice[0]/SettlementRow[6]/Settlement[1]/Totals[0]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 327.825, + "computedY" : 228.217, + "placementX" : 327.825, + "placementY" : 228.217, + "placementWidth" : 218.926, + "placementHeight" : 10.867, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 218.926, + "contentHeight" : 10.867, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/SettlementRow[6]/Settlement[1]/Totals[0]/Total2Layer[1]/Total2Holder[0]", + "entityName" : "Total2Holder", + "entityKind" : "SectionNode", + "parentPath" : "WorkspaceInvoice[0]/SettlementRow[6]/Settlement[1]/Totals[0]/Total2Layer[1]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 327.825, + "computedY" : 228.217, + "placementX" : 327.825, + "placementY" : 228.217, + "placementWidth" : 218.926, + "placementHeight" : 10.867, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 218.926, + "contentHeight" : 10.867, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/SettlementRow[6]/Settlement[1]/Totals[0]/Total2Layer[1]/Total2Holder[0]/Total2[0]", + "entityName" : "Total2", + "entityKind" : "RowNode", + "parentPath" : "WorkspaceInvoice[0]/SettlementRow[6]/Settlement[1]/Totals[0]/Total2Layer[1]/Total2Holder[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 327.825, + "computedY" : 228.217, + "placementX" : 327.825, + "placementY" : 228.217, + "placementWidth" : 218.926, + "placementHeight" : 10.867, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 218.926, + "contentHeight" : 10.867, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/SettlementRow[6]/Settlement[1]/Totals[0]/Total2Layer[1]/Total2Holder[0]/Total2[0]/Total2Label[0]", + "entityName" : "Total2Label", + "entityKind" : "ParagraphNode", + "parentPath" : "WorkspaceInvoice[0]/SettlementRow[6]/Settlement[1]/Totals[0]/Total2Layer[1]/Total2Holder[0]/Total2[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 327.825, + "computedY" : 228.217, + "placementX" : 327.825, + "placementY" : 228.217, + "placementWidth" : 34.033, + "placementHeight" : 10.867, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 34.033, + "contentHeight" : 10.867, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/SettlementRow[6]/Settlement[1]/Totals[0]/Total2Layer[1]/Total2Holder[0]/Total2[0]/Total2Value[1]", + "entityName" : "Total2Value", + "entityKind" : "ParagraphNode", + "parentPath" : "WorkspaceInvoice[0]/SettlementRow[6]/Settlement[1]/Totals[0]/Total2Layer[1]/Total2Holder[0]/Total2[0]", + "childIndex" : 1, + "depth" : 8, + "layer" : 8, + "computedX" : 437.288, + "computedY" : 228.217, + "placementX" : 437.288, + "placementY" : 228.217, + "placementWidth" : 109.463, + "placementHeight" : 10.867, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 109.463, + "contentHeight" : 10.867, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/SettlementRow[6]/Settlement[1]/TotalsRule[1]", + "entityName" : "TotalsRule", + "entityKind" : "LineNode", + "parentPath" : "WorkspaceInvoice[0]/SettlementRow[6]/Settlement[1]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 327.825, + "computedY" : 218.336, + "placementX" : 327.825, + "placementY" : 218.336, + "placementWidth" : 224.004, + "placementHeight" : 1.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 224.004, + "contentHeight" : 1.0, + "margin" : { + "top" : 8.881, + "right" : 0.0, + "bottom" : 13.711, + "left" : 3.385 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/SettlementRow[6]/Settlement[1]/TotalDue[2]", + "entityName" : "TotalDue", + "entityKind" : "SectionNode", + "parentPath" : "WorkspaceInvoice[0]/SettlementRow[6]/Settlement[1]", + "childIndex" : 2, + "depth" : 4, + "layer" : 4, + "computedX" : 324.439, + "computedY" : 187.143, + "placementX" : 324.439, + "placementY" : 187.143, + "placementWidth" : 227.954, + "placementHeight" : 17.482, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 227.954, + "contentHeight" : 17.482, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 5.642, + "bottom" : 0.0, + "left" : 3.385 + } + }, { + "path" : "WorkspaceInvoice[0]/SettlementRow[6]/Settlement[1]/TotalDue[2]/TotalDueRowLayer[0]", + "entityName" : "TotalDueRowLayer", + "entityKind" : "LayerStackNode", + "parentPath" : "WorkspaceInvoice[0]/SettlementRow[6]/Settlement[1]/TotalDue[2]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 327.825, + "computedY" : 187.143, + "placementX" : 327.825, + "placementY" : 187.143, + "placementWidth" : 218.926, + "placementHeight" : 17.482, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 218.926, + "contentHeight" : 17.482, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/SettlementRow[6]/Settlement[1]/TotalDue[2]/TotalDueRowLayer[0]/TotalDueRowHolder[0]", + "entityName" : "TotalDueRowHolder", + "entityKind" : "SectionNode", + "parentPath" : "WorkspaceInvoice[0]/SettlementRow[6]/Settlement[1]/TotalDue[2]/TotalDueRowLayer[0]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 327.825, + "computedY" : 187.143, + "placementX" : 327.825, + "placementY" : 187.143, + "placementWidth" : 218.926, + "placementHeight" : 17.482, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 218.926, + "contentHeight" : 17.482, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/SettlementRow[6]/Settlement[1]/TotalDue[2]/TotalDueRowLayer[0]/TotalDueRowHolder[0]/TotalDueRow[0]", + "entityName" : "TotalDueRow", + "entityKind" : "RowNode", + "parentPath" : "WorkspaceInvoice[0]/SettlementRow[6]/Settlement[1]/TotalDue[2]/TotalDueRowLayer[0]/TotalDueRowHolder[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 327.825, + "computedY" : 187.143, + "placementX" : 327.825, + "placementY" : 187.143, + "placementWidth" : 218.926, + "placementHeight" : 17.482, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 218.926, + "contentHeight" : 17.482, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/SettlementRow[6]/Settlement[1]/TotalDue[2]/TotalDueRowLayer[0]/TotalDueRowHolder[0]/TotalDueRow[0]/TotalDueLabel[0]", + "entityName" : "TotalDueLabel", + "entityKind" : "ParagraphNode", + "parentPath" : "WorkspaceInvoice[0]/SettlementRow[6]/Settlement[1]/TotalDue[2]/TotalDueRowLayer[0]/TotalDueRowHolder[0]/TotalDueRow[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 327.825, + "computedY" : 189.444, + "placementX" : 327.825, + "placementY" : 189.444, + "placementWidth" : 59.518, + "placementHeight" : 12.88, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 59.518, + "contentHeight" : 12.88, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/SettlementRow[6]/Settlement[1]/TotalDue[2]/TotalDueRowLayer[0]/TotalDueRowHolder[0]/TotalDueRow[0]/TotalDueValue[1]", + "entityName" : "TotalDueValue", + "entityKind" : "ParagraphNode", + "parentPath" : "WorkspaceInvoice[0]/SettlementRow[6]/Settlement[1]/TotalDue[2]/TotalDueRowLayer[0]/TotalDueRowHolder[0]/TotalDueRow[0]", + "childIndex" : 1, + "depth" : 8, + "layer" : 8, + "computedX" : 437.288, + "computedY" : 187.143, + "placementX" : 437.288, + "placementY" : 187.143, + "placementWidth" : 109.463, + "placementHeight" : 17.482, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 109.463, + "contentHeight" : 17.482, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/SettlementRow[6]/Settlement[1]/PaymentDuePanel[3]", + "entityName" : "PaymentDuePanel", + "entityKind" : "SectionNode", + "parentPath" : "WorkspaceInvoice[0]/SettlementRow[6]/Settlement[1]", + "childIndex" : 3, + "depth" : 4, + "layer" : 4, + "computedX" : 324.439, + "computedY" : 104.36, + "placementX" : 324.439, + "placementY" : 104.36, + "placementWidth" : 227.954, + "placementHeight" : 58.689, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 227.954, + "contentHeight" : 58.689, + "margin" : { + "top" : 24.093, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 14.106, + "right" : 13.542, + "bottom" : 15.799, + "left" : 12.978 + } + }, { + "path" : "WorkspaceInvoice[0]/SettlementRow[6]/Settlement[1]/PaymentDuePanel[3]/PaymentDueRowLayer[0]", + "entityName" : "PaymentDueRowLayer", + "entityKind" : "LayerStackNode", + "parentPath" : "WorkspaceInvoice[0]/SettlementRow[6]/Settlement[1]/PaymentDuePanel[3]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 337.417, + "computedY" : 120.159, + "placementX" : 337.417, + "placementY" : 120.159, + "placementWidth" : 201.435, + "placementHeight" : 28.784, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 201.435, + "contentHeight" : 28.784, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/SettlementRow[6]/Settlement[1]/PaymentDuePanel[3]/PaymentDueRowLayer[0]/PaymentDueRowHolder[0]", + "entityName" : "PaymentDueRowHolder", + "entityKind" : "SectionNode", + "parentPath" : "WorkspaceInvoice[0]/SettlementRow[6]/Settlement[1]/PaymentDuePanel[3]/PaymentDueRowLayer[0]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 337.417, + "computedY" : 120.159, + "placementX" : 337.417, + "placementY" : 120.159, + "placementWidth" : 201.435, + "placementHeight" : 28.784, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 201.435, + "contentHeight" : 28.784, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/SettlementRow[6]/Settlement[1]/PaymentDuePanel[3]/PaymentDueRowLayer[0]/PaymentDueRowHolder[0]/PaymentDueRow[0]", + "entityName" : "PaymentDueRow", + "entityKind" : "RowNode", + "parentPath" : "WorkspaceInvoice[0]/SettlementRow[6]/Settlement[1]/PaymentDuePanel[3]/PaymentDueRowLayer[0]/PaymentDueRowHolder[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 337.417, + "computedY" : 120.159, + "placementX" : 337.417, + "placementY" : 120.159, + "placementWidth" : 201.435, + "placementHeight" : 28.784, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 201.435, + "contentHeight" : 28.784, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/SettlementRow[6]/Settlement[1]/PaymentDuePanel[3]/PaymentDueRowLayer[0]/PaymentDueRowHolder[0]/PaymentDueRow[0]/DueTile[0]", + "entityName" : "DueTile", + "entityKind" : "SectionNode", + "parentPath" : "WorkspaceInvoice[0]/SettlementRow[6]/Settlement[1]/PaymentDuePanel[3]/PaymentDueRowLayer[0]/PaymentDueRowHolder[0]/PaymentDueRow[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 337.417, + "computedY" : 127.502, + "placementX" : 337.417, + "placementY" : 127.502, + "placementWidth" : 21.441, + "placementHeight" : 21.441, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 21.441, + "contentHeight" : 21.441, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/SettlementRow[6]/Settlement[1]/PaymentDuePanel[3]/PaymentDueRowLayer[0]/PaymentDueRowHolder[0]/PaymentDueRow[0]/DueTile[0]/DueTileShape[0]", + "entityName" : "DueTileShape", + "entityKind" : "ShapeContainerNode", + "parentPath" : "WorkspaceInvoice[0]/SettlementRow[6]/Settlement[1]/PaymentDuePanel[3]/PaymentDueRowLayer[0]/PaymentDueRowHolder[0]/PaymentDueRow[0]/DueTile[0]", + "childIndex" : 0, + "depth" : 9, + "layer" : 9, + "computedX" : 337.417, + "computedY" : 127.502, + "placementX" : 337.417, + "placementY" : 127.502, + "placementWidth" : 21.441, + "placementHeight" : 21.441, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 21.441, + "contentHeight" : 21.441, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/SettlementRow[6]/Settlement[1]/PaymentDuePanel[3]/PaymentDueRowLayer[0]/PaymentDueRowHolder[0]/PaymentDueRow[0]/DueTile[0]/DueTileShape[0]/Glyphcalendar[0]", + "entityName" : "Glyphcalendar", + "entityKind" : "SectionNode", + "parentPath" : "WorkspaceInvoice[0]/SettlementRow[6]/Settlement[1]/PaymentDuePanel[3]/PaymentDueRowLayer[0]/PaymentDueRowHolder[0]/PaymentDueRow[0]/DueTile[0]/DueTileShape[0]", + "childIndex" : 0, + "depth" : 10, + "layer" : 10, + "computedX" : 337.417, + "computedY" : 132.016, + "placementX" : 337.417, + "placementY" : 132.016, + "placementWidth" : 21.441, + "placementHeight" : 12.413, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 21.441, + "contentHeight" : 12.413, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/SettlementRow[6]/Settlement[1]/PaymentDuePanel[3]/PaymentDueRowLayer[0]/PaymentDueRowHolder[0]/PaymentDueRow[0]/DueTile[0]/DueTileShape[0]/Glyphcalendar[0]/Align[0]", + "entityName" : null, + "entityKind" : "Align", + "parentPath" : "WorkspaceInvoice[0]/SettlementRow[6]/Settlement[1]/PaymentDuePanel[3]/PaymentDueRowLayer[0]/PaymentDueRowHolder[0]/PaymentDueRow[0]/DueTile[0]/DueTileShape[0]/Glyphcalendar[0]", + "childIndex" : 0, + "depth" : 11, + "layer" : 11, + "computedX" : 337.417, + "computedY" : 132.016, + "placementX" : 337.417, + "placementY" : 132.016, + "placementWidth" : 21.441, + "placementHeight" : 12.413, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 21.441, + "contentHeight" : 12.413, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/SettlementRow[6]/Settlement[1]/PaymentDuePanel[3]/PaymentDueRowLayer[0]/PaymentDueRowHolder[0]/PaymentDueRow[0]/DueTile[0]/DueTileShape[0]/Glyphcalendar[0]/Align[0]/SvgIcon[0]", + "entityName" : "SvgIcon", + "entityKind" : "LayerStackNode", + "parentPath" : "WorkspaceInvoice[0]/SettlementRow[6]/Settlement[1]/PaymentDuePanel[3]/PaymentDueRowLayer[0]/PaymentDueRowHolder[0]/PaymentDueRow[0]/DueTile[0]/DueTileShape[0]/Glyphcalendar[0]/Align[0]", + "childIndex" : 0, + "depth" : 12, + "layer" : 12, + "computedX" : 341.931, + "computedY" : 132.016, + "placementX" : 341.931, + "placementY" : 132.016, + "placementWidth" : 12.413, + "placementHeight" : 12.413, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 12.413, + "contentHeight" : 12.413, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/SettlementRow[6]/Settlement[1]/PaymentDuePanel[3]/PaymentDueRowLayer[0]/PaymentDueRowHolder[0]/PaymentDueRow[0]/DueTile[0]/DueTileShape[0]/Glyphcalendar[0]/Align[0]/SvgIcon[0]/SvgLayer0[0]", + "entityName" : "SvgLayer0", + "entityKind" : "Path", + "parentPath" : "WorkspaceInvoice[0]/SettlementRow[6]/Settlement[1]/PaymentDuePanel[3]/PaymentDueRowLayer[0]/PaymentDueRowHolder[0]/PaymentDueRow[0]/DueTile[0]/DueTileShape[0]/Glyphcalendar[0]/Align[0]/SvgIcon[0]", + "childIndex" : 0, + "depth" : 13, + "layer" : 13, + "computedX" : 341.931, + "computedY" : 132.016, + "placementX" : 341.931, + "placementY" : 132.016, + "placementWidth" : 12.413, + "placementHeight" : 12.413, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 12.413, + "contentHeight" : 12.413, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/SettlementRow[6]/Settlement[1]/PaymentDuePanel[3]/PaymentDueRowLayer[0]/PaymentDueRowHolder[0]/PaymentDueRow[0]/DueText[1]", + "entityName" : "DueText", + "entityKind" : "SectionNode", + "parentPath" : "WorkspaceInvoice[0]/SettlementRow[6]/Settlement[1]/PaymentDuePanel[3]/PaymentDueRowLayer[0]/PaymentDueRowHolder[0]/PaymentDueRow[0]", + "childIndex" : 1, + "depth" : 8, + "layer" : 8, + "computedX" : 372.4, + "computedY" : 120.159, + "placementX" : 372.4, + "placementY" : 120.159, + "placementWidth" : 73.945, + "placementHeight" : 27.964, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 73.945, + "contentHeight" : 27.964, + "margin" : { + "top" : 0.821, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/SettlementRow[6]/Settlement[1]/PaymentDuePanel[3]/PaymentDueRowLayer[0]/PaymentDueRowHolder[0]/PaymentDueRow[0]/DueText[1]/DueLabel[0]", + "entityName" : "DueLabel", + "entityKind" : "ParagraphNode", + "parentPath" : "WorkspaceInvoice[0]/SettlementRow[6]/Settlement[1]/PaymentDuePanel[3]/PaymentDueRowLayer[0]/PaymentDueRowHolder[0]/PaymentDueRow[0]/DueText[1]", + "childIndex" : 0, + "depth" : 9, + "layer" : 9, + "computedX" : 372.4, + "computedY" : 137.907, + "placementX" : 372.4, + "placementY" : 137.907, + "placementWidth" : 73.398, + "placementHeight" : 10.215, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 73.398, + "contentHeight" : 10.215, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/SettlementRow[6]/Settlement[1]/PaymentDuePanel[3]/PaymentDueRowLayer[0]/PaymentDueRowHolder[0]/PaymentDueRow[0]/DueText[1]/DueValue[1]", + "entityName" : "DueValue", + "entityKind" : "ParagraphNode", + "parentPath" : "WorkspaceInvoice[0]/SettlementRow[6]/Settlement[1]/PaymentDuePanel[3]/PaymentDueRowLayer[0]/PaymentDueRowHolder[0]/PaymentDueRow[0]/DueText[1]", + "childIndex" : 1, + "depth" : 9, + "layer" : 9, + "computedX" : 372.4, + "computedY" : 120.159, + "placementX" : 372.4, + "placementY" : 120.159, + "placementWidth" : 73.945, + "placementHeight" : 14.647, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 73.945, + "contentHeight" : 14.647, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/ClosingRule[7]", + "entityName" : "ClosingRule", + "entityKind" : "LineNode", + "parentPath" : "WorkspaceInvoice[0]", + "childIndex" : 7, + "depth" : 2, + "layer" : 2, + "computedX" : 25.391, + "computedY" : 84.465, + "placementX" : 25.391, + "placementY" : 84.465, + "placementWidth" : 543.365, + "placementHeight" : 1.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 543.365, + "contentHeight" : 1.0, + "margin" : { + "top" : 1.918, + "right" : 0.0, + "bottom" : 8.294, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/ThankYou[8]", + "entityName" : "ThankYou", + "entityKind" : "SectionNode", + "parentPath" : "WorkspaceInvoice[0]", + "childIndex" : 8, + "depth" : 2, + "layer" : 2, + "computedX" : 25.391, + "computedY" : 53.652, + "placementX" : 25.391, + "placementY" : 53.652, + "placementWidth" : 543.365, + "placementHeight" : 22.519, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 543.365, + "contentHeight" : 22.519, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 3.385 + } + }, { + "path" : "WorkspaceInvoice[0]/ThankYou[8]/ThankYouRow[0]", + "entityName" : "ThankYouRow", + "entityKind" : "RowNode", + "parentPath" : "WorkspaceInvoice[0]/ThankYou[8]", + "childIndex" : 0, + "depth" : 3, + "layer" : 3, + "computedX" : 28.776, + "computedY" : 53.652, + "placementX" : 28.776, + "placementY" : 53.652, + "placementWidth" : 539.98, + "placementHeight" : 22.519, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 539.98, + "contentHeight" : 22.519, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/ThankYou[8]/ThankYouRow[0]/ThankYouIcon[0]", + "entityName" : "ThankYouIcon", + "entityKind" : "SectionNode", + "parentPath" : "WorkspaceInvoice[0]/ThankYou[8]/ThankYouRow[0]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 28.776, + "computedY" : 59.244, + "placementX" : 28.776, + "placementY" : 59.244, + "placementWidth" : 25.955, + "placementHeight" : 16.927, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 25.955, + "contentHeight" : 16.927, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/ThankYou[8]/ThankYouRow[0]/ThankYouIcon[0]/Glyphinfo[0]", + "entityName" : "Glyphinfo", + "entityKind" : "SectionNode", + "parentPath" : "WorkspaceInvoice[0]/ThankYou[8]/ThankYouRow[0]/ThankYouIcon[0]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 28.776, + "computedY" : 59.244, + "placementX" : 28.776, + "placementY" : 59.244, + "placementWidth" : 25.955, + "placementHeight" : 16.927, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 25.955, + "contentHeight" : 16.927, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/ThankYou[8]/ThankYouRow[0]/ThankYouIcon[0]/Glyphinfo[0]/Align[0]", + "entityName" : null, + "entityKind" : "Align", + "parentPath" : "WorkspaceInvoice[0]/ThankYou[8]/ThankYouRow[0]/ThankYouIcon[0]/Glyphinfo[0]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 28.776, + "computedY" : 59.244, + "placementX" : 28.776, + "placementY" : 59.244, + "placementWidth" : 25.955, + "placementHeight" : 16.927, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 25.955, + "contentHeight" : 16.927, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/ThankYou[8]/ThankYouRow[0]/ThankYouIcon[0]/Glyphinfo[0]/Align[0]/SvgIcon[0]", + "entityName" : "SvgIcon", + "entityKind" : "LayerStackNode", + "parentPath" : "WorkspaceInvoice[0]/ThankYou[8]/ThankYouRow[0]/ThankYouIcon[0]/Glyphinfo[0]/Align[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 28.776, + "computedY" : 59.244, + "placementX" : 28.776, + "placementY" : 59.244, + "placementWidth" : 16.927, + "placementHeight" : 16.927, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 16.927, + "contentHeight" : 16.927, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/ThankYou[8]/ThankYouRow[0]/ThankYouIcon[0]/Glyphinfo[0]/Align[0]/SvgIcon[0]/SvgLayer0[0]", + "entityName" : "SvgLayer0", + "entityKind" : "Path", + "parentPath" : "WorkspaceInvoice[0]/ThankYou[8]/ThankYouRow[0]/ThankYouIcon[0]/Glyphinfo[0]/Align[0]/SvgIcon[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 28.776, + "computedY" : 59.244, + "placementX" : 28.776, + "placementY" : 59.244, + "placementWidth" : 16.927, + "placementHeight" : 16.927, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 16.927, + "contentHeight" : 16.927, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/ThankYou[8]/ThankYouRow[0]/ThankYouText[1]", + "entityName" : "ThankYouText", + "entityKind" : "SectionNode", + "parentPath" : "WorkspaceInvoice[0]/ThankYou[8]/ThankYouRow[0]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 54.732, + "computedY" : 53.652, + "placementX" : 54.732, + "placementY" : 53.652, + "placementWidth" : 389.069, + "placementHeight" : 22.519, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 389.069, + "contentHeight" : 22.519, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/ThankYou[8]/ThankYouRow[0]/ThankYouText[1]/ThankYouTitle[0]", + "entityName" : "ThankYouTitle", + "entityKind" : "ParagraphNode", + "parentPath" : "WorkspaceInvoice[0]/ThankYou[8]/ThankYouRow[0]/ThankYouText[1]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 54.732, + "computedY" : 65.303, + "placementX" : 54.732, + "placementY" : 65.303, + "placementWidth" : 113.139, + "placementHeight" : 10.867, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 113.139, + "contentHeight" : 10.867, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/ThankYou[8]/ThankYouRow[0]/ThankYouText[1]/ThankYouBody[1]", + "entityName" : "ThankYouBody", + "entityKind" : "ParagraphNode", + "parentPath" : "WorkspaceInvoice[0]/ThankYou[8]/ThankYouRow[0]/ThankYouText[1]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 54.732, + "computedY" : 53.652, + "placementX" : 54.732, + "placementY" : 53.652, + "placementWidth" : 389.069, + "placementHeight" : 8.977, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 389.069, + "contentHeight" : 8.977, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/FooterRule[9]", + "entityName" : "FooterRule", + "entityKind" : "LineNode", + "parentPath" : "WorkspaceInvoice[0]", + "childIndex" : 9, + "depth" : 2, + "layer" : 2, + "computedX" : 25.391, + "computedY" : 44.552, + "placementX" : 25.391, + "placementY" : 44.552, + "placementWidth" : 543.365, + "placementHeight" : 1.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 543.365, + "contentHeight" : 1.0, + "margin" : { + "top" : 8.1, + "right" : 0.0, + "bottom" : 2.514, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/DocumentFooter[10]", + "entityName" : "DocumentFooter", + "entityKind" : "RowNode", + "parentPath" : "WorkspaceInvoice[0]", + "childIndex" : 10, + "depth" : 2, + "layer" : 2, + "computedX" : 25.391, + "computedY" : 18.885, + "placementX" : 25.391, + "placementY" : 18.885, + "placementWidth" : 543.365, + "placementHeight" : 23.152, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 543.365, + "contentHeight" : 23.152, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/DocumentFooter[10]/FooterWordmark[0]", + "entityName" : "FooterWordmark", + "entityKind" : "ParagraphNode", + "parentPath" : "WorkspaceInvoice[0]/DocumentFooter[10]", + "childIndex" : 0, + "depth" : 3, + "layer" : 3, + "computedX" : 25.391, + "computedY" : 18.885, + "placementX" : 25.391, + "placementY" : 18.885, + "placementWidth" : 58.595, + "placementHeight" : 23.152, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 58.595, + "contentHeight" : 23.152, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/DocumentFooter[10]/FooterAddress[1]", + "entityName" : "FooterAddress", + "entityKind" : "SectionNode", + "parentPath" : "WorkspaceInvoice[0]/DocumentFooter[10]", + "childIndex" : 1, + "depth" : 3, + "layer" : 3, + "computedX" : 106.642, + "computedY" : 19.011, + "placementX" : 106.642, + "placementY" : 19.011, + "placementWidth" : 255.901, + "placementHeight" : 22.9, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 255.901, + "contentHeight" : 22.9, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/DocumentFooter[10]/FooterAddress[1]/FooterName[0]", + "entityName" : "FooterName", + "entityKind" : "ParagraphNode", + "parentPath" : "WorkspaceInvoice[0]/DocumentFooter[10]/FooterAddress[1]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 106.642, + "computedY" : 31.989, + "placementX" : 106.642, + "placementY" : 31.989, + "placementWidth" : 95.264, + "placementHeight" : 9.922, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 95.264, + "contentHeight" : 9.922, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/DocumentFooter[10]/FooterAddress[1]/FooterAddressLine[1]", + "entityName" : "FooterAddressLine", + "entityKind" : "ParagraphNode", + "parentPath" : "WorkspaceInvoice[0]/DocumentFooter[10]/FooterAddress[1]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 106.642, + "computedY" : 19.011, + "placementX" : 106.642, + "placementY" : 19.011, + "placementWidth" : 255.901, + "placementHeight" : 9.922, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 255.901, + "contentHeight" : 9.922, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "WorkspaceInvoice[0]/DocumentFooter[10]/FooterSite[2]", + "entityName" : "FooterSite", + "entityKind" : "ParagraphNode", + "parentPath" : "WorkspaceInvoice[0]/DocumentFooter[10]", + "childIndex" : 2, + "depth" : 3, + "layer" : 3, + "computedX" : 484.12, + "computedY" : 25.5, + "placementX" : 484.12, + "placementY" : 25.5, + "placementWidth" : 82.944, + "placementHeight" : 9.922, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 82.944, + "contentHeight" : 9.922, + "margin" : { + "top" : 0.0, + "right" : 1.693, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + } ] +} diff --git a/qa/src/test/resources/layout-snapshots/canonical-templates/proposal/editorial_proposal_layout.json b/qa/src/test/resources/layout-snapshots/canonical-templates/proposal/editorial_proposal_layout.json new file mode 100644 index 000000000..59abca9ef --- /dev/null +++ b/qa/src/test/resources/layout-snapshots/canonical-templates/proposal/editorial_proposal_layout.json @@ -0,0 +1,5567 @@ +{ + "formatVersion" : "2.0", + "canvas" : { + "pageWidth" : 595.276, + "pageHeight" : 841.89, + "innerWidth" : 535.276, + "innerHeight" : 773.19, + "margin" : { + "top" : 19.7, + "right" : 30.0, + "bottom" : 49.0, + "left" : 30.0 + } + }, + "totalPages" : 2, + "nodes" : [ { + "path" : "Proposal[0]", + "entityName" : "Proposal", + "entityKind" : "ContainerNode", + "parentPath" : null, + "childIndex" : 0, + "depth" : 1, + "layer" : 1, + "computedX" : 30.0, + "computedY" : -673.275, + "placementX" : 30.0, + "placementY" : -673.275, + "placementWidth" : 535.276, + "placementHeight" : 1495.465, + "startPage" : 0, + "endPage" : 1, + "contentWidth" : 535.276, + "contentHeight" : 1495.465, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/Masthead[0]", + "entityName" : "Masthead", + "entityKind" : "SectionNode", + "parentPath" : "Proposal[0]", + "childIndex" : 0, + "depth" : 2, + "layer" : 2, + "computedX" : 30.0, + "computedY" : 775.788, + "placementX" : 30.0, + "placementY" : 775.788, + "placementWidth" : 535.276, + "placementHeight" : 46.402, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 535.276, + "contentHeight" : 46.402, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/Masthead[0]/MastheadRow[0]", + "entityName" : "MastheadRow", + "entityKind" : "RowNode", + "parentPath" : "Proposal[0]/Masthead[0]", + "childIndex" : 0, + "depth" : 3, + "layer" : 3, + "computedX" : 30.0, + "computedY" : 792.888, + "placementX" : 30.0, + "placementY" : 792.888, + "placementWidth" : 535.276, + "placementHeight" : 29.302, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 535.276, + "contentHeight" : 29.302, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/Masthead[0]/MastheadRow[0]/LogoMark[0]", + "entityName" : "LogoMark", + "entityKind" : "ShapeContainerNode", + "parentPath" : "Proposal[0]/Masthead[0]/MastheadRow[0]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 30.0, + "computedY" : 794.539, + "placementX" : 30.0, + "placementY" : 794.539, + "placementWidth" : 30.0, + "placementHeight" : 26.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 30.0, + "contentHeight" : 26.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/Masthead[0]/MastheadRow[0]/LogoMark[0]/MarkStem[0]", + "entityName" : "MarkStem", + "entityKind" : "Path", + "parentPath" : "Proposal[0]/Masthead[0]/MastheadRow[0]/LogoMark[0]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 30.0, + "computedY" : 794.539, + "placementX" : 30.0, + "placementY" : 794.539, + "placementWidth" : 30.0, + "placementHeight" : 26.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 30.0, + "contentHeight" : 26.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/Masthead[0]/MastheadRow[0]/LogoMark[0]/MarkDiagonal[1]", + "entityName" : "MarkDiagonal", + "entityKind" : "Path", + "parentPath" : "Proposal[0]/Masthead[0]/MastheadRow[0]/LogoMark[0]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 30.0, + "computedY" : 794.539, + "placementX" : 30.0, + "placementY" : 794.539, + "placementWidth" : 30.0, + "placementHeight" : 26.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 30.0, + "contentHeight" : 26.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/Masthead[0]/MastheadRow[0]/LogoMark[0]/MarkSlash[2]", + "entityName" : "MarkSlash", + "entityKind" : "Path", + "parentPath" : "Proposal[0]/Masthead[0]/MastheadRow[0]/LogoMark[0]", + "childIndex" : 2, + "depth" : 5, + "layer" : 5, + "computedX" : 30.0, + "computedY" : 794.539, + "placementX" : 30.0, + "placementY" : 794.539, + "placementWidth" : 30.0, + "placementHeight" : 26.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 30.0, + "contentHeight" : 26.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/Masthead[0]/MastheadRow[0]/LogoMark[0]/MarkRightStem[3]", + "entityName" : "MarkRightStem", + "entityKind" : "Path", + "parentPath" : "Proposal[0]/Masthead[0]/MastheadRow[0]/LogoMark[0]", + "childIndex" : 3, + "depth" : 5, + "layer" : 5, + "computedX" : 30.0, + "computedY" : 794.539, + "placementX" : 30.0, + "placementY" : 794.539, + "placementWidth" : 30.0, + "placementHeight" : 26.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 30.0, + "contentHeight" : 26.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/Masthead[0]/MastheadRow[0]/Wordmark[1]", + "entityName" : "Wordmark", + "entityKind" : "ShapeContainerNode", + "parentPath" : "Proposal[0]/Masthead[0]/MastheadRow[0]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 69.0, + "computedY" : 792.888, + "placementX" : 69.0, + "placementY" : 792.888, + "placementWidth" : 101.702, + "placementHeight" : 29.302, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 101.702, + "contentHeight" : 29.302, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/Masthead[0]/MastheadRow[0]/Wordmark[1]/WordmarkLine1[0]", + "entityName" : "WordmarkLine1", + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/Masthead[0]/MastheadRow[0]/Wordmark[1]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 69.0, + "computedY" : 802.87, + "placementX" : 69.0, + "placementY" : 802.87, + "placementWidth" : 92.672, + "placementHeight" : 19.32, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 92.672, + "contentHeight" : 19.32, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/Masthead[0]/MastheadRow[0]/Wordmark[1]/WordmarkLine2[1]", + "entityName" : "WordmarkLine2", + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/Masthead[0]/MastheadRow[0]/Wordmark[1]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 69.0, + "computedY" : 792.188, + "placementX" : 69.0, + "placementY" : 792.188, + "placementWidth" : 52.612, + "placementHeight" : 16.8, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 52.612, + "contentHeight" : 16.8, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/Masthead[0]/MastheadRow[0]/SpacerNode[2]", + "entityName" : null, + "entityKind" : "SpacerNode", + "parentPath" : "Proposal[0]/Masthead[0]/MastheadRow[0]", + "childIndex" : 2, + "depth" : 4, + "layer" : 4, + "computedX" : 179.702, + "computedY" : 807.539, + "placementX" : 179.702, + "placementY" : 807.539, + "placementWidth" : 0.0, + "placementHeight" : 0.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 0.0, + "contentHeight" : 0.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/Masthead[0]/MastheadRow[0]/DocumentLabel[3]", + "entityName" : "DocumentLabel", + "entityKind" : "SectionNode", + "parentPath" : "Proposal[0]/Masthead[0]/MastheadRow[0]", + "childIndex" : 3, + "depth" : 4, + "layer" : 4, + "computedX" : 495.69, + "computedY" : 799.259, + "placementX" : 495.69, + "placementY" : 799.259, + "placementWidth" : 69.586, + "placementHeight" : 16.56, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 69.586, + "contentHeight" : 16.56, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 8.5, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/Masthead[0]/MastheadRow[0]/DocumentLabel[3]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/Masthead[0]/MastheadRow[0]/DocumentLabel[3]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 495.69, + "computedY" : 805.859, + "placementX" : 495.69, + "placementY" : 805.859, + "placementWidth" : 61.086, + "placementHeight" : 9.96, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 61.086, + "contentHeight" : 9.96, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/Masthead[0]/MastheadRow[0]/DocumentLabel[3]/Align[1]", + "entityName" : null, + "entityKind" : "Align", + "parentPath" : "Proposal[0]/Masthead[0]/MastheadRow[0]/DocumentLabel[3]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 495.69, + "computedY" : 799.259, + "placementX" : 495.69, + "placementY" : 799.259, + "placementWidth" : 61.086, + "placementHeight" : 6.6, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 61.086, + "contentHeight" : 6.6, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/Masthead[0]/MastheadRow[0]/DocumentLabel[3]/Align[1]/DocumentLabelRule[0]", + "entityName" : "DocumentLabelRule", + "entityKind" : "LineNode", + "parentPath" : "Proposal[0]/Masthead[0]/MastheadRow[0]/DocumentLabel[3]/Align[1]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 495.69, + "computedY" : 799.259, + "placementX" : 495.69, + "placementY" : 799.259, + "placementWidth" : 61.557, + "placementHeight" : 1.6, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 61.557, + "contentHeight" : 1.6, + "margin" : { + "top" : 5.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/Masthead[0]/LineNode[1]", + "entityName" : null, + "entityKind" : "LineNode", + "parentPath" : "Proposal[0]/Masthead[0]", + "childIndex" : 1, + "depth" : 3, + "layer" : 3, + "computedX" : 30.0, + "computedY" : 775.788, + "placementX" : 30.0, + "placementY" : 775.788, + "placementWidth" : 535.276, + "placementHeight" : 1.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 535.276, + "contentHeight" : 1.0, + "margin" : { + "top" : 16.1, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/TitleBlock[1]", + "entityName" : "TitleBlock", + "entityKind" : "SectionNode", + "parentPath" : "Proposal[0]", + "childIndex" : 1, + "depth" : 2, + "layer" : 2, + "computedX" : 30.0, + "computedY" : 634.688, + "placementX" : 30.0, + "placementY" : 634.688, + "placementWidth" : 535.276, + "placementHeight" : 125.6, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 535.276, + "contentHeight" : 125.6, + "margin" : { + "top" : 15.5, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/TitleBlock[1]/TitleLines[0]", + "entityName" : "TitleLines", + "entityKind" : "ShapeContainerNode", + "parentPath" : "Proposal[0]/TitleBlock[1]", + "childIndex" : 0, + "depth" : 3, + "layer" : 3, + "computedX" : 30.0, + "computedY" : 634.688, + "placementX" : 30.0, + "placementY" : 634.688, + "placementWidth" : 535.276, + "placementHeight" : 125.6, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 535.276, + "contentHeight" : 125.6, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/TitleBlock[1]/TitleLines[0]/TitleLinesLine1[0]", + "entityName" : "TitleLinesLine1", + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/TitleBlock[1]/TitleLines[0]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 30.0, + "computedY" : 696.668, + "placementX" : 30.0, + "placementY" : 696.668, + "placementWidth" : 222.376, + "placementHeight" : 63.62, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 222.376, + "contentHeight" : 63.62, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/TitleBlock[1]/TitleLines[0]/TitleLinesLine2[1]", + "entityName" : "TitleLinesLine2", + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/TitleBlock[1]/TitleLines[0]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 30.0, + "computedY" : 654.768, + "placementX" : 30.0, + "placementY" : 654.768, + "placementWidth" : 321.108, + "placementHeight" : 63.62, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 321.108, + "contentHeight" : 63.62, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/TitleBlock[1]/TitleLines[0]/TitleLinesLine3[2]", + "entityName" : "TitleLinesLine3", + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/TitleBlock[1]/TitleLines[0]", + "childIndex" : 2, + "depth" : 4, + "layer" : 4, + "computedX" : 30.0, + "computedY" : 612.868, + "placementX" : 30.0, + "placementY" : 612.868, + "placementWidth" : 344.975, + "placementHeight" : 63.62, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 344.975, + "contentHeight" : 63.62, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/TitleMeta[2]", + "entityName" : "TitleMeta", + "entityKind" : "SectionNode", + "parentPath" : "Proposal[0]", + "childIndex" : 2, + "depth" : 2, + "layer" : 2, + "computedX" : 30.0, + "computedY" : 606.088, + "placementX" : 30.0, + "placementY" : 606.088, + "placementWidth" : 476.889, + "placementHeight" : 12.6, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 476.889, + "contentHeight" : 12.6, + "margin" : { + "top" : 16.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/TitleMeta[2]/MetaLine[0]", + "entityName" : "MetaLine", + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/TitleMeta[2]", + "childIndex" : 0, + "depth" : 3, + "layer" : 3, + "computedX" : 30.0, + "computedY" : 606.088, + "placementX" : 30.0, + "placementY" : 606.088, + "placementWidth" : 476.889, + "placementHeight" : 12.6, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 476.889, + "contentHeight" : 12.6, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/SummaryBand[3]", + "entityName" : "SummaryBand", + "entityKind" : "RowNode", + "parentPath" : "Proposal[0]", + "childIndex" : 3, + "depth" : 2, + "layer" : 2, + "computedX" : 30.0, + "computedY" : 359.121, + "placementX" : 30.0, + "placementY" : 359.121, + "placementWidth" : 535.276, + "placementHeight" : 229.167, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 535.276, + "contentHeight" : 229.167, + "margin" : { + "top" : 17.8, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/SummaryBand[3]/SummaryColumn[0]", + "entityName" : "SummaryColumn", + "entityKind" : "SectionNode", + "parentPath" : "Proposal[0]/SummaryBand[3]", + "childIndex" : 0, + "depth" : 3, + "layer" : 3, + "computedX" : 30.0, + "computedY" : 359.121, + "placementX" : 30.0, + "placementY" : 359.121, + "placementWidth" : 318.63, + "placementHeight" : 229.167, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 318.63, + "contentHeight" : 229.167, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/SummaryBand[3]/SummaryColumn[0]/SectionHeading[0]", + "entityName" : "SectionHeading", + "entityKind" : "SectionNode", + "parentPath" : "Proposal[0]/SummaryBand[3]/SummaryColumn[0]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 30.0, + "computedY" : 560.879, + "placementX" : 30.0, + "placementY" : 560.879, + "placementWidth" : 148.672, + "placementHeight" : 27.409, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 148.672, + "contentHeight" : 27.409, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/SummaryBand[3]/SummaryColumn[0]/SectionHeading[0]/SectionHeadingTitle[0]", + "entityName" : "SectionHeadingTitle", + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/SummaryBand[3]/SummaryColumn[0]/SectionHeading[0]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 30.0, + "computedY" : 563.479, + "placementX" : 30.0, + "placementY" : 563.479, + "placementWidth" : 148.672, + "placementHeight" : 24.809, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 148.672, + "contentHeight" : 24.809, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/SummaryBand[3]/SummaryColumn[0]/SectionHeading[0]/SectionHeadingRule[1]", + "entityName" : "SectionHeadingRule", + "entityKind" : "LineNode", + "parentPath" : "Proposal[0]/SummaryBand[3]/SummaryColumn[0]/SectionHeading[0]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 30.0, + "computedY" : 560.879, + "placementX" : 30.0, + "placementY" : 560.879, + "placementWidth" : 25.693, + "placementHeight" : 1.6, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 25.693, + "contentHeight" : 1.6, + "margin" : { + "top" : 1.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/SummaryBand[3]/SummaryColumn[0]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/SummaryBand[3]/SummaryColumn[0]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 30.0, + "computedY" : 479.179, + "placementX" : 30.0, + "placementY" : 479.179, + "placementWidth" : 318.63, + "placementHeight" : 72.2, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 318.63, + "contentHeight" : 72.2, + "margin" : { + "top" : 9.5, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/SummaryBand[3]/SummaryColumn[0]/ParagraphNode[2]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/SummaryBand[3]/SummaryColumn[0]", + "childIndex" : 2, + "depth" : 4, + "layer" : 4, + "computedX" : 30.0, + "computedY" : 413.029, + "placementX" : 30.0, + "placementY" : 413.029, + "placementWidth" : 314.24, + "placementHeight" : 57.15, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 314.24, + "contentHeight" : 57.15, + "margin" : { + "top" : 9.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/SummaryBand[3]/SummaryColumn[0]/SpacerNode[3]", + "entityName" : null, + "entityKind" : "SpacerNode", + "parentPath" : "Proposal[0]/SummaryBand[3]/SummaryColumn[0]", + "childIndex" : 3, + "depth" : 4, + "layer" : 4, + "computedX" : 30.0, + "computedY" : 386.529, + "placementX" : 30.0, + "placementY" : 386.529, + "placementWidth" : 0.0, + "placementHeight" : 26.5, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 0.0, + "contentHeight" : 26.5, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/SummaryBand[3]/SummaryColumn[0]/SectionHeading[4]", + "entityName" : "SectionHeading", + "entityKind" : "SectionNode", + "parentPath" : "Proposal[0]/SummaryBand[3]/SummaryColumn[0]", + "childIndex" : 4, + "depth" : 4, + "layer" : 4, + "computedX" : 30.0, + "computedY" : 359.121, + "placementX" : 30.0, + "placementY" : 359.121, + "placementWidth" : 100.783, + "placementHeight" : 27.409, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 100.783, + "contentHeight" : 27.409, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/SummaryBand[3]/SummaryColumn[0]/SectionHeading[4]/SectionHeadingTitle[0]", + "entityName" : "SectionHeadingTitle", + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/SummaryBand[3]/SummaryColumn[0]/SectionHeading[4]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 30.0, + "computedY" : 361.721, + "placementX" : 30.0, + "placementY" : 361.721, + "placementWidth" : 100.783, + "placementHeight" : 24.809, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 100.783, + "contentHeight" : 24.809, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/SummaryBand[3]/SummaryColumn[0]/SectionHeading[4]/SectionHeadingRule[1]", + "entityName" : "SectionHeadingRule", + "entityKind" : "LineNode", + "parentPath" : "Proposal[0]/SummaryBand[3]/SummaryColumn[0]/SectionHeading[4]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 30.0, + "computedY" : 359.121, + "placementX" : 30.0, + "placementY" : 359.121, + "placementWidth" : 25.693, + "placementHeight" : 1.6, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 25.693, + "contentHeight" : 1.6, + "margin" : { + "top" : 1.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/SummaryBand[3]/GlanceColumn[1]", + "entityName" : "GlanceColumn", + "entityKind" : "SectionNode", + "parentPath" : "Proposal[0]/SummaryBand[3]", + "childIndex" : 1, + "depth" : 3, + "layer" : 3, + "computedX" : 384.353, + "computedY" : 361.97, + "placementX" : 384.353, + "placementY" : 361.97, + "placementWidth" : 180.923, + "placementHeight" : 226.318, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 180.923, + "contentHeight" : 226.318, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 5.246, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/SummaryBand[3]/GlanceColumn[1]/GlancePanel[0]", + "entityName" : "GlancePanel", + "entityKind" : "SectionNode", + "parentPath" : "Proposal[0]/SummaryBand[3]/GlanceColumn[1]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 384.353, + "computedY" : 361.97, + "placementX" : 384.353, + "placementY" : 361.97, + "placementWidth" : 180.923, + "placementHeight" : 221.072, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 180.923, + "contentHeight" : 221.072, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 17.0, + "right" : 17.0, + "bottom" : 17.0, + "left" : 17.0 + } + }, { + "path" : "Proposal[0]/SummaryBand[3]/GlanceColumn[1]/GlancePanel[0]/GlanceFact[0]", + "entityName" : "GlanceFact", + "entityKind" : "ShapeContainerNode", + "parentPath" : "Proposal[0]/SummaryBand[3]/GlanceColumn[1]/GlancePanel[0]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 401.353, + "computedY" : 533.863, + "placementX" : 401.353, + "placementY" : 533.863, + "placementWidth" : 146.923, + "placementHeight" : 32.18, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 146.923, + "contentHeight" : 32.18, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/SummaryBand[3]/GlanceColumn[1]/GlancePanel[0]/GlanceFact[0]/Icon-fact-duration[0]", + "entityName" : "Icon-fact-duration", + "entityKind" : "ShapeContainerNode", + "parentPath" : "Proposal[0]/SummaryBand[3]/GlanceColumn[1]/GlancePanel[0]/GlanceFact[0]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 401.353, + "computedY" : 539.952, + "placementX" : 401.353, + "placementY" : 539.952, + "placementWidth" : 20.0, + "placementHeight" : 20.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 20.0, + "contentHeight" : 20.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/SummaryBand[3]/GlanceColumn[1]/GlancePanel[0]/GlanceFact[0]/Icon-fact-duration[0]/SvgIcon[0]", + "entityName" : "SvgIcon", + "entityKind" : "LayerStackNode", + "parentPath" : "Proposal[0]/SummaryBand[3]/GlanceColumn[1]/GlancePanel[0]/GlanceFact[0]/Icon-fact-duration[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 401.353, + "computedY" : 539.952, + "placementX" : 401.353, + "placementY" : 539.952, + "placementWidth" : 20.0, + "placementHeight" : 20.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 20.0, + "contentHeight" : 20.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/SummaryBand[3]/GlanceColumn[1]/GlancePanel[0]/GlanceFact[0]/Icon-fact-duration[0]/SvgIcon[0]/SvgLayer0[0]", + "entityName" : "SvgLayer0", + "entityKind" : "Path", + "parentPath" : "Proposal[0]/SummaryBand[3]/GlanceColumn[1]/GlancePanel[0]/GlanceFact[0]/Icon-fact-duration[0]/SvgIcon[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 401.353, + "computedY" : 539.952, + "placementX" : 401.353, + "placementY" : 539.952, + "placementWidth" : 20.0, + "placementHeight" : 20.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 20.0, + "contentHeight" : 20.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/SummaryBand[3]/GlanceColumn[1]/GlancePanel[0]/GlanceFact[0]/FactText[1]", + "entityName" : "FactText", + "entityKind" : "SectionNode", + "parentPath" : "Proposal[0]/SummaryBand[3]/GlanceColumn[1]/GlancePanel[0]/GlanceFact[0]", + "childIndex" : 1, + "depth" : 6, + "layer" : 6, + "computedX" : 435.353, + "computedY" : 537.052, + "placementX" : 435.353, + "placementY" : 537.052, + "placementWidth" : 74.235, + "placementHeight" : 25.8, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 74.235, + "contentHeight" : 25.8, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/SummaryBand[3]/GlanceColumn[1]/GlancePanel[0]/GlanceFact[0]/FactText[1]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/SummaryBand[3]/GlanceColumn[1]/GlancePanel[0]/GlanceFact[0]/FactText[1]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 435.353, + "computedY" : 553.852, + "placementX" : 435.353, + "placementY" : 553.852, + "placementWidth" : 74.235, + "placementHeight" : 9.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 74.235, + "contentHeight" : 9.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/SummaryBand[3]/GlanceColumn[1]/GlancePanel[0]/GlanceFact[0]/FactText[1]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/SummaryBand[3]/GlanceColumn[1]/GlancePanel[0]/GlanceFact[0]/FactText[1]", + "childIndex" : 1, + "depth" : 7, + "layer" : 7, + "computedX" : 435.353, + "computedY" : 537.052, + "placementX" : 435.353, + "placementY" : 537.052, + "placementWidth" : 51.366, + "placementHeight" : 16.8, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 51.366, + "contentHeight" : 16.8, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/SummaryBand[3]/GlanceColumn[1]/GlancePanel[0]/LineNode[1]", + "entityName" : null, + "entityKind" : "LineNode", + "parentPath" : "Proposal[0]/SummaryBand[3]/GlanceColumn[1]/GlancePanel[0]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 401.353, + "computedY" : 525.763, + "placementX" : 401.353, + "placementY" : 525.763, + "placementWidth" : 146.923, + "placementHeight" : 1.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 146.923, + "contentHeight" : 1.0, + "margin" : { + "top" : 7.1, + "right" : 0.0, + "bottom" : 7.1, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/SummaryBand[3]/GlanceColumn[1]/GlancePanel[0]/GlanceFact[2]", + "entityName" : "GlanceFact", + "entityKind" : "ShapeContainerNode", + "parentPath" : "Proposal[0]/SummaryBand[3]/GlanceColumn[1]/GlancePanel[0]", + "childIndex" : 2, + "depth" : 5, + "layer" : 5, + "computedX" : 401.353, + "computedY" : 486.483, + "placementX" : 401.353, + "placementY" : 486.483, + "placementWidth" : 146.923, + "placementHeight" : 32.18, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 146.923, + "contentHeight" : 32.18, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/SummaryBand[3]/GlanceColumn[1]/GlancePanel[0]/GlanceFact[2]/Icon-fact-start[0]", + "entityName" : "Icon-fact-start", + "entityKind" : "ShapeContainerNode", + "parentPath" : "Proposal[0]/SummaryBand[3]/GlanceColumn[1]/GlancePanel[0]/GlanceFact[2]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 401.353, + "computedY" : 492.573, + "placementX" : 401.353, + "placementY" : 492.573, + "placementWidth" : 20.0, + "placementHeight" : 20.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 20.0, + "contentHeight" : 20.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/SummaryBand[3]/GlanceColumn[1]/GlancePanel[0]/GlanceFact[2]/Icon-fact-start[0]/SvgIcon[0]", + "entityName" : "SvgIcon", + "entityKind" : "LayerStackNode", + "parentPath" : "Proposal[0]/SummaryBand[3]/GlanceColumn[1]/GlancePanel[0]/GlanceFact[2]/Icon-fact-start[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 401.353, + "computedY" : 492.573, + "placementX" : 401.353, + "placementY" : 492.573, + "placementWidth" : 20.0, + "placementHeight" : 20.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 20.0, + "contentHeight" : 20.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/SummaryBand[3]/GlanceColumn[1]/GlancePanel[0]/GlanceFact[2]/Icon-fact-start[0]/SvgIcon[0]/SvgLayer0[0]", + "entityName" : "SvgLayer0", + "entityKind" : "Path", + "parentPath" : "Proposal[0]/SummaryBand[3]/GlanceColumn[1]/GlancePanel[0]/GlanceFact[2]/Icon-fact-start[0]/SvgIcon[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 401.353, + "computedY" : 492.573, + "placementX" : 401.353, + "placementY" : 492.573, + "placementWidth" : 20.0, + "placementHeight" : 20.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 20.0, + "contentHeight" : 20.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/SummaryBand[3]/GlanceColumn[1]/GlancePanel[0]/GlanceFact[2]/FactText[1]", + "entityName" : "FactText", + "entityKind" : "SectionNode", + "parentPath" : "Proposal[0]/SummaryBand[3]/GlanceColumn[1]/GlancePanel[0]/GlanceFact[2]", + "childIndex" : 1, + "depth" : 6, + "layer" : 6, + "computedX" : 435.353, + "computedY" : 489.673, + "placementX" : 435.353, + "placementY" : 489.673, + "placementWidth" : 104.062, + "placementHeight" : 25.8, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 104.062, + "contentHeight" : 25.8, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/SummaryBand[3]/GlanceColumn[1]/GlancePanel[0]/GlanceFact[2]/FactText[1]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/SummaryBand[3]/GlanceColumn[1]/GlancePanel[0]/GlanceFact[2]/FactText[1]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 435.353, + "computedY" : 506.473, + "placementX" : 435.353, + "placementY" : 506.473, + "placementWidth" : 60.518, + "placementHeight" : 9.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 60.518, + "contentHeight" : 9.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/SummaryBand[3]/GlanceColumn[1]/GlancePanel[0]/GlanceFact[2]/FactText[1]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/SummaryBand[3]/GlanceColumn[1]/GlancePanel[0]/GlanceFact[2]/FactText[1]", + "childIndex" : 1, + "depth" : 7, + "layer" : 7, + "computedX" : 435.353, + "computedY" : 489.673, + "placementX" : 435.353, + "placementY" : 489.673, + "placementWidth" : 104.062, + "placementHeight" : 16.8, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 104.062, + "contentHeight" : 16.8, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/SummaryBand[3]/GlanceColumn[1]/GlancePanel[0]/LineNode[3]", + "entityName" : null, + "entityKind" : "LineNode", + "parentPath" : "Proposal[0]/SummaryBand[3]/GlanceColumn[1]/GlancePanel[0]", + "childIndex" : 3, + "depth" : 5, + "layer" : 5, + "computedX" : 401.353, + "computedY" : 478.383, + "placementX" : 401.353, + "placementY" : 478.383, + "placementWidth" : 146.923, + "placementHeight" : 1.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 146.923, + "contentHeight" : 1.0, + "margin" : { + "top" : 7.1, + "right" : 0.0, + "bottom" : 7.1, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/SummaryBand[3]/GlanceColumn[1]/GlancePanel[0]/GlanceFact[4]", + "entityName" : "GlanceFact", + "entityKind" : "ShapeContainerNode", + "parentPath" : "Proposal[0]/SummaryBand[3]/GlanceColumn[1]/GlancePanel[0]", + "childIndex" : 4, + "depth" : 5, + "layer" : 5, + "computedX" : 401.353, + "computedY" : 439.104, + "placementX" : 401.353, + "placementY" : 439.104, + "placementWidth" : 146.923, + "placementHeight" : 32.18, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 146.923, + "contentHeight" : 32.18, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/SummaryBand[3]/GlanceColumn[1]/GlancePanel[0]/GlanceFact[4]/Icon-fact-contact[0]", + "entityName" : "Icon-fact-contact", + "entityKind" : "ShapeContainerNode", + "parentPath" : "Proposal[0]/SummaryBand[3]/GlanceColumn[1]/GlancePanel[0]/GlanceFact[4]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 401.353, + "computedY" : 445.193, + "placementX" : 401.353, + "placementY" : 445.193, + "placementWidth" : 20.0, + "placementHeight" : 20.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 20.0, + "contentHeight" : 20.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/SummaryBand[3]/GlanceColumn[1]/GlancePanel[0]/GlanceFact[4]/Icon-fact-contact[0]/SvgIcon[0]", + "entityName" : "SvgIcon", + "entityKind" : "LayerStackNode", + "parentPath" : "Proposal[0]/SummaryBand[3]/GlanceColumn[1]/GlancePanel[0]/GlanceFact[4]/Icon-fact-contact[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 401.353, + "computedY" : 445.193, + "placementX" : 401.353, + "placementY" : 445.193, + "placementWidth" : 20.0, + "placementHeight" : 20.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 20.0, + "contentHeight" : 20.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/SummaryBand[3]/GlanceColumn[1]/GlancePanel[0]/GlanceFact[4]/Icon-fact-contact[0]/SvgIcon[0]/SvgLayer0[0]", + "entityName" : "SvgLayer0", + "entityKind" : "Path", + "parentPath" : "Proposal[0]/SummaryBand[3]/GlanceColumn[1]/GlancePanel[0]/GlanceFact[4]/Icon-fact-contact[0]/SvgIcon[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 401.353, + "computedY" : 445.193, + "placementX" : 401.353, + "placementY" : 445.193, + "placementWidth" : 20.0, + "placementHeight" : 20.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 20.0, + "contentHeight" : 20.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/SummaryBand[3]/GlanceColumn[1]/GlancePanel[0]/GlanceFact[4]/FactText[1]", + "entityName" : "FactText", + "entityKind" : "SectionNode", + "parentPath" : "Proposal[0]/SummaryBand[3]/GlanceColumn[1]/GlancePanel[0]/GlanceFact[4]", + "childIndex" : 1, + "depth" : 6, + "layer" : 6, + "computedX" : 435.353, + "computedY" : 442.293, + "placementX" : 435.353, + "placementY" : 442.293, + "placementWidth" : 78.148, + "placementHeight" : 25.8, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 78.148, + "contentHeight" : 25.8, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/SummaryBand[3]/GlanceColumn[1]/GlancePanel[0]/GlanceFact[4]/FactText[1]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/SummaryBand[3]/GlanceColumn[1]/GlancePanel[0]/GlanceFact[4]/FactText[1]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 435.353, + "computedY" : 459.093, + "placementX" : 435.353, + "placementY" : 459.093, + "placementWidth" : 71.483, + "placementHeight" : 9.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 71.483, + "contentHeight" : 9.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/SummaryBand[3]/GlanceColumn[1]/GlancePanel[0]/GlanceFact[4]/FactText[1]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/SummaryBand[3]/GlanceColumn[1]/GlancePanel[0]/GlanceFact[4]/FactText[1]", + "childIndex" : 1, + "depth" : 7, + "layer" : 7, + "computedX" : 435.353, + "computedY" : 442.293, + "placementX" : 435.353, + "placementY" : 442.293, + "placementWidth" : 78.148, + "placementHeight" : 16.8, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 78.148, + "contentHeight" : 16.8, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/SummaryBand[3]/GlanceColumn[1]/GlancePanel[0]/LineNode[5]", + "entityName" : null, + "entityKind" : "LineNode", + "parentPath" : "Proposal[0]/SummaryBand[3]/GlanceColumn[1]/GlancePanel[0]", + "childIndex" : 5, + "depth" : 5, + "layer" : 5, + "computedX" : 401.353, + "computedY" : 431.004, + "placementX" : 401.353, + "placementY" : 431.004, + "placementWidth" : 146.923, + "placementHeight" : 1.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 146.923, + "contentHeight" : 1.0, + "margin" : { + "top" : 7.1, + "right" : 0.0, + "bottom" : 7.1, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/SummaryBand[3]/GlanceColumn[1]/GlancePanel[0]/GlanceFact[6]", + "entityName" : "GlanceFact", + "entityKind" : "ShapeContainerNode", + "parentPath" : "Proposal[0]/SummaryBand[3]/GlanceColumn[1]/GlancePanel[0]", + "childIndex" : 6, + "depth" : 5, + "layer" : 5, + "computedX" : 401.353, + "computedY" : 378.97, + "placementX" : 401.353, + "placementY" : 378.97, + "placementWidth" : 146.923, + "placementHeight" : 44.934, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 146.923, + "contentHeight" : 44.934, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/SummaryBand[3]/GlanceColumn[1]/GlancePanel[0]/GlanceFact[6]/Icon-fact-validity[0]", + "entityName" : "Icon-fact-validity", + "entityKind" : "ShapeContainerNode", + "parentPath" : "Proposal[0]/SummaryBand[3]/GlanceColumn[1]/GlancePanel[0]/GlanceFact[6]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 401.353, + "computedY" : 391.437, + "placementX" : 401.353, + "placementY" : 391.437, + "placementWidth" : 20.0, + "placementHeight" : 20.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 20.0, + "contentHeight" : 20.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/SummaryBand[3]/GlanceColumn[1]/GlancePanel[0]/GlanceFact[6]/Icon-fact-validity[0]/SvgIcon[0]", + "entityName" : "SvgIcon", + "entityKind" : "LayerStackNode", + "parentPath" : "Proposal[0]/SummaryBand[3]/GlanceColumn[1]/GlancePanel[0]/GlanceFact[6]/Icon-fact-validity[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 401.353, + "computedY" : 391.437, + "placementX" : 401.353, + "placementY" : 391.437, + "placementWidth" : 20.0, + "placementHeight" : 20.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 20.0, + "contentHeight" : 20.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/SummaryBand[3]/GlanceColumn[1]/GlancePanel[0]/GlanceFact[6]/Icon-fact-validity[0]/SvgIcon[0]/SvgLayer0[0]", + "entityName" : "SvgLayer0", + "entityKind" : "Path", + "parentPath" : "Proposal[0]/SummaryBand[3]/GlanceColumn[1]/GlancePanel[0]/GlanceFact[6]/Icon-fact-validity[0]/SvgIcon[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 401.353, + "computedY" : 391.437, + "placementX" : 401.353, + "placementY" : 391.437, + "placementWidth" : 20.0, + "placementHeight" : 20.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 20.0, + "contentHeight" : 20.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/SummaryBand[3]/GlanceColumn[1]/GlancePanel[0]/GlanceFact[6]/FactText[1]", + "entityName" : "FactText", + "entityKind" : "SectionNode", + "parentPath" : "Proposal[0]/SummaryBand[3]/GlanceColumn[1]/GlancePanel[0]/GlanceFact[6]", + "childIndex" : 1, + "depth" : 6, + "layer" : 6, + "computedX" : 435.353, + "computedY" : 383.737, + "placementX" : 435.353, + "placementY" : 383.737, + "placementWidth" : 87.576, + "placementHeight" : 35.4, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 87.576, + "contentHeight" : 35.4, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/SummaryBand[3]/GlanceColumn[1]/GlancePanel[0]/GlanceFact[6]/FactText[1]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/SummaryBand[3]/GlanceColumn[1]/GlancePanel[0]/GlanceFact[6]/FactText[1]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 435.353, + "computedY" : 410.137, + "placementX" : 435.353, + "placementY" : 410.137, + "placementWidth" : 75.128, + "placementHeight" : 9.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 75.128, + "contentHeight" : 9.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/SummaryBand[3]/GlanceColumn[1]/GlancePanel[0]/GlanceFact[6]/FactText[1]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/SummaryBand[3]/GlanceColumn[1]/GlancePanel[0]/GlanceFact[6]/FactText[1]", + "childIndex" : 1, + "depth" : 7, + "layer" : 7, + "computedX" : 435.353, + "computedY" : 393.337, + "placementX" : 435.353, + "placementY" : 393.337, + "placementWidth" : 48.258, + "placementHeight" : 16.8, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 48.258, + "contentHeight" : 16.8, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/SummaryBand[3]/GlanceColumn[1]/GlancePanel[0]/GlanceFact[6]/FactText[1]/ParagraphNode[2]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/SummaryBand[3]/GlanceColumn[1]/GlancePanel[0]/GlanceFact[6]/FactText[1]", + "childIndex" : 2, + "depth" : 7, + "layer" : 7, + "computedX" : 435.353, + "computedY" : 383.737, + "placementX" : 435.353, + "placementY" : 383.737, + "placementWidth" : 87.576, + "placementHeight" : 9.6, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 87.576, + "contentHeight" : 9.6, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/GoalCells[4]", + "entityName" : "GoalCells", + "entityKind" : "RowNode", + "parentPath" : "Proposal[0]", + "childIndex" : 4, + "depth" : 2, + "layer" : 2, + "computedX" : 30.0, + "computedY" : 299.42, + "placementX" : 30.0, + "placementY" : 299.42, + "placementWidth" : 535.276, + "placementHeight" : 41.901, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 535.276, + "contentHeight" : 41.901, + "margin" : { + "top" : 17.8, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/GoalCells[4]/Icon-goal-check[0]", + "entityName" : "Icon-goal-check", + "entityKind" : "ShapeContainerNode", + "parentPath" : "Proposal[0]/GoalCells[4]", + "childIndex" : 0, + "depth" : 3, + "layer" : 3, + "computedX" : 30.0, + "computedY" : 325.321, + "placementX" : 30.0, + "placementY" : 325.321, + "placementWidth" : 16.0, + "placementHeight" : 16.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 16.0, + "contentHeight" : 16.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/GoalCells[4]/Icon-goal-check[0]/SvgIcon[0]", + "entityName" : "SvgIcon", + "entityKind" : "LayerStackNode", + "parentPath" : "Proposal[0]/GoalCells[4]/Icon-goal-check[0]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 30.0, + "computedY" : 325.321, + "placementX" : 30.0, + "placementY" : 325.321, + "placementWidth" : 16.0, + "placementHeight" : 16.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 16.0, + "contentHeight" : 16.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/GoalCells[4]/Icon-goal-check[0]/SvgIcon[0]/SvgLayer0[0]", + "entityName" : "SvgLayer0", + "entityKind" : "Path", + "parentPath" : "Proposal[0]/GoalCells[4]/Icon-goal-check[0]/SvgIcon[0]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 30.0, + "computedY" : 325.321, + "placementX" : 30.0, + "placementY" : 325.321, + "placementWidth" : 16.0, + "placementHeight" : 16.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 16.0, + "contentHeight" : 16.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/GoalCells[4]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/GoalCells[4]", + "childIndex" : 1, + "depth" : 3, + "layer" : 3, + "computedX" : 54.0, + "computedY" : 302.821, + "placementX" : 54.0, + "placementY" : 302.821, + "placementWidth" : 80.352, + "placementHeight" : 38.5, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 80.352, + "contentHeight" : 38.5, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/GoalCells[4]/LineNode[2]", + "entityName" : null, + "entityKind" : "LineNode", + "parentPath" : "Proposal[0]/GoalCells[4]", + "childIndex" : 2, + "depth" : 3, + "layer" : 3, + "computedX" : 159.294, + "computedY" : 299.42, + "placementX" : 159.294, + "placementY" : 299.42, + "placementWidth" : 1.0, + "placementHeight" : 41.901, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 1.0, + "contentHeight" : 41.901, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/GoalCells[4]/Icon-goal-check[3]", + "entityName" : "Icon-goal-check", + "entityKind" : "ShapeContainerNode", + "parentPath" : "Proposal[0]/GoalCells[4]", + "childIndex" : 3, + "depth" : 3, + "layer" : 3, + "computedX" : 167.994, + "computedY" : 325.321, + "placementX" : 167.994, + "placementY" : 325.321, + "placementWidth" : 16.0, + "placementHeight" : 16.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 16.0, + "contentHeight" : 16.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/GoalCells[4]/Icon-goal-check[3]/SvgIcon[0]", + "entityName" : "SvgIcon", + "entityKind" : "LayerStackNode", + "parentPath" : "Proposal[0]/GoalCells[4]/Icon-goal-check[3]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 167.994, + "computedY" : 325.321, + "placementX" : 167.994, + "placementY" : 325.321, + "placementWidth" : 16.0, + "placementHeight" : 16.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 16.0, + "contentHeight" : 16.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/GoalCells[4]/Icon-goal-check[3]/SvgIcon[0]/SvgLayer0[0]", + "entityName" : "SvgLayer0", + "entityKind" : "Path", + "parentPath" : "Proposal[0]/GoalCells[4]/Icon-goal-check[3]/SvgIcon[0]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 167.994, + "computedY" : 325.321, + "placementX" : 167.994, + "placementY" : 325.321, + "placementWidth" : 16.0, + "placementHeight" : 16.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 16.0, + "contentHeight" : 16.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/GoalCells[4]/ParagraphNode[4]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/GoalCells[4]", + "childIndex" : 4, + "depth" : 3, + "layer" : 3, + "computedX" : 191.994, + "computedY" : 302.821, + "placementX" : 191.994, + "placementY" : 302.821, + "placementWidth" : 72.54, + "placementHeight" : 38.5, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 72.54, + "contentHeight" : 38.5, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/GoalCells[4]/LineNode[5]", + "entityName" : null, + "entityKind" : "LineNode", + "parentPath" : "Proposal[0]/GoalCells[4]", + "childIndex" : 5, + "depth" : 3, + "layer" : 3, + "computedX" : 297.288, + "computedY" : 299.42, + "placementX" : 297.288, + "placementY" : 299.42, + "placementWidth" : 1.0, + "placementHeight" : 41.901, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 1.0, + "contentHeight" : 41.901, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/GoalCells[4]/Icon-goal-check[6]", + "entityName" : "Icon-goal-check", + "entityKind" : "ShapeContainerNode", + "parentPath" : "Proposal[0]/GoalCells[4]", + "childIndex" : 6, + "depth" : 3, + "layer" : 3, + "computedX" : 305.988, + "computedY" : 325.321, + "placementX" : 305.988, + "placementY" : 325.321, + "placementWidth" : 16.0, + "placementHeight" : 16.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 16.0, + "contentHeight" : 16.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/GoalCells[4]/Icon-goal-check[6]/SvgIcon[0]", + "entityName" : "SvgIcon", + "entityKind" : "LayerStackNode", + "parentPath" : "Proposal[0]/GoalCells[4]/Icon-goal-check[6]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 305.988, + "computedY" : 325.321, + "placementX" : 305.988, + "placementY" : 325.321, + "placementWidth" : 16.0, + "placementHeight" : 16.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 16.0, + "contentHeight" : 16.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/GoalCells[4]/Icon-goal-check[6]/SvgIcon[0]/SvgLayer0[0]", + "entityName" : "SvgLayer0", + "entityKind" : "Path", + "parentPath" : "Proposal[0]/GoalCells[4]/Icon-goal-check[6]/SvgIcon[0]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 305.988, + "computedY" : 325.321, + "placementX" : 305.988, + "placementY" : 325.321, + "placementWidth" : 16.0, + "placementHeight" : 16.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 16.0, + "contentHeight" : 16.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/GoalCells[4]/ParagraphNode[7]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/GoalCells[4]", + "childIndex" : 7, + "depth" : 3, + "layer" : 3, + "computedX" : 329.988, + "computedY" : 302.821, + "placementX" : 329.988, + "placementY" : 302.821, + "placementWidth" : 87.291, + "placementHeight" : 38.5, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 87.291, + "contentHeight" : 38.5, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/GoalCells[4]/LineNode[8]", + "entityName" : null, + "entityKind" : "LineNode", + "parentPath" : "Proposal[0]/GoalCells[4]", + "childIndex" : 8, + "depth" : 3, + "layer" : 3, + "computedX" : 435.282, + "computedY" : 299.42, + "placementX" : 435.282, + "placementY" : 299.42, + "placementWidth" : 1.0, + "placementHeight" : 41.901, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 1.0, + "contentHeight" : 41.901, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/GoalCells[4]/Icon-goal-check[9]", + "entityName" : "Icon-goal-check", + "entityKind" : "ShapeContainerNode", + "parentPath" : "Proposal[0]/GoalCells[4]", + "childIndex" : 9, + "depth" : 3, + "layer" : 3, + "computedX" : 443.982, + "computedY" : 325.321, + "placementX" : 443.982, + "placementY" : 325.321, + "placementWidth" : 16.0, + "placementHeight" : 16.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 16.0, + "contentHeight" : 16.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/GoalCells[4]/Icon-goal-check[9]/SvgIcon[0]", + "entityName" : "SvgIcon", + "entityKind" : "LayerStackNode", + "parentPath" : "Proposal[0]/GoalCells[4]/Icon-goal-check[9]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 443.982, + "computedY" : 325.321, + "placementX" : 443.982, + "placementY" : 325.321, + "placementWidth" : 16.0, + "placementHeight" : 16.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 16.0, + "contentHeight" : 16.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/GoalCells[4]/Icon-goal-check[9]/SvgIcon[0]/SvgLayer0[0]", + "entityName" : "SvgLayer0", + "entityKind" : "Path", + "parentPath" : "Proposal[0]/GoalCells[4]/Icon-goal-check[9]/SvgIcon[0]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 443.982, + "computedY" : 325.321, + "placementX" : 443.982, + "placementY" : 325.321, + "placementWidth" : 16.0, + "placementHeight" : 16.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 16.0, + "contentHeight" : 16.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/GoalCells[4]/ParagraphNode[10]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/GoalCells[4]", + "childIndex" : 10, + "depth" : 3, + "layer" : 3, + "computedX" : 467.982, + "computedY" : 302.821, + "placementX" : 467.982, + "placementY" : 302.821, + "placementWidth" : 86.607, + "placementHeight" : 38.5, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 86.607, + "contentHeight" : 38.5, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/ScopeOfWork[5]", + "entityName" : "ScopeOfWork", + "entityKind" : "SectionNode", + "parentPath" : "Proposal[0]", + "childIndex" : 5, + "depth" : 2, + "layer" : 2, + "computedX" : 30.0, + "computedY" : 88.911, + "placementX" : 30.0, + "placementY" : 88.911, + "placementWidth" : 535.276, + "placementHeight" : 192.709, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 535.276, + "contentHeight" : 192.709, + "margin" : { + "top" : 17.8, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/ScopeOfWork[5]/SectionHeading[0]", + "entityName" : "SectionHeading", + "entityKind" : "SectionNode", + "parentPath" : "Proposal[0]/ScopeOfWork[5]", + "childIndex" : 0, + "depth" : 3, + "layer" : 3, + "computedX" : 30.0, + "computedY" : 254.211, + "placementX" : 30.0, + "placementY" : 254.211, + "placementWidth" : 109.585, + "placementHeight" : 27.409, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 109.585, + "contentHeight" : 27.409, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/ScopeOfWork[5]/SectionHeading[0]/SectionHeadingTitle[0]", + "entityName" : "SectionHeadingTitle", + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/ScopeOfWork[5]/SectionHeading[0]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 30.0, + "computedY" : 256.811, + "placementX" : 30.0, + "placementY" : 256.811, + "placementWidth" : 109.585, + "placementHeight" : 24.809, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 109.585, + "contentHeight" : 24.809, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/ScopeOfWork[5]/SectionHeading[0]/SectionHeadingRule[1]", + "entityName" : "SectionHeadingRule", + "entityKind" : "LineNode", + "parentPath" : "Proposal[0]/ScopeOfWork[5]/SectionHeading[0]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 30.0, + "computedY" : 254.211, + "placementX" : 30.0, + "placementY" : 254.211, + "placementWidth" : 25.693, + "placementHeight" : 1.6, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 25.693, + "contentHeight" : 1.6, + "margin" : { + "top" : 1.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/ScopeOfWork[5]/LineNode[1]", + "entityName" : null, + "entityKind" : "LineNode", + "parentPath" : "Proposal[0]/ScopeOfWork[5]", + "childIndex" : 1, + "depth" : 3, + "layer" : 3, + "computedX" : 30.0, + "computedY" : 243.711, + "placementX" : 30.0, + "placementY" : 243.711, + "placementWidth" : 535.276, + "placementHeight" : 1.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 535.276, + "contentHeight" : 1.0, + "margin" : { + "top" : 9.5, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/ScopeOfWork[5]/ScopeRow01[2]", + "entityName" : "ScopeRow01", + "entityKind" : "RowNode", + "parentPath" : "Proposal[0]/ScopeOfWork[5]", + "childIndex" : 2, + "depth" : 3, + "layer" : 3, + "computedX" : 30.0, + "computedY" : 213.551, + "placementX" : 30.0, + "placementY" : 213.551, + "placementWidth" : 535.276, + "placementHeight" : 30.16, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 535.276, + "contentHeight" : 30.16, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 2.9, + "right" : 0.0, + "bottom" : 2.9, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/ScopeOfWork[5]/ScopeRow01[2]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/ScopeOfWork[5]/ScopeRow01[2]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 30.0, + "computedY" : 230.011, + "placementX" : 30.0, + "placementY" : 230.011, + "placementWidth" : 10.44, + "placementHeight" : 10.8, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 10.44, + "contentHeight" : 10.8, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/ScopeOfWork[5]/ScopeRow01[2]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/ScopeOfWork[5]/ScopeRow01[2]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 64.0, + "computedY" : 229.411, + "placementX" : 64.0, + "placementY" : 229.411, + "placementWidth" : 92.748, + "placementHeight" : 11.4, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 92.748, + "contentHeight" : 11.4, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/ScopeOfWork[5]/ScopeRow01[2]/ParagraphNode[2]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/ScopeOfWork[5]/ScopeRow01[2]", + "childIndex" : 2, + "depth" : 4, + "layer" : 4, + "computedX" : 260.169, + "computedY" : 216.451, + "placementX" : 260.169, + "placementY" : 216.451, + "placementWidth" : 259.311, + "placementHeight" : 24.36, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 259.311, + "contentHeight" : 24.36, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/ScopeOfWork[5]/LineNode[3]", + "entityName" : null, + "entityKind" : "LineNode", + "parentPath" : "Proposal[0]/ScopeOfWork[5]", + "childIndex" : 3, + "depth" : 3, + "layer" : 3, + "computedX" : 30.0, + "computedY" : 212.551, + "placementX" : 30.0, + "placementY" : 212.551, + "placementWidth" : 535.276, + "placementHeight" : 1.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 535.276, + "contentHeight" : 1.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/ScopeOfWork[5]/ScopeRow02[4]", + "entityName" : "ScopeRow02", + "entityKind" : "RowNode", + "parentPath" : "Proposal[0]/ScopeOfWork[5]", + "childIndex" : 4, + "depth" : 3, + "layer" : 3, + "computedX" : 30.0, + "computedY" : 182.391, + "placementX" : 30.0, + "placementY" : 182.391, + "placementWidth" : 535.276, + "placementHeight" : 30.16, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 535.276, + "contentHeight" : 30.16, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 2.9, + "right" : 0.0, + "bottom" : 2.9, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/ScopeOfWork[5]/ScopeRow02[4]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/ScopeOfWork[5]/ScopeRow02[4]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 30.0, + "computedY" : 198.851, + "placementX" : 30.0, + "placementY" : 198.851, + "placementWidth" : 10.44, + "placementHeight" : 10.8, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 10.44, + "contentHeight" : 10.8, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/ScopeOfWork[5]/ScopeRow02[4]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/ScopeOfWork[5]/ScopeRow02[4]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 64.0, + "computedY" : 198.251, + "placementX" : 64.0, + "placementY" : 198.251, + "placementWidth" : 112.87, + "placementHeight" : 11.4, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 112.87, + "contentHeight" : 11.4, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/ScopeOfWork[5]/ScopeRow02[4]/ParagraphNode[2]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/ScopeOfWork[5]/ScopeRow02[4]", + "childIndex" : 2, + "depth" : 4, + "layer" : 4, + "computedX" : 260.169, + "computedY" : 185.291, + "placementX" : 260.169, + "placementY" : 185.291, + "placementWidth" : 244.342, + "placementHeight" : 24.36, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 244.342, + "contentHeight" : 24.36, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/ScopeOfWork[5]/LineNode[5]", + "entityName" : null, + "entityKind" : "LineNode", + "parentPath" : "Proposal[0]/ScopeOfWork[5]", + "childIndex" : 5, + "depth" : 3, + "layer" : 3, + "computedX" : 30.0, + "computedY" : 181.391, + "placementX" : 30.0, + "placementY" : 181.391, + "placementWidth" : 535.276, + "placementHeight" : 1.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 535.276, + "contentHeight" : 1.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/ScopeOfWork[5]/ScopeRow03[6]", + "entityName" : "ScopeRow03", + "entityKind" : "RowNode", + "parentPath" : "Proposal[0]/ScopeOfWork[5]", + "childIndex" : 6, + "depth" : 3, + "layer" : 3, + "computedX" : 30.0, + "computedY" : 151.231, + "placementX" : 30.0, + "placementY" : 151.231, + "placementWidth" : 535.276, + "placementHeight" : 30.16, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 535.276, + "contentHeight" : 30.16, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 2.9, + "right" : 0.0, + "bottom" : 2.9, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/ScopeOfWork[5]/ScopeRow03[6]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/ScopeOfWork[5]/ScopeRow03[6]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 30.0, + "computedY" : 167.691, + "placementX" : 30.0, + "placementY" : 167.691, + "placementWidth" : 10.44, + "placementHeight" : 10.8, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 10.44, + "contentHeight" : 10.8, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/ScopeOfWork[5]/ScopeRow03[6]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/ScopeOfWork[5]/ScopeRow03[6]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 64.0, + "computedY" : 167.091, + "placementX" : 64.0, + "placementY" : 167.091, + "placementWidth" : 97.28, + "placementHeight" : 11.4, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 97.28, + "contentHeight" : 11.4, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/ScopeOfWork[5]/ScopeRow03[6]/ParagraphNode[2]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/ScopeOfWork[5]/ScopeRow03[6]", + "childIndex" : 2, + "depth" : 4, + "layer" : 4, + "computedX" : 260.169, + "computedY" : 154.131, + "placementX" : 260.169, + "placementY" : 154.131, + "placementWidth" : 276.032, + "placementHeight" : 24.36, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 276.032, + "contentHeight" : 24.36, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/ScopeOfWork[5]/LineNode[7]", + "entityName" : null, + "entityKind" : "LineNode", + "parentPath" : "Proposal[0]/ScopeOfWork[5]", + "childIndex" : 7, + "depth" : 3, + "layer" : 3, + "computedX" : 30.0, + "computedY" : 150.231, + "placementX" : 30.0, + "placementY" : 150.231, + "placementWidth" : 535.276, + "placementHeight" : 1.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 535.276, + "contentHeight" : 1.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/ScopeOfWork[5]/ScopeRow04[8]", + "entityName" : "ScopeRow04", + "entityKind" : "RowNode", + "parentPath" : "Proposal[0]/ScopeOfWork[5]", + "childIndex" : 8, + "depth" : 3, + "layer" : 3, + "computedX" : 30.0, + "computedY" : 120.071, + "placementX" : 30.0, + "placementY" : 120.071, + "placementWidth" : 535.276, + "placementHeight" : 30.16, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 535.276, + "contentHeight" : 30.16, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 2.9, + "right" : 0.0, + "bottom" : 2.9, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/ScopeOfWork[5]/ScopeRow04[8]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/ScopeOfWork[5]/ScopeRow04[8]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 30.0, + "computedY" : 136.531, + "placementX" : 30.0, + "placementY" : 136.531, + "placementWidth" : 10.44, + "placementHeight" : 10.8, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 10.44, + "contentHeight" : 10.8, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/ScopeOfWork[5]/ScopeRow04[8]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/ScopeOfWork[5]/ScopeRow04[8]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 64.0, + "computedY" : 135.931, + "placementX" : 64.0, + "placementY" : 135.931, + "placementWidth" : 133.19, + "placementHeight" : 11.4, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 133.19, + "contentHeight" : 11.4, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/ScopeOfWork[5]/ScopeRow04[8]/ParagraphNode[2]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/ScopeOfWork[5]/ScopeRow04[8]", + "childIndex" : 2, + "depth" : 4, + "layer" : 4, + "computedX" : 260.169, + "computedY" : 122.971, + "placementX" : 260.169, + "placementY" : 122.971, + "placementWidth" : 282.091, + "placementHeight" : 24.36, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 282.091, + "contentHeight" : 24.36, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/ScopeOfWork[5]/LineNode[9]", + "entityName" : null, + "entityKind" : "LineNode", + "parentPath" : "Proposal[0]/ScopeOfWork[5]", + "childIndex" : 9, + "depth" : 3, + "layer" : 3, + "computedX" : 30.0, + "computedY" : 119.071, + "placementX" : 30.0, + "placementY" : 119.071, + "placementWidth" : 535.276, + "placementHeight" : 1.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 535.276, + "contentHeight" : 1.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/ScopeOfWork[5]/ScopeRow05[10]", + "entityName" : "ScopeRow05", + "entityKind" : "RowNode", + "parentPath" : "Proposal[0]/ScopeOfWork[5]", + "childIndex" : 10, + "depth" : 3, + "layer" : 3, + "computedX" : 30.0, + "computedY" : 88.911, + "placementX" : 30.0, + "placementY" : 88.911, + "placementWidth" : 535.276, + "placementHeight" : 30.16, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 535.276, + "contentHeight" : 30.16, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 2.9, + "right" : 0.0, + "bottom" : 2.9, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/ScopeOfWork[5]/ScopeRow05[10]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/ScopeOfWork[5]/ScopeRow05[10]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 30.0, + "computedY" : 105.371, + "placementX" : 30.0, + "placementY" : 105.371, + "placementWidth" : 10.44, + "placementHeight" : 10.8, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 10.44, + "contentHeight" : 10.8, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/ScopeOfWork[5]/ScopeRow05[10]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/ScopeOfWork[5]/ScopeRow05[10]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 64.0, + "computedY" : 104.771, + "placementX" : 64.0, + "placementY" : 104.771, + "placementWidth" : 66.633, + "placementHeight" : 11.4, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 66.633, + "contentHeight" : 11.4, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/ScopeOfWork[5]/ScopeRow05[10]/ParagraphNode[2]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/ScopeOfWork[5]/ScopeRow05[10]", + "childIndex" : 2, + "depth" : 4, + "layer" : 4, + "computedX" : 260.169, + "computedY" : 91.811, + "placementX" : 260.169, + "placementY" : 91.811, + "placementWidth" : 274.933, + "placementHeight" : 24.36, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 274.933, + "contentHeight" : 24.36, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/Masthead[6]", + "entityName" : "Masthead", + "entityKind" : "SectionNode", + "parentPath" : "Proposal[0]", + "childIndex" : 6, + "depth" : 2, + "layer" : 2, + "computedX" : 30.0, + "computedY" : 775.788, + "placementX" : 30.0, + "placementY" : 775.788, + "placementWidth" : 535.276, + "placementHeight" : 46.402, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 535.276, + "contentHeight" : 46.402, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/Masthead[6]/MastheadRow[0]", + "entityName" : "MastheadRow", + "entityKind" : "RowNode", + "parentPath" : "Proposal[0]/Masthead[6]", + "childIndex" : 0, + "depth" : 3, + "layer" : 3, + "computedX" : 30.0, + "computedY" : 792.888, + "placementX" : 30.0, + "placementY" : 792.888, + "placementWidth" : 535.276, + "placementHeight" : 29.302, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 535.276, + "contentHeight" : 29.302, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/Masthead[6]/MastheadRow[0]/LogoMark[0]", + "entityName" : "LogoMark", + "entityKind" : "ShapeContainerNode", + "parentPath" : "Proposal[0]/Masthead[6]/MastheadRow[0]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 30.0, + "computedY" : 794.539, + "placementX" : 30.0, + "placementY" : 794.539, + "placementWidth" : 30.0, + "placementHeight" : 26.0, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 30.0, + "contentHeight" : 26.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/Masthead[6]/MastheadRow[0]/LogoMark[0]/MarkStem[0]", + "entityName" : "MarkStem", + "entityKind" : "Path", + "parentPath" : "Proposal[0]/Masthead[6]/MastheadRow[0]/LogoMark[0]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 30.0, + "computedY" : 794.539, + "placementX" : 30.0, + "placementY" : 794.539, + "placementWidth" : 30.0, + "placementHeight" : 26.0, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 30.0, + "contentHeight" : 26.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/Masthead[6]/MastheadRow[0]/LogoMark[0]/MarkDiagonal[1]", + "entityName" : "MarkDiagonal", + "entityKind" : "Path", + "parentPath" : "Proposal[0]/Masthead[6]/MastheadRow[0]/LogoMark[0]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 30.0, + "computedY" : 794.539, + "placementX" : 30.0, + "placementY" : 794.539, + "placementWidth" : 30.0, + "placementHeight" : 26.0, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 30.0, + "contentHeight" : 26.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/Masthead[6]/MastheadRow[0]/LogoMark[0]/MarkSlash[2]", + "entityName" : "MarkSlash", + "entityKind" : "Path", + "parentPath" : "Proposal[0]/Masthead[6]/MastheadRow[0]/LogoMark[0]", + "childIndex" : 2, + "depth" : 5, + "layer" : 5, + "computedX" : 30.0, + "computedY" : 794.539, + "placementX" : 30.0, + "placementY" : 794.539, + "placementWidth" : 30.0, + "placementHeight" : 26.0, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 30.0, + "contentHeight" : 26.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/Masthead[6]/MastheadRow[0]/LogoMark[0]/MarkRightStem[3]", + "entityName" : "MarkRightStem", + "entityKind" : "Path", + "parentPath" : "Proposal[0]/Masthead[6]/MastheadRow[0]/LogoMark[0]", + "childIndex" : 3, + "depth" : 5, + "layer" : 5, + "computedX" : 30.0, + "computedY" : 794.539, + "placementX" : 30.0, + "placementY" : 794.539, + "placementWidth" : 30.0, + "placementHeight" : 26.0, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 30.0, + "contentHeight" : 26.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/Masthead[6]/MastheadRow[0]/Wordmark[1]", + "entityName" : "Wordmark", + "entityKind" : "ShapeContainerNode", + "parentPath" : "Proposal[0]/Masthead[6]/MastheadRow[0]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 69.0, + "computedY" : 792.888, + "placementX" : 69.0, + "placementY" : 792.888, + "placementWidth" : 101.702, + "placementHeight" : 29.302, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 101.702, + "contentHeight" : 29.302, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/Masthead[6]/MastheadRow[0]/Wordmark[1]/WordmarkLine1[0]", + "entityName" : "WordmarkLine1", + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/Masthead[6]/MastheadRow[0]/Wordmark[1]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 69.0, + "computedY" : 802.87, + "placementX" : 69.0, + "placementY" : 802.87, + "placementWidth" : 92.672, + "placementHeight" : 19.32, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 92.672, + "contentHeight" : 19.32, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/Masthead[6]/MastheadRow[0]/Wordmark[1]/WordmarkLine2[1]", + "entityName" : "WordmarkLine2", + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/Masthead[6]/MastheadRow[0]/Wordmark[1]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 69.0, + "computedY" : 792.188, + "placementX" : 69.0, + "placementY" : 792.188, + "placementWidth" : 52.612, + "placementHeight" : 16.8, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 52.612, + "contentHeight" : 16.8, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/Masthead[6]/MastheadRow[0]/SpacerNode[2]", + "entityName" : null, + "entityKind" : "SpacerNode", + "parentPath" : "Proposal[0]/Masthead[6]/MastheadRow[0]", + "childIndex" : 2, + "depth" : 4, + "layer" : 4, + "computedX" : 179.702, + "computedY" : 807.539, + "placementX" : 179.702, + "placementY" : 807.539, + "placementWidth" : 0.0, + "placementHeight" : 0.0, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 0.0, + "contentHeight" : 0.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/Masthead[6]/MastheadRow[0]/DocumentLabel[3]", + "entityName" : "DocumentLabel", + "entityKind" : "SectionNode", + "parentPath" : "Proposal[0]/Masthead[6]/MastheadRow[0]", + "childIndex" : 3, + "depth" : 4, + "layer" : 4, + "computedX" : 495.69, + "computedY" : 799.259, + "placementX" : 495.69, + "placementY" : 799.259, + "placementWidth" : 69.586, + "placementHeight" : 16.56, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 69.586, + "contentHeight" : 16.56, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 8.5, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/Masthead[6]/MastheadRow[0]/DocumentLabel[3]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/Masthead[6]/MastheadRow[0]/DocumentLabel[3]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 495.69, + "computedY" : 805.859, + "placementX" : 495.69, + "placementY" : 805.859, + "placementWidth" : 61.086, + "placementHeight" : 9.96, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 61.086, + "contentHeight" : 9.96, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/Masthead[6]/MastheadRow[0]/DocumentLabel[3]/Align[1]", + "entityName" : null, + "entityKind" : "Align", + "parentPath" : "Proposal[0]/Masthead[6]/MastheadRow[0]/DocumentLabel[3]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 495.69, + "computedY" : 799.259, + "placementX" : 495.69, + "placementY" : 799.259, + "placementWidth" : 61.086, + "placementHeight" : 6.6, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 61.086, + "contentHeight" : 6.6, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/Masthead[6]/MastheadRow[0]/DocumentLabel[3]/Align[1]/DocumentLabelRule[0]", + "entityName" : "DocumentLabelRule", + "entityKind" : "LineNode", + "parentPath" : "Proposal[0]/Masthead[6]/MastheadRow[0]/DocumentLabel[3]/Align[1]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 495.69, + "computedY" : 799.259, + "placementX" : 495.69, + "placementY" : 799.259, + "placementWidth" : 61.557, + "placementHeight" : 1.6, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 61.557, + "contentHeight" : 1.6, + "margin" : { + "top" : 5.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/Masthead[6]/LineNode[1]", + "entityName" : null, + "entityKind" : "LineNode", + "parentPath" : "Proposal[0]/Masthead[6]", + "childIndex" : 1, + "depth" : 3, + "layer" : 3, + "computedX" : 30.0, + "computedY" : 775.788, + "placementX" : 30.0, + "placementY" : 775.788, + "placementWidth" : 535.276, + "placementHeight" : 1.0, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 535.276, + "contentHeight" : 1.0, + "margin" : { + "top" : 16.1, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/Deliverables[7]", + "entityName" : "Deliverables", + "entityKind" : "SectionNode", + "parentPath" : "Proposal[0]", + "childIndex" : 7, + "depth" : 2, + "layer" : 2, + "computedX" : 30.0, + "computedY" : 629.479, + "placementX" : 30.0, + "placementY" : 629.479, + "placementWidth" : 535.276, + "placementHeight" : 127.309, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 535.276, + "contentHeight" : 127.309, + "margin" : { + "top" : 19.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/Deliverables[7]/SectionHeading[0]", + "entityName" : "SectionHeading", + "entityKind" : "SectionNode", + "parentPath" : "Proposal[0]/Deliverables[7]", + "childIndex" : 0, + "depth" : 3, + "layer" : 3, + "computedX" : 30.0, + "computedY" : 729.379, + "placementX" : 30.0, + "placementY" : 729.379, + "placementWidth" : 93.073, + "placementHeight" : 27.409, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 93.073, + "contentHeight" : 27.409, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/Deliverables[7]/SectionHeading[0]/SectionHeadingTitle[0]", + "entityName" : "SectionHeadingTitle", + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/Deliverables[7]/SectionHeading[0]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 30.0, + "computedY" : 731.979, + "placementX" : 30.0, + "placementY" : 731.979, + "placementWidth" : 93.073, + "placementHeight" : 24.809, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 93.073, + "contentHeight" : 24.809, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/Deliverables[7]/SectionHeading[0]/SectionHeadingRule[1]", + "entityName" : "SectionHeadingRule", + "entityKind" : "LineNode", + "parentPath" : "Proposal[0]/Deliverables[7]/SectionHeading[0]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 30.0, + "computedY" : 729.379, + "placementX" : 30.0, + "placementY" : 729.379, + "placementWidth" : 25.693, + "placementHeight" : 1.6, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 25.693, + "contentHeight" : 1.6, + "margin" : { + "top" : 1.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/Deliverables[7]/DeliverableColumns[1]", + "entityName" : "DeliverableColumns", + "entityKind" : "RowNode", + "parentPath" : "Proposal[0]/Deliverables[7]", + "childIndex" : 1, + "depth" : 3, + "layer" : 3, + "computedX" : 30.0, + "computedY" : 629.479, + "placementX" : 30.0, + "placementY" : 629.479, + "placementWidth" : 535.276, + "placementHeight" : 99.9, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 535.276, + "contentHeight" : 99.9, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 9.5, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/Deliverables[7]/DeliverableColumns[1]/DeliverablesLeft[0]", + "entityName" : "DeliverablesLeft", + "entityKind" : "SectionNode", + "parentPath" : "Proposal[0]/Deliverables[7]/DeliverableColumns[1]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 30.0, + "computedY" : 629.479, + "placementX" : 30.0, + "placementY" : 629.479, + "placementWidth" : 174.6, + "placementHeight" : 90.4, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 174.6, + "contentHeight" : 90.4, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/Deliverables[7]/DeliverableColumns[1]/DeliverablesLeft[0]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/Deliverables[7]/DeliverableColumns[1]/DeliverablesLeft[0]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 30.0, + "computedY" : 707.879, + "placementX" : 30.0, + "placementY" : 707.879, + "placementWidth" : 118.56, + "placementHeight" : 12.0, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 118.56, + "contentHeight" : 12.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/Deliverables[7]/DeliverableColumns[1]/DeliverablesLeft[0]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/Deliverables[7]/DeliverableColumns[1]/DeliverablesLeft[0]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 30.0, + "computedY" : 688.279, + "placementX" : 30.0, + "placementY" : 688.279, + "placementWidth" : 174.6, + "placementHeight" : 12.0, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 174.6, + "contentHeight" : 12.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/Deliverables[7]/DeliverableColumns[1]/DeliverablesLeft[0]/ParagraphNode[2]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/Deliverables[7]/DeliverableColumns[1]/DeliverablesLeft[0]", + "childIndex" : 2, + "depth" : 5, + "layer" : 5, + "computedX" : 30.0, + "computedY" : 668.679, + "placementX" : 30.0, + "placementY" : 668.679, + "placementWidth" : 164.43, + "placementHeight" : 12.0, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 164.43, + "contentHeight" : 12.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/Deliverables[7]/DeliverableColumns[1]/DeliverablesLeft[0]/ParagraphNode[3]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/Deliverables[7]/DeliverableColumns[1]/DeliverablesLeft[0]", + "childIndex" : 3, + "depth" : 5, + "layer" : 5, + "computedX" : 30.0, + "computedY" : 649.079, + "placementX" : 30.0, + "placementY" : 649.079, + "placementWidth" : 116.23, + "placementHeight" : 12.0, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 116.23, + "contentHeight" : 12.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/Deliverables[7]/DeliverableColumns[1]/DeliverablesLeft[0]/ParagraphNode[4]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/Deliverables[7]/DeliverableColumns[1]/DeliverablesLeft[0]", + "childIndex" : 4, + "depth" : 5, + "layer" : 5, + "computedX" : 30.0, + "computedY" : 629.479, + "placementX" : 30.0, + "placementY" : 629.479, + "placementWidth" : 138.3, + "placementHeight" : 12.0, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 138.3, + "contentHeight" : 12.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/Deliverables[7]/DeliverableColumns[1]/DeliverablesRight[1]", + "entityName" : "DeliverablesRight", + "entityKind" : "SectionNode", + "parentPath" : "Proposal[0]/Deliverables[7]/DeliverableColumns[1]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 292.821, + "computedY" : 635.259, + "placementX" : 292.821, + "placementY" : 635.259, + "placementWidth" : 186.8, + "placementHeight" : 84.62, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 186.8, + "contentHeight" : 84.62, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/Deliverables[7]/DeliverableColumns[1]/DeliverablesRight[1]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/Deliverables[7]/DeliverableColumns[1]/DeliverablesRight[1]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 292.821, + "computedY" : 694.059, + "placementX" : 292.821, + "placementY" : 694.059, + "placementWidth" : 185.14, + "placementHeight" : 25.82, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 185.14, + "contentHeight" : 25.82, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/Deliverables[7]/DeliverableColumns[1]/DeliverablesRight[1]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/Deliverables[7]/DeliverableColumns[1]/DeliverablesRight[1]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 292.821, + "computedY" : 674.459, + "placementX" : 292.821, + "placementY" : 674.459, + "placementWidth" : 164.67, + "placementHeight" : 12.0, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 164.67, + "contentHeight" : 12.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/Deliverables[7]/DeliverableColumns[1]/DeliverablesRight[1]/ParagraphNode[2]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/Deliverables[7]/DeliverableColumns[1]/DeliverablesRight[1]", + "childIndex" : 2, + "depth" : 5, + "layer" : 5, + "computedX" : 292.821, + "computedY" : 654.859, + "placementX" : 292.821, + "placementY" : 654.859, + "placementWidth" : 186.8, + "placementHeight" : 12.0, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 186.8, + "contentHeight" : 12.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/Deliverables[7]/DeliverableColumns[1]/DeliverablesRight[1]/ParagraphNode[3]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/Deliverables[7]/DeliverableColumns[1]/DeliverablesRight[1]", + "childIndex" : 3, + "depth" : 5, + "layer" : 5, + "computedX" : 292.821, + "computedY" : 635.259, + "placementX" : 292.821, + "placementY" : 635.259, + "placementWidth" : 163.73, + "placementHeight" : 12.0, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 163.73, + "contentHeight" : 12.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/Timeline[8]", + "entityName" : "Timeline", + "entityKind" : "SectionNode", + "parentPath" : "Proposal[0]", + "childIndex" : 8, + "depth" : 2, + "layer" : 2, + "computedX" : 30.0, + "computedY" : 405.621, + "placementX" : 30.0, + "placementY" : 405.621, + "placementWidth" : 535.276, + "placementHeight" : 206.059, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 535.276, + "contentHeight" : 206.059, + "margin" : { + "top" : 17.8, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/Timeline[8]/SectionHeading[0]", + "entityName" : "SectionHeading", + "entityKind" : "SectionNode", + "parentPath" : "Proposal[0]/Timeline[8]", + "childIndex" : 0, + "depth" : 3, + "layer" : 3, + "computedX" : 30.0, + "computedY" : 584.271, + "placementX" : 30.0, + "placementY" : 584.271, + "placementWidth" : 68.525, + "placementHeight" : 27.409, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 68.525, + "contentHeight" : 27.409, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/Timeline[8]/SectionHeading[0]/SectionHeadingTitle[0]", + "entityName" : "SectionHeadingTitle", + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/Timeline[8]/SectionHeading[0]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 30.0, + "computedY" : 586.871, + "placementX" : 30.0, + "placementY" : 586.871, + "placementWidth" : 68.525, + "placementHeight" : 24.809, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 68.525, + "contentHeight" : 24.809, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/Timeline[8]/SectionHeading[0]/SectionHeadingRule[1]", + "entityName" : "SectionHeadingRule", + "entityKind" : "LineNode", + "parentPath" : "Proposal[0]/Timeline[8]/SectionHeading[0]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 30.0, + "computedY" : 584.271, + "placementX" : 30.0, + "placementY" : 584.271, + "placementWidth" : 25.693, + "placementHeight" : 1.6, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 25.693, + "contentHeight" : 1.6, + "margin" : { + "top" : 1.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/Timeline[8]/TimelineTable[1]", + "entityName" : "TimelineTable", + "entityKind" : "TableNode", + "parentPath" : "Proposal[0]/Timeline[8]", + "childIndex" : 1, + "depth" : 3, + "layer" : 3, + "computedX" : 30.0, + "computedY" : 405.621, + "placementX" : 30.0, + "placementY" : 405.621, + "placementWidth" : 535.276, + "placementHeight" : 164.95, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 535.276, + "contentHeight" : 164.95, + "margin" : { + "top" : 13.7, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/MoneyBand[9]", + "entityName" : "MoneyBand", + "entityKind" : "RowNode", + "parentPath" : "Proposal[0]", + "childIndex" : 9, + "depth" : 2, + "layer" : 2, + "computedX" : 30.0, + "computedY" : 164.512, + "placementX" : 30.0, + "placementY" : 164.512, + "placementWidth" : 535.276, + "placementHeight" : 223.309, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 535.276, + "contentHeight" : 223.309, + "margin" : { + "top" : 17.8, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/MoneyBand[9]/InvestmentColumn[0]", + "entityName" : "InvestmentColumn", + "entityKind" : "SectionNode", + "parentPath" : "Proposal[0]/MoneyBand[9]", + "childIndex" : 0, + "depth" : 3, + "layer" : 3, + "computedX" : 30.0, + "computedY" : 164.512, + "placementX" : 30.0, + "placementY" : 164.512, + "placementWidth" : 319.024, + "placementHeight" : 223.309, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 319.024, + "contentHeight" : 223.309, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/MoneyBand[9]/InvestmentColumn[0]/SectionHeading[0]", + "entityName" : "SectionHeading", + "entityKind" : "SectionNode", + "parentPath" : "Proposal[0]/MoneyBand[9]/InvestmentColumn[0]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 30.0, + "computedY" : 360.412, + "placementX" : 30.0, + "placementY" : 360.412, + "placementWidth" : 85.2, + "placementHeight" : 27.409, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 85.2, + "contentHeight" : 27.409, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/MoneyBand[9]/InvestmentColumn[0]/SectionHeading[0]/SectionHeadingTitle[0]", + "entityName" : "SectionHeadingTitle", + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/MoneyBand[9]/InvestmentColumn[0]/SectionHeading[0]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 30.0, + "computedY" : 363.012, + "placementX" : 30.0, + "placementY" : 363.012, + "placementWidth" : 85.2, + "placementHeight" : 24.809, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 85.2, + "contentHeight" : 24.809, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/MoneyBand[9]/InvestmentColumn[0]/SectionHeading[0]/SectionHeadingRule[1]", + "entityName" : "SectionHeadingRule", + "entityKind" : "LineNode", + "parentPath" : "Proposal[0]/MoneyBand[9]/InvestmentColumn[0]/SectionHeading[0]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 30.0, + "computedY" : 360.412, + "placementX" : 30.0, + "placementY" : 360.412, + "placementWidth" : 25.693, + "placementHeight" : 1.6, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 25.693, + "contentHeight" : 1.6, + "margin" : { + "top" : 1.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/MoneyBand[9]/InvestmentColumn[0]/InvestmentTable[1]", + "entityName" : "InvestmentTable", + "entityKind" : "TableNode", + "parentPath" : "Proposal[0]/MoneyBand[9]/InvestmentColumn[0]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 30.0, + "computedY" : 164.512, + "placementX" : 30.0, + "placementY" : 164.512, + "placementWidth" : 319.024, + "placementHeight" : 182.2, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 319.024, + "contentHeight" : 182.2, + "margin" : { + "top" : 13.7, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/MoneyBand[9]/TermsColumn[1]", + "entityName" : "TermsColumn", + "entityKind" : "SectionNode", + "parentPath" : "Proposal[0]/MoneyBand[9]", + "childIndex" : 1, + "depth" : 3, + "layer" : 3, + "computedX" : 388.635, + "computedY" : 211.012, + "placementX" : 388.635, + "placementY" : 211.012, + "placementWidth" : 176.54, + "placementHeight" : 176.809, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 176.54, + "contentHeight" : 176.809, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/MoneyBand[9]/TermsColumn[1]/SectionHeading[0]", + "entityName" : "SectionHeading", + "entityKind" : "SectionNode", + "parentPath" : "Proposal[0]/MoneyBand[9]/TermsColumn[1]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 388.635, + "computedY" : 360.412, + "placementX" : 388.635, + "placementY" : 360.412, + "placementWidth" : 48.248, + "placementHeight" : 27.409, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 48.248, + "contentHeight" : 27.409, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/MoneyBand[9]/TermsColumn[1]/SectionHeading[0]/SectionHeadingTitle[0]", + "entityName" : "SectionHeadingTitle", + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/MoneyBand[9]/TermsColumn[1]/SectionHeading[0]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 388.635, + "computedY" : 363.012, + "placementX" : 388.635, + "placementY" : 363.012, + "placementWidth" : 48.248, + "placementHeight" : 24.809, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 48.248, + "contentHeight" : 24.809, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/MoneyBand[9]/TermsColumn[1]/SectionHeading[0]/SectionHeadingRule[1]", + "entityName" : "SectionHeadingRule", + "entityKind" : "LineNode", + "parentPath" : "Proposal[0]/MoneyBand[9]/TermsColumn[1]/SectionHeading[0]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 388.635, + "computedY" : 360.412, + "placementX" : 388.635, + "placementY" : 360.412, + "placementWidth" : 25.693, + "placementHeight" : 1.6, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 25.693, + "contentHeight" : 1.6, + "margin" : { + "top" : 1.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/MoneyBand[9]/TermsColumn[1]/TermsList[1]", + "entityName" : "TermsList", + "entityKind" : "SectionNode", + "parentPath" : "Proposal[0]/MoneyBand[9]/TermsColumn[1]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 388.635, + "computedY" : 211.012, + "placementX" : 388.635, + "placementY" : 211.012, + "placementWidth" : 176.54, + "placementHeight" : 149.4, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 176.54, + "contentHeight" : 149.4, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 9.5, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/MoneyBand[9]/TermsColumn[1]/TermsList[1]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/MoneyBand[9]/TermsColumn[1]/TermsList[1]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 388.635, + "computedY" : 325.092, + "placementX" : 388.635, + "placementY" : 325.092, + "placementWidth" : 175.09, + "placementHeight" : 25.82, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 175.09, + "contentHeight" : 25.82, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/MoneyBand[9]/TermsColumn[1]/TermsList[1]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/MoneyBand[9]/TermsColumn[1]/TermsList[1]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 388.635, + "computedY" : 291.672, + "placementX" : 388.635, + "placementY" : 291.672, + "placementWidth" : 170.41, + "placementHeight" : 25.82, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 170.41, + "contentHeight" : 25.82, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/MoneyBand[9]/TermsColumn[1]/TermsList[1]/ParagraphNode[2]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/MoneyBand[9]/TermsColumn[1]/TermsList[1]", + "childIndex" : 2, + "depth" : 5, + "layer" : 5, + "computedX" : 388.635, + "computedY" : 258.252, + "placementX" : 388.635, + "placementY" : 258.252, + "placementWidth" : 176.54, + "placementHeight" : 25.82, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 176.54, + "contentHeight" : 25.82, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/MoneyBand[9]/TermsColumn[1]/TermsList[1]/ParagraphNode[3]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/MoneyBand[9]/TermsColumn[1]/TermsList[1]", + "childIndex" : 3, + "depth" : 5, + "layer" : 5, + "computedX" : 388.635, + "computedY" : 211.012, + "placementX" : 388.635, + "placementY" : 211.012, + "placementWidth" : 166.306, + "placementHeight" : 39.64, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 166.306, + "contentHeight" : 39.64, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/Acceptance[10]", + "entityName" : "Acceptance", + "entityKind" : "SectionNode", + "parentPath" : "Proposal[0]", + "childIndex" : 10, + "depth" : 2, + "layer" : 2, + "computedX" : 30.0, + "computedY" : 60.004, + "placementX" : 30.0, + "placementY" : 60.004, + "placementWidth" : 535.276, + "placementHeight" : 86.709, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 535.276, + "contentHeight" : 86.709, + "margin" : { + "top" : 17.8, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 9.0, + "right" : 9.0, + "bottom" : 9.0, + "left" : 9.0 + } + }, { + "path" : "Proposal[0]/Acceptance[10]/AcceptanceBody[0]", + "entityName" : "AcceptanceBody", + "entityKind" : "SectionNode", + "parentPath" : "Proposal[0]/Acceptance[10]", + "childIndex" : 0, + "depth" : 3, + "layer" : 3, + "computedX" : 39.0, + "computedY" : 69.004, + "placementX" : 39.0, + "placementY" : 69.004, + "placementWidth" : 517.276, + "placementHeight" : 68.709, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 517.276, + "contentHeight" : 68.709, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 9.0, + "bottom" : 0.0, + "left" : 9.0 + } + }, { + "path" : "Proposal[0]/Acceptance[10]/AcceptanceBody[0]/SectionHeading[0]", + "entityName" : "SectionHeading", + "entityKind" : "SectionNode", + "parentPath" : "Proposal[0]/Acceptance[10]/AcceptanceBody[0]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 48.0, + "computedY" : 110.304, + "placementX" : 48.0, + "placementY" : 110.304, + "placementWidth" : 86.814, + "placementHeight" : 27.409, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 86.814, + "contentHeight" : 27.409, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/Acceptance[10]/AcceptanceBody[0]/SectionHeading[0]/SectionHeadingTitle[0]", + "entityName" : "SectionHeadingTitle", + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/Acceptance[10]/AcceptanceBody[0]/SectionHeading[0]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 48.0, + "computedY" : 112.904, + "placementX" : 48.0, + "placementY" : 112.904, + "placementWidth" : 86.814, + "placementHeight" : 24.809, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 86.814, + "contentHeight" : 24.809, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/Acceptance[10]/AcceptanceBody[0]/SectionHeading[0]/SectionHeadingRule[1]", + "entityName" : "SectionHeadingRule", + "entityKind" : "LineNode", + "parentPath" : "Proposal[0]/Acceptance[10]/AcceptanceBody[0]/SectionHeading[0]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 48.0, + "computedY" : 110.304, + "placementX" : 48.0, + "placementY" : 110.304, + "placementWidth" : 25.693, + "placementHeight" : 1.6, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 25.693, + "contentHeight" : 1.6, + "margin" : { + "top" : 1.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/Acceptance[10]/AcceptanceBody[0]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/Acceptance[10]/AcceptanceBody[0]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 48.0, + "computedY" : 88.804, + "placementX" : 48.0, + "placementY" : 88.804, + "placementWidth" : 432.25, + "placementHeight" : 12.0, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 432.25, + "contentHeight" : 12.0, + "margin" : { + "top" : 9.5, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/Acceptance[10]/AcceptanceBody[0]/SignatureFields[2]", + "entityName" : "SignatureFields", + "entityKind" : "RowNode", + "parentPath" : "Proposal[0]/Acceptance[10]/AcceptanceBody[0]", + "childIndex" : 2, + "depth" : 4, + "layer" : 4, + "computedX" : 48.0, + "computedY" : 69.004, + "placementX" : 48.0, + "placementY" : 69.004, + "placementWidth" : 499.276, + "placementHeight" : 19.8, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 499.276, + "contentHeight" : 19.8, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 9.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/Acceptance[10]/AcceptanceBody[0]/SignatureFields[2]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/Acceptance[10]/AcceptanceBody[0]/SignatureFields[2]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 48.0, + "computedY" : 69.004, + "placementX" : 48.0, + "placementY" : 69.004, + "placementWidth" : 51.759, + "placementHeight" : 10.8, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 51.759, + "contentHeight" : 10.8, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/Acceptance[10]/AcceptanceBody[0]/SignatureFields[2]/LineNode[1]", + "entityName" : null, + "entityKind" : "LineNode", + "parentPath" : "Proposal[0]/Acceptance[10]/AcceptanceBody[0]/SignatureFields[2]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 103.759, + "computedY" : 69.004, + "placementX" : 103.759, + "placementY" : 69.004, + "placementWidth" : 128.36, + "placementHeight" : 1.0, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 128.36, + "contentHeight" : 1.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/Acceptance[10]/AcceptanceBody[0]/SignatureFields[2]/SpacerNode[2]", + "entityName" : null, + "entityKind" : "SpacerNode", + "parentPath" : "Proposal[0]/Acceptance[10]/AcceptanceBody[0]/SignatureFields[2]", + "childIndex" : 2, + "depth" : 5, + "layer" : 5, + "computedX" : 236.119, + "computedY" : 69.004, + "placementX" : 236.119, + "placementY" : 69.004, + "placementWidth" : 7.5, + "placementHeight" : 0.0, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 7.5, + "contentHeight" : 0.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/Acceptance[10]/AcceptanceBody[0]/SignatureFields[2]/ParagraphNode[3]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/Acceptance[10]/AcceptanceBody[0]/SignatureFields[2]", + "childIndex" : 3, + "depth" : 5, + "layer" : 5, + "computedX" : 247.619, + "computedY" : 69.004, + "placementX" : 247.619, + "placementY" : 69.004, + "placementWidth" : 39.753, + "placementHeight" : 10.8, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 39.753, + "contentHeight" : 10.8, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/Acceptance[10]/AcceptanceBody[0]/SignatureFields[2]/LineNode[4]", + "entityName" : null, + "entityKind" : "LineNode", + "parentPath" : "Proposal[0]/Acceptance[10]/AcceptanceBody[0]/SignatureFields[2]", + "childIndex" : 4, + "depth" : 5, + "layer" : 5, + "computedX" : 291.372, + "computedY" : 69.004, + "placementX" : 291.372, + "placementY" : 69.004, + "placementWidth" : 132.135, + "placementHeight" : 1.0, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 132.135, + "contentHeight" : 1.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/Acceptance[10]/AcceptanceBody[0]/SignatureFields[2]/SpacerNode[5]", + "entityName" : null, + "entityKind" : "SpacerNode", + "parentPath" : "Proposal[0]/Acceptance[10]/AcceptanceBody[0]/SignatureFields[2]", + "childIndex" : 5, + "depth" : 5, + "layer" : 5, + "computedX" : 427.508, + "computedY" : 69.004, + "placementX" : 427.508, + "placementY" : 69.004, + "placementWidth" : 7.5, + "placementHeight" : 0.0, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 7.5, + "contentHeight" : 0.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/Acceptance[10]/AcceptanceBody[0]/SignatureFields[2]/ParagraphNode[6]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/Acceptance[10]/AcceptanceBody[0]/SignatureFields[2]", + "childIndex" : 6, + "depth" : 5, + "layer" : 5, + "computedX" : 439.008, + "computedY" : 69.004, + "placementX" : 439.008, + "placementY" : 69.004, + "placementWidth" : 21.555, + "placementHeight" : 10.8, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 21.555, + "contentHeight" : 10.8, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/Acceptance[10]/AcceptanceBody[0]/SignatureFields[2]/LineNode[7]", + "entityName" : null, + "entityKind" : "LineNode", + "parentPath" : "Proposal[0]/Acceptance[10]/AcceptanceBody[0]/SignatureFields[2]", + "childIndex" : 7, + "depth" : 5, + "layer" : 5, + "computedX" : 464.563, + "computedY" : 69.004, + "placementX" : 464.563, + "placementY" : 69.004, + "placementWidth" : 82.713, + "placementHeight" : 1.0, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 82.713, + "contentHeight" : 1.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + } ] +} diff --git a/qa/src/test/resources/layout-snapshots/canonical-templates/proposal/indigo_proposal_layout.json b/qa/src/test/resources/layout-snapshots/canonical-templates/proposal/indigo_proposal_layout.json new file mode 100644 index 000000000..931929924 --- /dev/null +++ b/qa/src/test/resources/layout-snapshots/canonical-templates/proposal/indigo_proposal_layout.json @@ -0,0 +1,5477 @@ +{ + "formatVersion" : "2.0", + "canvas" : { + "pageWidth" : 595.276, + "pageHeight" : 841.89, + "innerWidth" : 544.446, + "innerHeight" : 802.355, + "margin" : { + "top" : 25.98, + "right" : 23.156, + "bottom" : 13.555, + "left" : 27.674 + } + }, + "totalPages" : 1, + "nodes" : [ { + "path" : "Proposal[0]", + "entityName" : "Proposal", + "entityKind" : "ContainerNode", + "parentPath" : null, + "childIndex" : 0, + "depth" : 1, + "layer" : 1, + "computedX" : 27.674, + "computedY" : 14.442, + "placementX" : 27.674, + "placementY" : 14.442, + "placementWidth" : 544.446, + "placementHeight" : 801.468, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 544.446, + "contentHeight" : 801.468, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/Masthead[0]", + "entityName" : "Masthead", + "entityKind" : "RowNode", + "parentPath" : "Proposal[0]", + "childIndex" : 0, + "depth" : 2, + "layer" : 2, + "computedX" : 27.674, + "computedY" : 785.505, + "placementX" : 27.674, + "placementY" : 785.505, + "placementWidth" : 544.446, + "placementHeight" : 28.3, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 544.446, + "contentHeight" : 28.3, + "margin" : { + "top" : 2.105, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/Masthead[0]/Wordmark[0]", + "entityName" : "Wordmark", + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/Masthead[0]", + "childIndex" : 0, + "depth" : 3, + "layer" : 3, + "computedX" : 27.674, + "computedY" : 785.505, + "placementX" : 27.674, + "placementY" : 785.505, + "placementWidth" : 97.267, + "placementHeight" : 28.3, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 97.267, + "contentHeight" : 28.3, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/Masthead[0]/DocumentLabel[1]", + "entityName" : "DocumentLabel", + "entityKind" : "SectionNode", + "parentPath" : "Proposal[0]/Masthead[0]", + "childIndex" : 1, + "depth" : 3, + "layer" : 3, + "computedX" : 299.897, + "computedY" : 789.166, + "placementX" : 299.897, + "placementY" : 789.166, + "placementWidth" : 272.223, + "placementHeight" : 24.638, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 272.223, + "contentHeight" : 24.638, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/Masthead[0]/DocumentLabel[1]/DocumentLabelText[0]", + "entityName" : "DocumentLabelText", + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/Masthead[0]/DocumentLabel[1]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 299.897, + "computedY" : 799.905, + "placementX" : 299.897, + "placementY" : 799.905, + "placementWidth" : 272.223, + "placementHeight" : 13.9, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 272.223, + "contentHeight" : 13.9, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/Masthead[0]/DocumentLabel[1]/DocumentLabelRule[1]", + "entityName" : "DocumentLabelRule", + "entityKind" : "LineNode", + "parentPath" : "Proposal[0]/Masthead[0]/DocumentLabel[1]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 539.927, + "computedY" : 789.166, + "placementX" : 539.927, + "placementY" : 789.166, + "placementWidth" : 32.192, + "placementHeight" : 1.694, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 32.192, + "contentHeight" : 1.694, + "margin" : { + "top" : 9.044, + "right" : 0.0, + "bottom" : 0.0, + "left" : 240.03 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/MastheadRule[1]", + "entityName" : "MastheadRule", + "entityKind" : "LineNode", + "parentPath" : "Proposal[0]", + "childIndex" : 1, + "depth" : 2, + "layer" : 2, + "computedX" : 27.674, + "computedY" : 771.987, + "placementX" : 27.674, + "placementY" : 771.987, + "placementWidth" : 544.446, + "placementHeight" : 1.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 544.446, + "contentHeight" : 1.0, + "margin" : { + "top" : 12.518, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/IntroRow[2]", + "entityName" : "IntroRow", + "entityKind" : "RowNode", + "parentPath" : "Proposal[0]", + "childIndex" : 2, + "depth" : 2, + "layer" : 2, + "computedX" : 27.674, + "computedY" : 475.902, + "placementX" : 27.674, + "placementY" : 475.902, + "placementWidth" : 544.446, + "placementHeight" : 267.769, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 544.446, + "contentHeight" : 267.769, + "margin" : { + "top" : 28.316, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/IntroRow[2]/IntroLeft[0]", + "entityName" : "IntroLeft", + "entityKind" : "SectionNode", + "parentPath" : "Proposal[0]/IntroRow[2]", + "childIndex" : 0, + "depth" : 3, + "layer" : 3, + "computedX" : 27.674, + "computedY" : 553.804, + "placementX" : 27.674, + "placementY" : 553.804, + "placementWidth" : 154.184, + "placementHeight" : 189.867, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 154.184, + "contentHeight" : 189.867, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/IntroRow[2]/IntroLeft[0]/RecipientLabel[0]", + "entityName" : "RecipientLabel", + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/IntroRow[2]/IntroLeft[0]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 27.674, + "computedY" : 735.571, + "placementX" : 27.674, + "placementY" : 735.571, + "placementWidth" : 62.564, + "placementHeight" : 8.1, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 62.564, + "contentHeight" : 8.1, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/IntroRow[2]/IntroLeft[0]/RecipientName[1]", + "entityName" : "RecipientName", + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/IntroRow[2]/IntroLeft[0]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 27.674, + "computedY" : 713.658, + "placementX" : 27.674, + "placementY" : 713.658, + "placementWidth" : 112.385, + "placementHeight" : 13.2, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 112.385, + "contentHeight" : 13.2, + "margin" : { + "top" : 8.714, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/IntroRow[2]/IntroLeft[0]/RecipientAddress_0[2]", + "entityName" : "RecipientAddress_0", + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/IntroRow[2]/IntroLeft[0]", + "childIndex" : 2, + "depth" : 4, + "layer" : 4, + "computedX" : 27.674, + "computedY" : 693.457, + "placementX" : 27.674, + "placementY" : 693.457, + "placementWidth" : 56.522, + "placementHeight" : 8.8, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 56.522, + "contentHeight" : 8.8, + "margin" : { + "top" : 11.4, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/IntroRow[2]/IntroLeft[0]/RecipientAddress_1[3]", + "entityName" : "RecipientAddress_1", + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/IntroRow[2]/IntroLeft[0]", + "childIndex" : 3, + "depth" : 4, + "layer" : 4, + "computedX" : 27.674, + "computedY" : 681.032, + "placementX" : 27.674, + "placementY" : 681.032, + "placementWidth" : 85.765, + "placementHeight" : 8.8, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 85.765, + "contentHeight" : 8.8, + "margin" : { + "top" : 3.625, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/IntroRow[2]/IntroLeft[0]/RecipientAddress_2[4]", + "entityName" : "RecipientAddress_2", + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/IntroRow[2]/IntroLeft[0]", + "childIndex" : 4, + "depth" : 4, + "layer" : 4, + "computedX" : 27.674, + "computedY" : 668.042, + "placementX" : 27.674, + "placementY" : 668.042, + "placementWidth" : 64.108, + "placementHeight" : 8.8, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 64.108, + "contentHeight" : 8.8, + "margin" : { + "top" : 4.19, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/IntroRow[2]/IntroLeft[0]/RecipientDivider[5]", + "entityName" : "RecipientDivider", + "entityKind" : "LineNode", + "parentPath" : "Proposal[0]/IntroRow[2]/IntroLeft[0]", + "childIndex" : 5, + "depth" : 4, + "layer" : 4, + "computedX" : 27.674, + "computedY" : 639.111, + "placementX" : 27.674, + "placementY" : 639.111, + "placementWidth" : 154.184, + "placementHeight" : 1.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 154.184, + "contentHeight" : 1.0, + "margin" : { + "top" : 27.931, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/IntroRow[2]/IntroLeft[0]/AttentionLabel[6]", + "entityName" : "AttentionLabel", + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/IntroRow[2]/IntroLeft[0]", + "childIndex" : 6, + "depth" : 4, + "layer" : 4, + "computedX" : 27.674, + "computedY" : 614.556, + "placementX" : 27.674, + "placementY" : 614.556, + "placementWidth" : 21.263, + "placementHeight" : 8.1, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 21.263, + "contentHeight" : 8.1, + "margin" : { + "top" : 16.455, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/IntroRow[2]/IntroLeft[0]/AttentionName[7]", + "entityName" : "AttentionName", + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/IntroRow[2]/IntroLeft[0]", + "childIndex" : 7, + "depth" : 4, + "layer" : 4, + "computedX" : 27.674, + "computedY" : 595.586, + "placementX" : 27.674, + "placementY" : 595.586, + "placementWidth" : 57.888, + "placementHeight" : 9.6, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 57.888, + "contentHeight" : 9.6, + "margin" : { + "top" : 9.37, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/IntroRow[2]/IntroLeft[0]/AttentionRole[8]", + "entityName" : "AttentionRole", + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/IntroRow[2]/IntroLeft[0]", + "childIndex" : 8, + "depth" : 4, + "layer" : 4, + "computedX" : 27.674, + "computedY" : 580.256, + "placementX" : 27.674, + "placementY" : 580.256, + "placementWidth" : 33.268, + "placementHeight" : 8.9, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 33.268, + "contentHeight" : 8.9, + "margin" : { + "top" : 6.431, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/IntroRow[2]/IntroLeft[0]/AttentionEmail[9]", + "entityName" : "AttentionEmail", + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/IntroRow[2]/IntroLeft[0]", + "childIndex" : 9, + "depth" : 4, + "layer" : 4, + "computedX" : 27.674, + "computedY" : 566.794, + "placementX" : 27.674, + "placementY" : 566.794, + "placementWidth" : 112.42, + "placementHeight" : 8.8, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 112.42, + "contentHeight" : 8.8, + "margin" : { + "top" : 4.661, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/IntroRow[2]/IntroLeft[0]/AttentionPhone[10]", + "entityName" : "AttentionPhone", + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/IntroRow[2]/IntroLeft[0]", + "childIndex" : 10, + "depth" : 4, + "layer" : 4, + "computedX" : 27.674, + "computedY" : 553.804, + "placementX" : 27.674, + "placementY" : 553.804, + "placementWidth" : 67.901, + "placementHeight" : 8.8, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 67.901, + "contentHeight" : 8.8, + "margin" : { + "top" : 4.19, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/IntroRow[2]/ColumnDividerUpper[1]", + "entityName" : "ColumnDividerUpper", + "entityKind" : "SectionNode", + "parentPath" : "Proposal[0]/IntroRow[2]", + "childIndex" : 1, + "depth" : 3, + "layer" : 3, + "computedX" : 227.041, + "computedY" : 476.891, + "placementX" : 227.041, + "placementY" : 476.891, + "placementWidth" : 1.0, + "placementHeight" : 266.781, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 1.0, + "contentHeight" : 266.781, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/IntroRow[2]/ColumnDividerUpper[1]/ColumnDividerUpperRule[0]", + "entityName" : "ColumnDividerUpperRule", + "entityKind" : "LineNode", + "parentPath" : "Proposal[0]/IntroRow[2]/ColumnDividerUpper[1]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 227.041, + "computedY" : 476.891, + "placementX" : 227.041, + "placementY" : 476.891, + "placementWidth" : 1.0, + "placementHeight" : 267.705, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 1.0, + "contentHeight" : 267.705, + "margin" : { + "top" : -0.924, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/IntroRow[2]/IntroRight[2]", + "entityName" : "IntroRight", + "entityKind" : "SectionNode", + "parentPath" : "Proposal[0]/IntroRow[2]", + "childIndex" : 2, + "depth" : 3, + "layer" : 3, + "computedX" : 249.067, + "computedY" : 475.902, + "placementX" : 249.067, + "placementY" : 475.902, + "placementWidth" : 323.053, + "placementHeight" : 267.769, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 323.053, + "contentHeight" : 267.769, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/IntroRow[2]/IntroRight[2]/HeadlineLine_0[0]", + "entityName" : "HeadlineLine_0", + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/IntroRow[2]/IntroRight[2]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 249.067, + "computedY" : 715.789, + "placementX" : 249.067, + "placementY" : 715.789, + "placementWidth" : 148.716, + "placementHeight" : 25.5, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 148.716, + "contentHeight" : 25.5, + "margin" : { + "top" : 2.382, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/IntroRow[2]/IntroRight[2]/HeadlineLine_1[1]", + "entityName" : "HeadlineLine_1", + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/IntroRow[2]/IntroRight[2]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 249.067, + "computedY" : 685.291, + "placementX" : 249.067, + "placementY" : 685.291, + "placementWidth" : 284.274, + "placementHeight" : 25.5, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 284.274, + "contentHeight" : 25.5, + "margin" : { + "top" : 4.998, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/IntroRow[2]/IntroRight[2]/HeadlineLine_2[2]", + "entityName" : "HeadlineLine_2", + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/IntroRow[2]/IntroRight[2]", + "childIndex" : 2, + "depth" : 4, + "layer" : 4, + "computedX" : 249.067, + "computedY" : 655.358, + "placementX" : 249.067, + "placementY" : 655.358, + "placementWidth" : 257.244, + "placementHeight" : 25.5, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 257.244, + "contentHeight" : 25.5, + "margin" : { + "top" : 4.433, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/IntroRow[2]/IntroRight[2]/HeadlineLine_3[3]", + "entityName" : "HeadlineLine_3", + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/IntroRow[2]/IntroRight[2]", + "childIndex" : 3, + "depth" : 4, + "layer" : 4, + "computedX" : 249.067, + "computedY" : 624.86, + "placementX" : 249.067, + "placementY" : 624.86, + "placementWidth" : 143.106, + "placementHeight" : 25.5, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 143.106, + "contentHeight" : 25.5, + "margin" : { + "top" : 4.998, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/IntroRow[2]/IntroRight[2]/HeadlineRule[4]", + "entityName" : "HeadlineRule", + "entityKind" : "LineNode", + "parentPath" : "Proposal[0]/IntroRow[2]/IntroRight[2]", + "childIndex" : 4, + "depth" : 4, + "layer" : 4, + "computedX" : 249.067, + "computedY" : 612.437, + "placementX" : 249.067, + "placementY" : 612.437, + "placementWidth" : 32.757, + "placementHeight" : 1.694, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 32.757, + "contentHeight" : 1.694, + "margin" : { + "top" : 10.729, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/IntroRow[2]/IntroRight[2]/HeadlineStandfirst[5]", + "entityName" : "HeadlineStandfirst", + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/IntroRow[2]/IntroRight[2]", + "childIndex" : 5, + "depth" : 4, + "layer" : 4, + "computedX" : 249.067, + "computedY" : 559.33, + "placementX" : 249.067, + "placementY" : 559.33, + "placementWidth" : 231.141, + "placementHeight" : 36.809, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 231.141, + "contentHeight" : 36.809, + "margin" : { + "top" : 16.298, + "right" : 91.494, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/IntroRow[2]/IntroRight[2]/MetaTilesLayer[6]", + "entityName" : "MetaTilesLayer", + "entityKind" : "LayerStackNode", + "parentPath" : "Proposal[0]/IntroRow[2]/IntroRight[2]", + "childIndex" : 6, + "depth" : 4, + "layer" : 4, + "computedX" : 249.067, + "computedY" : 475.902, + "placementX" : 249.067, + "placementY" : 475.902, + "placementWidth" : 323.053, + "placementHeight" : 83.428, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 323.053, + "contentHeight" : 83.428, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/IntroRow[2]/IntroRight[2]/MetaTilesLayer[6]/MetaTilesHolder[0]", + "entityName" : "MetaTilesHolder", + "entityKind" : "SectionNode", + "parentPath" : "Proposal[0]/IntroRow[2]/IntroRight[2]/MetaTilesLayer[6]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 249.067, + "computedY" : 475.902, + "placementX" : 249.067, + "placementY" : 475.902, + "placementWidth" : 323.053, + "placementHeight" : 83.428, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 323.053, + "contentHeight" : 83.428, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/IntroRow[2]/IntroRight[2]/MetaTilesLayer[6]/MetaTilesHolder[0]/MetaTiles[0]", + "entityName" : "MetaTiles", + "entityKind" : "RowNode", + "parentPath" : "Proposal[0]/IntroRow[2]/IntroRight[2]/MetaTilesLayer[6]/MetaTilesHolder[0]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 249.067, + "computedY" : 475.902, + "placementX" : 249.067, + "placementY" : 475.902, + "placementWidth" : 323.053, + "placementHeight" : 56.417, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 323.053, + "contentHeight" : 56.417, + "margin" : { + "top" : 27.011, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/IntroRow[2]/IntroRight[2]/MetaTilesLayer[6]/MetaTilesHolder[0]/MetaTiles[0]/MetaTile_0[0]", + "entityName" : "MetaTile_0", + "entityKind" : "SectionNode", + "parentPath" : "Proposal[0]/IntroRow[2]/IntroRight[2]/MetaTilesLayer[6]/MetaTilesHolder[0]/MetaTiles[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 249.067, + "computedY" : 475.902, + "placementX" : 249.067, + "placementY" : 475.902, + "placementWidth" : 44.749, + "placementHeight" : 56.417, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 44.749, + "contentHeight" : 56.417, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/IntroRow[2]/IntroRight[2]/MetaTilesLayer[6]/MetaTilesHolder[0]/MetaTiles[0]/MetaTile_0[0]/MetaCircle_0[0]", + "entityName" : "MetaCircle_0", + "entityKind" : "ShapeContainerNode", + "parentPath" : "Proposal[0]/IntroRow[2]/IntroRight[2]/MetaTilesLayer[6]/MetaTilesHolder[0]/MetaTiles[0]/MetaTile_0[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 249.067, + "computedY" : 507.469, + "placementX" : 249.067, + "placementY" : 507.469, + "placementWidth" : 24.85, + "placementHeight" : 24.85, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 24.85, + "contentHeight" : 24.85, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/IntroRow[2]/IntroRight[2]/MetaTilesLayer[6]/MetaTilesHolder[0]/MetaTiles[0]/MetaTile_0[0]/MetaCircle_0[0]/dateGlyph[0]", + "entityName" : "dateGlyph", + "entityKind" : "SectionNode", + "parentPath" : "Proposal[0]/IntroRow[2]/IntroRight[2]/MetaTilesLayer[6]/MetaTilesHolder[0]/MetaTiles[0]/MetaTile_0[0]/MetaCircle_0[0]", + "childIndex" : 0, + "depth" : 9, + "layer" : 9, + "computedX" : 249.067, + "computedY" : 514.246, + "placementX" : 249.067, + "placementY" : 514.246, + "placementWidth" : 24.85, + "placementHeight" : 11.296, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 24.85, + "contentHeight" : 11.296, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/IntroRow[2]/IntroRight[2]/MetaTilesLayer[6]/MetaTilesHolder[0]/MetaTiles[0]/MetaTile_0[0]/MetaCircle_0[0]/dateGlyph[0]/Align[0]", + "entityName" : null, + "entityKind" : "Align", + "parentPath" : "Proposal[0]/IntroRow[2]/IntroRight[2]/MetaTilesLayer[6]/MetaTilesHolder[0]/MetaTiles[0]/MetaTile_0[0]/MetaCircle_0[0]/dateGlyph[0]", + "childIndex" : 0, + "depth" : 10, + "layer" : 10, + "computedX" : 249.067, + "computedY" : 514.246, + "placementX" : 249.067, + "placementY" : 514.246, + "placementWidth" : 24.85, + "placementHeight" : 11.296, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 24.85, + "contentHeight" : 11.296, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/IntroRow[2]/IntroRight[2]/MetaTilesLayer[6]/MetaTilesHolder[0]/MetaTiles[0]/MetaTile_0[0]/MetaCircle_0[0]/dateGlyph[0]/Align[0]/SvgIcon[0]", + "entityName" : "SvgIcon", + "entityKind" : "LayerStackNode", + "parentPath" : "Proposal[0]/IntroRow[2]/IntroRight[2]/MetaTilesLayer[6]/MetaTilesHolder[0]/MetaTiles[0]/MetaTile_0[0]/MetaCircle_0[0]/dateGlyph[0]/Align[0]", + "childIndex" : 0, + "depth" : 11, + "layer" : 11, + "computedX" : 255.844, + "computedY" : 514.246, + "placementX" : 255.844, + "placementY" : 514.246, + "placementWidth" : 11.296, + "placementHeight" : 11.296, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 11.296, + "contentHeight" : 11.296, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/IntroRow[2]/IntroRight[2]/MetaTilesLayer[6]/MetaTilesHolder[0]/MetaTiles[0]/MetaTile_0[0]/MetaCircle_0[0]/dateGlyph[0]/Align[0]/SvgIcon[0]/SvgLayer0[0]", + "entityName" : "SvgLayer0", + "entityKind" : "Path", + "parentPath" : "Proposal[0]/IntroRow[2]/IntroRight[2]/MetaTilesLayer[6]/MetaTilesHolder[0]/MetaTiles[0]/MetaTile_0[0]/MetaCircle_0[0]/dateGlyph[0]/Align[0]/SvgIcon[0]", + "childIndex" : 0, + "depth" : 12, + "layer" : 12, + "computedX" : 255.844, + "computedY" : 514.246, + "placementX" : 255.844, + "placementY" : 514.246, + "placementWidth" : 11.296, + "placementHeight" : 11.296, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 11.296, + "contentHeight" : 11.296, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/IntroRow[2]/IntroRight[2]/MetaTilesLayer[6]/MetaTilesHolder[0]/MetaTiles[0]/MetaTile_0[0]/MetaLabel_0[1]", + "entityName" : "MetaLabel_0", + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/IntroRow[2]/IntroRight[2]/MetaTilesLayer[6]/MetaTilesHolder[0]/MetaTiles[0]/MetaTile_0[0]", + "childIndex" : 1, + "depth" : 8, + "layer" : 8, + "computedX" : 249.067, + "computedY" : 489.261, + "placementX" : 249.067, + "placementY" : 489.261, + "placementWidth" : 16.796, + "placementHeight" : 6.8, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 16.796, + "contentHeight" : 6.8, + "margin" : { + "top" : 11.408, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/IntroRow[2]/IntroRight[2]/MetaTilesLayer[6]/MetaTilesHolder[0]/MetaTiles[0]/MetaTile_0[0]/MetaValue_0[2]", + "entityName" : "MetaValue_0", + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/IntroRow[2]/IntroRight[2]/MetaTilesLayer[6]/MetaTilesHolder[0]/MetaTiles[0]/MetaTile_0[0]", + "childIndex" : 2, + "depth" : 8, + "layer" : 8, + "computedX" : 249.067, + "computedY" : 475.902, + "placementX" : 249.067, + "placementY" : 475.902, + "placementWidth" : 44.749, + "placementHeight" : 7.8, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 44.749, + "contentHeight" : 7.8, + "margin" : { + "top" : 5.559, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/IntroRow[2]/IntroRight[2]/MetaTilesLayer[6]/MetaTilesHolder[0]/MetaTiles[0]/MetaTile_1[1]", + "entityName" : "MetaTile_1", + "entityKind" : "SectionNode", + "parentPath" : "Proposal[0]/IntroRow[2]/IntroRight[2]/MetaTilesLayer[6]/MetaTilesHolder[0]/MetaTiles[0]", + "childIndex" : 1, + "depth" : 7, + "layer" : 7, + "computedX" : 322.488, + "computedY" : 475.902, + "placementX" : 322.488, + "placementY" : 475.902, + "placementWidth" : 70.769, + "placementHeight" : 56.417, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 70.769, + "contentHeight" : 56.417, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/IntroRow[2]/IntroRight[2]/MetaTilesLayer[6]/MetaTilesHolder[0]/MetaTiles[0]/MetaTile_1[1]/MetaCircle_1[0]", + "entityName" : "MetaCircle_1", + "entityKind" : "ShapeContainerNode", + "parentPath" : "Proposal[0]/IntroRow[2]/IntroRight[2]/MetaTilesLayer[6]/MetaTilesHolder[0]/MetaTiles[0]/MetaTile_1[1]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 322.488, + "computedY" : 507.469, + "placementX" : 322.488, + "placementY" : 507.469, + "placementWidth" : 24.85, + "placementHeight" : 24.85, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 24.85, + "contentHeight" : 24.85, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/IntroRow[2]/IntroRight[2]/MetaTilesLayer[6]/MetaTilesHolder[0]/MetaTiles[0]/MetaTile_1[1]/MetaCircle_1[0]/identifierGlyph[0]", + "entityName" : "identifierGlyph", + "entityKind" : "SectionNode", + "parentPath" : "Proposal[0]/IntroRow[2]/IntroRight[2]/MetaTilesLayer[6]/MetaTilesHolder[0]/MetaTiles[0]/MetaTile_1[1]/MetaCircle_1[0]", + "childIndex" : 0, + "depth" : 9, + "layer" : 9, + "computedX" : 322.488, + "computedY" : 514.246, + "placementX" : 322.488, + "placementY" : 514.246, + "placementWidth" : 24.85, + "placementHeight" : 11.296, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 24.85, + "contentHeight" : 11.296, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/IntroRow[2]/IntroRight[2]/MetaTilesLayer[6]/MetaTilesHolder[0]/MetaTiles[0]/MetaTile_1[1]/MetaCircle_1[0]/identifierGlyph[0]/Align[0]", + "entityName" : null, + "entityKind" : "Align", + "parentPath" : "Proposal[0]/IntroRow[2]/IntroRight[2]/MetaTilesLayer[6]/MetaTilesHolder[0]/MetaTiles[0]/MetaTile_1[1]/MetaCircle_1[0]/identifierGlyph[0]", + "childIndex" : 0, + "depth" : 10, + "layer" : 10, + "computedX" : 322.488, + "computedY" : 514.246, + "placementX" : 322.488, + "placementY" : 514.246, + "placementWidth" : 24.85, + "placementHeight" : 11.296, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 24.85, + "contentHeight" : 11.296, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/IntroRow[2]/IntroRight[2]/MetaTilesLayer[6]/MetaTilesHolder[0]/MetaTiles[0]/MetaTile_1[1]/MetaCircle_1[0]/identifierGlyph[0]/Align[0]/SvgIcon[0]", + "entityName" : "SvgIcon", + "entityKind" : "LayerStackNode", + "parentPath" : "Proposal[0]/IntroRow[2]/IntroRight[2]/MetaTilesLayer[6]/MetaTilesHolder[0]/MetaTiles[0]/MetaTile_1[1]/MetaCircle_1[0]/identifierGlyph[0]/Align[0]", + "childIndex" : 0, + "depth" : 11, + "layer" : 11, + "computedX" : 329.265, + "computedY" : 514.246, + "placementX" : 329.265, + "placementY" : 514.246, + "placementWidth" : 11.296, + "placementHeight" : 11.296, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 11.296, + "contentHeight" : 11.296, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/IntroRow[2]/IntroRight[2]/MetaTilesLayer[6]/MetaTilesHolder[0]/MetaTiles[0]/MetaTile_1[1]/MetaCircle_1[0]/identifierGlyph[0]/Align[0]/SvgIcon[0]/SvgLayer0[0]", + "entityName" : "SvgLayer0", + "entityKind" : "Path", + "parentPath" : "Proposal[0]/IntroRow[2]/IntroRight[2]/MetaTilesLayer[6]/MetaTilesHolder[0]/MetaTiles[0]/MetaTile_1[1]/MetaCircle_1[0]/identifierGlyph[0]/Align[0]/SvgIcon[0]", + "childIndex" : 0, + "depth" : 12, + "layer" : 12, + "computedX" : 329.265, + "computedY" : 514.246, + "placementX" : 329.265, + "placementY" : 514.246, + "placementWidth" : 11.296, + "placementHeight" : 11.296, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 11.296, + "contentHeight" : 11.296, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/IntroRow[2]/IntroRight[2]/MetaTilesLayer[6]/MetaTilesHolder[0]/MetaTiles[0]/MetaTile_1[1]/MetaLabel_1[1]", + "entityName" : "MetaLabel_1", + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/IntroRow[2]/IntroRight[2]/MetaTilesLayer[6]/MetaTilesHolder[0]/MetaTiles[0]/MetaTile_1[1]", + "childIndex" : 1, + "depth" : 8, + "layer" : 8, + "computedX" : 322.488, + "computedY" : 489.261, + "placementX" : 322.488, + "placementY" : 489.261, + "placementWidth" : 43.214, + "placementHeight" : 6.8, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 43.214, + "contentHeight" : 6.8, + "margin" : { + "top" : 11.408, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/IntroRow[2]/IntroRight[2]/MetaTilesLayer[6]/MetaTilesHolder[0]/MetaTiles[0]/MetaTile_1[1]/MetaValue_1[2]", + "entityName" : "MetaValue_1", + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/IntroRow[2]/IntroRight[2]/MetaTilesLayer[6]/MetaTilesHolder[0]/MetaTiles[0]/MetaTile_1[1]", + "childIndex" : 2, + "depth" : 8, + "layer" : 8, + "computedX" : 322.488, + "computedY" : 475.902, + "placementX" : 322.488, + "placementY" : 475.902, + "placementWidth" : 70.769, + "placementHeight" : 7.8, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 70.769, + "contentHeight" : 7.8, + "margin" : { + "top" : 5.559, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/IntroRow[2]/IntroRight[2]/MetaTilesLayer[6]/MetaTilesHolder[0]/MetaTiles[0]/MetaTile_2[2]", + "entityName" : "MetaTile_2", + "entityKind" : "SectionNode", + "parentPath" : "Proposal[0]/IntroRow[2]/IntroRight[2]/MetaTilesLayer[6]/MetaTilesHolder[0]/MetaTiles[0]", + "childIndex" : 2, + "depth" : 7, + "layer" : 7, + "computedX" : 402.122, + "computedY" : 475.902, + "placementX" : 402.122, + "placementY" : 475.902, + "placementWidth" : 46.722, + "placementHeight" : 56.417, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 46.722, + "contentHeight" : 56.417, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/IntroRow[2]/IntroRight[2]/MetaTilesLayer[6]/MetaTilesHolder[0]/MetaTiles[0]/MetaTile_2[2]/MetaCircle_2[0]", + "entityName" : "MetaCircle_2", + "entityKind" : "ShapeContainerNode", + "parentPath" : "Proposal[0]/IntroRow[2]/IntroRight[2]/MetaTilesLayer[6]/MetaTilesHolder[0]/MetaTiles[0]/MetaTile_2[2]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 402.122, + "computedY" : 507.469, + "placementX" : 402.122, + "placementY" : 507.469, + "placementWidth" : 24.85, + "placementHeight" : 24.85, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 24.85, + "contentHeight" : 24.85, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/IntroRow[2]/IntroRight[2]/MetaTilesLayer[6]/MetaTilesHolder[0]/MetaTiles[0]/MetaTile_2[2]/MetaCircle_2[0]/validGlyph[0]", + "entityName" : "validGlyph", + "entityKind" : "SectionNode", + "parentPath" : "Proposal[0]/IntroRow[2]/IntroRight[2]/MetaTilesLayer[6]/MetaTilesHolder[0]/MetaTiles[0]/MetaTile_2[2]/MetaCircle_2[0]", + "childIndex" : 0, + "depth" : 9, + "layer" : 9, + "computedX" : 402.122, + "computedY" : 514.246, + "placementX" : 402.122, + "placementY" : 514.246, + "placementWidth" : 24.85, + "placementHeight" : 11.296, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 24.85, + "contentHeight" : 11.296, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/IntroRow[2]/IntroRight[2]/MetaTilesLayer[6]/MetaTilesHolder[0]/MetaTiles[0]/MetaTile_2[2]/MetaCircle_2[0]/validGlyph[0]/Align[0]", + "entityName" : null, + "entityKind" : "Align", + "parentPath" : "Proposal[0]/IntroRow[2]/IntroRight[2]/MetaTilesLayer[6]/MetaTilesHolder[0]/MetaTiles[0]/MetaTile_2[2]/MetaCircle_2[0]/validGlyph[0]", + "childIndex" : 0, + "depth" : 10, + "layer" : 10, + "computedX" : 402.122, + "computedY" : 514.246, + "placementX" : 402.122, + "placementY" : 514.246, + "placementWidth" : 24.85, + "placementHeight" : 11.296, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 24.85, + "contentHeight" : 11.296, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/IntroRow[2]/IntroRight[2]/MetaTilesLayer[6]/MetaTilesHolder[0]/MetaTiles[0]/MetaTile_2[2]/MetaCircle_2[0]/validGlyph[0]/Align[0]/SvgIcon[0]", + "entityName" : "SvgIcon", + "entityKind" : "LayerStackNode", + "parentPath" : "Proposal[0]/IntroRow[2]/IntroRight[2]/MetaTilesLayer[6]/MetaTilesHolder[0]/MetaTiles[0]/MetaTile_2[2]/MetaCircle_2[0]/validGlyph[0]/Align[0]", + "childIndex" : 0, + "depth" : 11, + "layer" : 11, + "computedX" : 408.899, + "computedY" : 514.246, + "placementX" : 408.899, + "placementY" : 514.246, + "placementWidth" : 11.296, + "placementHeight" : 11.296, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 11.296, + "contentHeight" : 11.296, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/IntroRow[2]/IntroRight[2]/MetaTilesLayer[6]/MetaTilesHolder[0]/MetaTiles[0]/MetaTile_2[2]/MetaCircle_2[0]/validGlyph[0]/Align[0]/SvgIcon[0]/SvgLayer0[0]", + "entityName" : "SvgLayer0", + "entityKind" : "Path", + "parentPath" : "Proposal[0]/IntroRow[2]/IntroRight[2]/MetaTilesLayer[6]/MetaTilesHolder[0]/MetaTiles[0]/MetaTile_2[2]/MetaCircle_2[0]/validGlyph[0]/Align[0]/SvgIcon[0]", + "childIndex" : 0, + "depth" : 12, + "layer" : 12, + "computedX" : 408.899, + "computedY" : 514.246, + "placementX" : 408.899, + "placementY" : 514.246, + "placementWidth" : 11.296, + "placementHeight" : 11.296, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 11.296, + "contentHeight" : 11.296, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/IntroRow[2]/IntroRight[2]/MetaTilesLayer[6]/MetaTilesHolder[0]/MetaTiles[0]/MetaTile_2[2]/MetaLabel_2[1]", + "entityName" : "MetaLabel_2", + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/IntroRow[2]/IntroRight[2]/MetaTilesLayer[6]/MetaTilesHolder[0]/MetaTiles[0]/MetaTile_2[2]", + "childIndex" : 1, + "depth" : 8, + "layer" : 8, + "computedX" : 402.122, + "computedY" : 489.261, + "placementX" : 402.122, + "placementY" : 489.261, + "placementWidth" : 39.175, + "placementHeight" : 6.8, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 39.175, + "contentHeight" : 6.8, + "margin" : { + "top" : 11.408, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/IntroRow[2]/IntroRight[2]/MetaTilesLayer[6]/MetaTilesHolder[0]/MetaTiles[0]/MetaTile_2[2]/MetaValue_2[2]", + "entityName" : "MetaValue_2", + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/IntroRow[2]/IntroRight[2]/MetaTilesLayer[6]/MetaTilesHolder[0]/MetaTiles[0]/MetaTile_2[2]", + "childIndex" : 2, + "depth" : 8, + "layer" : 8, + "computedX" : 402.122, + "computedY" : 475.902, + "placementX" : 402.122, + "placementY" : 475.902, + "placementWidth" : 46.722, + "placementHeight" : 7.8, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 46.722, + "contentHeight" : 7.8, + "margin" : { + "top" : 5.559, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/IntroRow[2]/IntroRight[2]/MetaTilesLayer[6]/MetaTilesHolder[0]/MetaTiles[0]/MetaTile_3[3]", + "entityName" : "MetaTile_3", + "entityKind" : "SectionNode", + "parentPath" : "Proposal[0]/IntroRow[2]/IntroRight[2]/MetaTilesLayer[6]/MetaTilesHolder[0]/MetaTiles[0]", + "childIndex" : 3, + "depth" : 7, + "layer" : 7, + "computedX" : 486.838, + "computedY" : 475.902, + "placementX" : 486.838, + "placementY" : 475.902, + "placementWidth" : 79.895, + "placementHeight" : 56.417, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 79.895, + "contentHeight" : 56.417, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/IntroRow[2]/IntroRight[2]/MetaTilesLayer[6]/MetaTilesHolder[0]/MetaTiles[0]/MetaTile_3[3]/MetaCircle_3[0]", + "entityName" : "MetaCircle_3", + "entityKind" : "ShapeContainerNode", + "parentPath" : "Proposal[0]/IntroRow[2]/IntroRight[2]/MetaTilesLayer[6]/MetaTilesHolder[0]/MetaTiles[0]/MetaTile_3[3]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 486.838, + "computedY" : 507.469, + "placementX" : 486.838, + "placementY" : 507.469, + "placementWidth" : 24.85, + "placementHeight" : 24.85, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 24.85, + "contentHeight" : 24.85, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/IntroRow[2]/IntroRight[2]/MetaTilesLayer[6]/MetaTilesHolder[0]/MetaTiles[0]/MetaTile_3[3]/MetaCircle_3[0]/prepared-byGlyph[0]", + "entityName" : "prepared-byGlyph", + "entityKind" : "SectionNode", + "parentPath" : "Proposal[0]/IntroRow[2]/IntroRight[2]/MetaTilesLayer[6]/MetaTilesHolder[0]/MetaTiles[0]/MetaTile_3[3]/MetaCircle_3[0]", + "childIndex" : 0, + "depth" : 9, + "layer" : 9, + "computedX" : 486.838, + "computedY" : 514.246, + "placementX" : 486.838, + "placementY" : 514.246, + "placementWidth" : 24.85, + "placementHeight" : 11.296, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 24.85, + "contentHeight" : 11.296, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/IntroRow[2]/IntroRight[2]/MetaTilesLayer[6]/MetaTilesHolder[0]/MetaTiles[0]/MetaTile_3[3]/MetaCircle_3[0]/prepared-byGlyph[0]/Align[0]", + "entityName" : null, + "entityKind" : "Align", + "parentPath" : "Proposal[0]/IntroRow[2]/IntroRight[2]/MetaTilesLayer[6]/MetaTilesHolder[0]/MetaTiles[0]/MetaTile_3[3]/MetaCircle_3[0]/prepared-byGlyph[0]", + "childIndex" : 0, + "depth" : 10, + "layer" : 10, + "computedX" : 486.838, + "computedY" : 514.246, + "placementX" : 486.838, + "placementY" : 514.246, + "placementWidth" : 24.85, + "placementHeight" : 11.296, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 24.85, + "contentHeight" : 11.296, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/IntroRow[2]/IntroRight[2]/MetaTilesLayer[6]/MetaTilesHolder[0]/MetaTiles[0]/MetaTile_3[3]/MetaCircle_3[0]/prepared-byGlyph[0]/Align[0]/SvgIcon[0]", + "entityName" : "SvgIcon", + "entityKind" : "LayerStackNode", + "parentPath" : "Proposal[0]/IntroRow[2]/IntroRight[2]/MetaTilesLayer[6]/MetaTilesHolder[0]/MetaTiles[0]/MetaTile_3[3]/MetaCircle_3[0]/prepared-byGlyph[0]/Align[0]", + "childIndex" : 0, + "depth" : 11, + "layer" : 11, + "computedX" : 493.616, + "computedY" : 514.246, + "placementX" : 493.616, + "placementY" : 514.246, + "placementWidth" : 11.296, + "placementHeight" : 11.296, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 11.296, + "contentHeight" : 11.296, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/IntroRow[2]/IntroRight[2]/MetaTilesLayer[6]/MetaTilesHolder[0]/MetaTiles[0]/MetaTile_3[3]/MetaCircle_3[0]/prepared-byGlyph[0]/Align[0]/SvgIcon[0]/SvgLayer0[0]", + "entityName" : "SvgLayer0", + "entityKind" : "Path", + "parentPath" : "Proposal[0]/IntroRow[2]/IntroRight[2]/MetaTilesLayer[6]/MetaTilesHolder[0]/MetaTiles[0]/MetaTile_3[3]/MetaCircle_3[0]/prepared-byGlyph[0]/Align[0]/SvgIcon[0]", + "childIndex" : 0, + "depth" : 12, + "layer" : 12, + "computedX" : 493.616, + "computedY" : 514.246, + "placementX" : 493.616, + "placementY" : 514.246, + "placementWidth" : 11.296, + "placementHeight" : 11.296, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 11.296, + "contentHeight" : 11.296, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/IntroRow[2]/IntroRight[2]/MetaTilesLayer[6]/MetaTilesHolder[0]/MetaTiles[0]/MetaTile_3[3]/MetaLabel_3[1]", + "entityName" : "MetaLabel_3", + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/IntroRow[2]/IntroRight[2]/MetaTilesLayer[6]/MetaTilesHolder[0]/MetaTiles[0]/MetaTile_3[3]", + "childIndex" : 1, + "depth" : 8, + "layer" : 8, + "computedX" : 486.838, + "computedY" : 489.261, + "placementX" : 486.838, + "placementY" : 489.261, + "placementWidth" : 45.145, + "placementHeight" : 6.8, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 45.145, + "contentHeight" : 6.8, + "margin" : { + "top" : 11.408, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/IntroRow[2]/IntroRight[2]/MetaTilesLayer[6]/MetaTilesHolder[0]/MetaTiles[0]/MetaTile_3[3]/MetaValue_3[2]", + "entityName" : "MetaValue_3", + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/IntroRow[2]/IntroRight[2]/MetaTilesLayer[6]/MetaTilesHolder[0]/MetaTiles[0]/MetaTile_3[3]", + "childIndex" : 2, + "depth" : 8, + "layer" : 8, + "computedX" : 486.838, + "computedY" : 475.902, + "placementX" : 486.838, + "placementY" : 475.902, + "placementWidth" : 79.895, + "placementHeight" : 7.8, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 79.895, + "contentHeight" : 7.8, + "margin" : { + "top" : 5.559, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/AboutBand[3]", + "entityName" : "AboutBand", + "entityKind" : "SectionNode", + "parentPath" : "Proposal[0]", + "childIndex" : 3, + "depth" : 2, + "layer" : 2, + "computedX" : 0.0, + "computedY" : 287.77, + "placementX" : 0.0, + "placementY" : 287.77, + "placementWidth" : 595.276, + "placementHeight" : 173.387, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 595.276, + "contentHeight" : 173.387, + "margin" : { + "top" : 14.745, + "right" : -23.156, + "bottom" : 0.0, + "left" : -27.674 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 27.674 + } + }, { + "path" : "Proposal[0]/AboutBand[3]/AboutRow[0]", + "entityName" : "AboutRow", + "entityKind" : "RowNode", + "parentPath" : "Proposal[0]/AboutBand[3]", + "childIndex" : 0, + "depth" : 3, + "layer" : 3, + "computedX" : 27.674, + "computedY" : 287.77, + "placementX" : 27.674, + "placementY" : 287.77, + "placementWidth" : 567.602, + "placementHeight" : 173.387, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 567.602, + "contentHeight" : 173.387, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/AboutBand[3]/AboutRow[0]/AboutLeft[0]", + "entityName" : "AboutLeft", + "entityKind" : "SectionNode", + "parentPath" : "Proposal[0]/AboutBand[3]/AboutRow[0]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 27.674, + "computedY" : 307.459, + "placementX" : 27.674, + "placementY" : 307.459, + "placementWidth" : 237.771, + "placementHeight" : 153.698, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 237.771, + "contentHeight" : 153.698, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/AboutBand[3]/AboutRow[0]/AboutLeft[0]/AboutHeading[0]", + "entityName" : "AboutHeading", + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/AboutBand[3]/AboutRow[0]/AboutLeft[0]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 27.674, + "computedY" : 432.931, + "placementX" : 27.674, + "placementY" : 432.931, + "placementWidth" : 108.872, + "placementHeight" : 8.1, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 108.872, + "contentHeight" : 8.1, + "margin" : { + "top" : 20.126, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/AboutBand[3]/AboutRow[0]/AboutLeft[0]/AboutBody[1]", + "entityName" : "AboutBody", + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/AboutBand[3]/AboutRow[0]/AboutLeft[0]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 27.674, + "computedY" : 381.584, + "placementX" : 27.674, + "placementY" : 381.584, + "placementWidth" : 213.556, + "placementHeight" : 41.687, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 213.556, + "contentHeight" : 41.687, + "margin" : { + "top" : 9.66, + "right" : 22.026, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/AboutBand[3]/AboutRow[0]/AboutLeft[0]/FeatureTilesLayer[2]", + "entityName" : "FeatureTilesLayer", + "entityKind" : "LayerStackNode", + "parentPath" : "Proposal[0]/AboutBand[3]/AboutRow[0]/AboutLeft[0]", + "childIndex" : 2, + "depth" : 5, + "layer" : 5, + "computedX" : 27.674, + "computedY" : 307.459, + "placementX" : 27.674, + "placementY" : 307.459, + "placementWidth" : 237.771, + "placementHeight" : 74.125, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 237.771, + "contentHeight" : 74.125, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/AboutBand[3]/AboutRow[0]/AboutLeft[0]/FeatureTilesLayer[2]/FeatureTilesHolder[0]", + "entityName" : "FeatureTilesHolder", + "entityKind" : "SectionNode", + "parentPath" : "Proposal[0]/AboutBand[3]/AboutRow[0]/AboutLeft[0]/FeatureTilesLayer[2]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 27.674, + "computedY" : 307.459, + "placementX" : 27.674, + "placementY" : 307.459, + "placementWidth" : 237.771, + "placementHeight" : 74.125, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 237.771, + "contentHeight" : 74.125, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/AboutBand[3]/AboutRow[0]/AboutLeft[0]/FeatureTilesLayer[2]/FeatureTilesHolder[0]/FeatureTiles[0]", + "entityName" : "FeatureTiles", + "entityKind" : "RowNode", + "parentPath" : "Proposal[0]/AboutBand[3]/AboutRow[0]/AboutLeft[0]/FeatureTilesLayer[2]/FeatureTilesHolder[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 27.674, + "computedY" : 307.459, + "placementX" : 27.674, + "placementY" : 307.459, + "placementWidth" : 237.771, + "placementHeight" : 54.251, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 237.771, + "contentHeight" : 54.251, + "margin" : { + "top" : 19.874, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/AboutBand[3]/AboutRow[0]/AboutLeft[0]/FeatureTilesLayer[2]/FeatureTilesHolder[0]/FeatureTiles[0]/FeatureCell_0[0]", + "entityName" : "FeatureCell_0", + "entityKind" : "SectionNode", + "parentPath" : "Proposal[0]/AboutBand[3]/AboutRow[0]/AboutLeft[0]/FeatureTilesLayer[2]/FeatureTilesHolder[0]/FeatureTiles[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 27.674, + "computedY" : 307.459, + "placementX" : 27.674, + "placementY" : 307.459, + "placementWidth" : 27.109, + "placementHeight" : 54.251, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 27.109, + "contentHeight" : 54.251, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/AboutBand[3]/AboutRow[0]/AboutLeft[0]/FeatureTilesLayer[2]/FeatureTilesHolder[0]/FeatureTiles[0]/FeatureCell_0[0]/FeatureTile_0[0]", + "entityName" : "FeatureTile_0", + "entityKind" : "ShapeContainerNode", + "parentPath" : "Proposal[0]/AboutBand[3]/AboutRow[0]/AboutLeft[0]/FeatureTilesLayer[2]/FeatureTilesHolder[0]/FeatureTiles[0]/FeatureCell_0[0]", + "childIndex" : 0, + "depth" : 9, + "layer" : 9, + "computedX" : 27.674, + "computedY" : 334.601, + "placementX" : 27.674, + "placementY" : 334.601, + "placementWidth" : 27.109, + "placementHeight" : 27.109, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 27.109, + "contentHeight" : 27.109, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/AboutBand[3]/AboutRow[0]/AboutLeft[0]/FeatureTilesLayer[2]/FeatureTilesHolder[0]/FeatureTiles[0]/FeatureCell_0[0]/FeatureTile_0[0]/globalGlyph[0]", + "entityName" : "globalGlyph", + "entityKind" : "SectionNode", + "parentPath" : "Proposal[0]/AboutBand[3]/AboutRow[0]/AboutLeft[0]/FeatureTilesLayer[2]/FeatureTilesHolder[0]/FeatureTiles[0]/FeatureCell_0[0]/FeatureTile_0[0]", + "childIndex" : 0, + "depth" : 10, + "layer" : 10, + "computedX" : 27.674, + "computedY" : 341.378, + "placementX" : 27.674, + "placementY" : 341.378, + "placementWidth" : 27.109, + "placementHeight" : 13.555, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 27.109, + "contentHeight" : 13.555, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/AboutBand[3]/AboutRow[0]/AboutLeft[0]/FeatureTilesLayer[2]/FeatureTilesHolder[0]/FeatureTiles[0]/FeatureCell_0[0]/FeatureTile_0[0]/globalGlyph[0]/Align[0]", + "entityName" : null, + "entityKind" : "Align", + "parentPath" : "Proposal[0]/AboutBand[3]/AboutRow[0]/AboutLeft[0]/FeatureTilesLayer[2]/FeatureTilesHolder[0]/FeatureTiles[0]/FeatureCell_0[0]/FeatureTile_0[0]/globalGlyph[0]", + "childIndex" : 0, + "depth" : 11, + "layer" : 11, + "computedX" : 27.674, + "computedY" : 341.378, + "placementX" : 27.674, + "placementY" : 341.378, + "placementWidth" : 27.109, + "placementHeight" : 13.555, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 27.109, + "contentHeight" : 13.555, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/AboutBand[3]/AboutRow[0]/AboutLeft[0]/FeatureTilesLayer[2]/FeatureTilesHolder[0]/FeatureTiles[0]/FeatureCell_0[0]/FeatureTile_0[0]/globalGlyph[0]/Align[0]/SvgIcon[0]", + "entityName" : "SvgIcon", + "entityKind" : "LayerStackNode", + "parentPath" : "Proposal[0]/AboutBand[3]/AboutRow[0]/AboutLeft[0]/FeatureTilesLayer[2]/FeatureTilesHolder[0]/FeatureTiles[0]/FeatureCell_0[0]/FeatureTile_0[0]/globalGlyph[0]/Align[0]", + "childIndex" : 0, + "depth" : 12, + "layer" : 12, + "computedX" : 34.451, + "computedY" : 341.378, + "placementX" : 34.451, + "placementY" : 341.378, + "placementWidth" : 13.555, + "placementHeight" : 13.555, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 13.555, + "contentHeight" : 13.555, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/AboutBand[3]/AboutRow[0]/AboutLeft[0]/FeatureTilesLayer[2]/FeatureTilesHolder[0]/FeatureTiles[0]/FeatureCell_0[0]/FeatureTile_0[0]/globalGlyph[0]/Align[0]/SvgIcon[0]/SvgLayer0[0]", + "entityName" : "SvgLayer0", + "entityKind" : "Path", + "parentPath" : "Proposal[0]/AboutBand[3]/AboutRow[0]/AboutLeft[0]/FeatureTilesLayer[2]/FeatureTilesHolder[0]/FeatureTiles[0]/FeatureCell_0[0]/FeatureTile_0[0]/globalGlyph[0]/Align[0]/SvgIcon[0]", + "childIndex" : 0, + "depth" : 13, + "layer" : 13, + "computedX" : 34.451, + "computedY" : 341.378, + "placementX" : 34.451, + "placementY" : 341.378, + "placementWidth" : 13.555, + "placementHeight" : 13.555, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 13.555, + "contentHeight" : 13.555, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/AboutBand[3]/AboutRow[0]/AboutLeft[0]/FeatureTilesLayer[2]/FeatureTilesHolder[0]/FeatureTiles[0]/FeatureCell_0[0]/FeatureTile_0[0]/globalGlyph[0]/Align[0]/SvgIcon[0]/SvgLayer1[1]", + "entityName" : "SvgLayer1", + "entityKind" : "Path", + "parentPath" : "Proposal[0]/AboutBand[3]/AboutRow[0]/AboutLeft[0]/FeatureTilesLayer[2]/FeatureTilesHolder[0]/FeatureTiles[0]/FeatureCell_0[0]/FeatureTile_0[0]/globalGlyph[0]/Align[0]/SvgIcon[0]", + "childIndex" : 1, + "depth" : 13, + "layer" : 13, + "computedX" : 34.451, + "computedY" : 341.378, + "placementX" : 34.451, + "placementY" : 341.378, + "placementWidth" : 13.555, + "placementHeight" : 13.555, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 13.555, + "contentHeight" : 13.555, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/AboutBand[3]/AboutRow[0]/AboutLeft[0]/FeatureTilesLayer[2]/FeatureTilesHolder[0]/FeatureTiles[0]/FeatureCell_0[0]/FeatureLine1_0[1]", + "entityName" : "FeatureLine1_0", + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/AboutBand[3]/AboutRow[0]/AboutLeft[0]/FeatureTilesLayer[2]/FeatureTilesHolder[0]/FeatureTiles[0]/FeatureCell_0[0]", + "childIndex" : 1, + "depth" : 9, + "layer" : 9, + "computedX" : 27.674, + "computedY" : 318.19, + "placementX" : 27.674, + "placementY" : 318.19, + "placementWidth" : 22.973, + "placementHeight" : 7.9, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 22.973, + "contentHeight" : 7.9, + "margin" : { + "top" : 8.511, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/AboutBand[3]/AboutRow[0]/AboutLeft[0]/FeatureTilesLayer[2]/FeatureTilesHolder[0]/FeatureTiles[0]/FeatureCell_0[0]/FeatureLine2_0[2]", + "entityName" : "FeatureLine2_0", + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/AboutBand[3]/AboutRow[0]/AboutLeft[0]/FeatureTilesLayer[2]/FeatureTilesHolder[0]/FeatureTiles[0]/FeatureCell_0[0]", + "childIndex" : 2, + "depth" : 9, + "layer" : 9, + "computedX" : 27.674, + "computedY" : 307.459, + "placementX" : 27.674, + "placementY" : 307.459, + "placementWidth" : 19.474, + "placementHeight" : 7.9, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 19.474, + "contentHeight" : 7.9, + "margin" : { + "top" : 2.831, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/AboutBand[3]/AboutRow[0]/AboutLeft[0]/FeatureTilesLayer[2]/FeatureTilesHolder[0]/FeatureTiles[0]/FeatureCell_1[1]", + "entityName" : "FeatureCell_1", + "entityKind" : "SectionNode", + "parentPath" : "Proposal[0]/AboutBand[3]/AboutRow[0]/AboutLeft[0]/FeatureTilesLayer[2]/FeatureTilesHolder[0]/FeatureTiles[0]", + "childIndex" : 1, + "depth" : 8, + "layer" : 8, + "computedX" : 90.195, + "computedY" : 307.459, + "placementX" : 90.195, + "placementY" : 307.459, + "placementWidth" : 48.941, + "placementHeight" : 54.251, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 48.941, + "contentHeight" : 54.251, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/AboutBand[3]/AboutRow[0]/AboutLeft[0]/FeatureTilesLayer[2]/FeatureTilesHolder[0]/FeatureTiles[0]/FeatureCell_1[1]/FeatureTile_1[0]", + "entityName" : "FeatureTile_1", + "entityKind" : "ShapeContainerNode", + "parentPath" : "Proposal[0]/AboutBand[3]/AboutRow[0]/AboutLeft[0]/FeatureTilesLayer[2]/FeatureTilesHolder[0]/FeatureTiles[0]/FeatureCell_1[1]", + "childIndex" : 0, + "depth" : 9, + "layer" : 9, + "computedX" : 90.195, + "computedY" : 334.601, + "placementX" : 90.195, + "placementY" : 334.601, + "placementWidth" : 27.109, + "placementHeight" : 27.109, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 27.109, + "contentHeight" : 27.109, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/AboutBand[3]/AboutRow[0]/AboutLeft[0]/FeatureTilesLayer[2]/FeatureTilesHolder[0]/FeatureTiles[0]/FeatureCell_1[1]/FeatureTile_1[0]/infrastructureGlyph[0]", + "entityName" : "infrastructureGlyph", + "entityKind" : "SectionNode", + "parentPath" : "Proposal[0]/AboutBand[3]/AboutRow[0]/AboutLeft[0]/FeatureTilesLayer[2]/FeatureTilesHolder[0]/FeatureTiles[0]/FeatureCell_1[1]/FeatureTile_1[0]", + "childIndex" : 0, + "depth" : 10, + "layer" : 10, + "computedX" : 90.195, + "computedY" : 341.378, + "placementX" : 90.195, + "placementY" : 341.378, + "placementWidth" : 27.109, + "placementHeight" : 13.555, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 27.109, + "contentHeight" : 13.555, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/AboutBand[3]/AboutRow[0]/AboutLeft[0]/FeatureTilesLayer[2]/FeatureTilesHolder[0]/FeatureTiles[0]/FeatureCell_1[1]/FeatureTile_1[0]/infrastructureGlyph[0]/Align[0]", + "entityName" : null, + "entityKind" : "Align", + "parentPath" : "Proposal[0]/AboutBand[3]/AboutRow[0]/AboutLeft[0]/FeatureTilesLayer[2]/FeatureTilesHolder[0]/FeatureTiles[0]/FeatureCell_1[1]/FeatureTile_1[0]/infrastructureGlyph[0]", + "childIndex" : 0, + "depth" : 11, + "layer" : 11, + "computedX" : 90.195, + "computedY" : 341.378, + "placementX" : 90.195, + "placementY" : 341.378, + "placementWidth" : 27.109, + "placementHeight" : 13.555, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 27.109, + "contentHeight" : 13.555, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/AboutBand[3]/AboutRow[0]/AboutLeft[0]/FeatureTilesLayer[2]/FeatureTilesHolder[0]/FeatureTiles[0]/FeatureCell_1[1]/FeatureTile_1[0]/infrastructureGlyph[0]/Align[0]/SvgIcon[0]", + "entityName" : "SvgIcon", + "entityKind" : "LayerStackNode", + "parentPath" : "Proposal[0]/AboutBand[3]/AboutRow[0]/AboutLeft[0]/FeatureTilesLayer[2]/FeatureTilesHolder[0]/FeatureTiles[0]/FeatureCell_1[1]/FeatureTile_1[0]/infrastructureGlyph[0]/Align[0]", + "childIndex" : 0, + "depth" : 12, + "layer" : 12, + "computedX" : 96.972, + "computedY" : 341.378, + "placementX" : 96.972, + "placementY" : 341.378, + "placementWidth" : 13.555, + "placementHeight" : 13.555, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 13.555, + "contentHeight" : 13.555, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/AboutBand[3]/AboutRow[0]/AboutLeft[0]/FeatureTilesLayer[2]/FeatureTilesHolder[0]/FeatureTiles[0]/FeatureCell_1[1]/FeatureTile_1[0]/infrastructureGlyph[0]/Align[0]/SvgIcon[0]/SvgLayer0[0]", + "entityName" : "SvgLayer0", + "entityKind" : "Path", + "parentPath" : "Proposal[0]/AboutBand[3]/AboutRow[0]/AboutLeft[0]/FeatureTilesLayer[2]/FeatureTilesHolder[0]/FeatureTiles[0]/FeatureCell_1[1]/FeatureTile_1[0]/infrastructureGlyph[0]/Align[0]/SvgIcon[0]", + "childIndex" : 0, + "depth" : 13, + "layer" : 13, + "computedX" : 96.972, + "computedY" : 341.378, + "placementX" : 96.972, + "placementY" : 341.378, + "placementWidth" : 13.555, + "placementHeight" : 13.555, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 13.555, + "contentHeight" : 13.555, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/AboutBand[3]/AboutRow[0]/AboutLeft[0]/FeatureTilesLayer[2]/FeatureTilesHolder[0]/FeatureTiles[0]/FeatureCell_1[1]/FeatureLine1_1[1]", + "entityName" : "FeatureLine1_1", + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/AboutBand[3]/AboutRow[0]/AboutLeft[0]/FeatureTilesLayer[2]/FeatureTilesHolder[0]/FeatureTiles[0]/FeatureCell_1[1]", + "childIndex" : 1, + "depth" : 9, + "layer" : 9, + "computedX" : 90.195, + "computedY" : 318.19, + "placementX" : 90.195, + "placementY" : 318.19, + "placementWidth" : 27.595, + "placementHeight" : 7.9, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 27.595, + "contentHeight" : 7.9, + "margin" : { + "top" : 8.511, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/AboutBand[3]/AboutRow[0]/AboutLeft[0]/FeatureTilesLayer[2]/FeatureTilesHolder[0]/FeatureTiles[0]/FeatureCell_1[1]/FeatureLine2_1[2]", + "entityName" : "FeatureLine2_1", + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/AboutBand[3]/AboutRow[0]/AboutLeft[0]/FeatureTilesLayer[2]/FeatureTilesHolder[0]/FeatureTiles[0]/FeatureCell_1[1]", + "childIndex" : 2, + "depth" : 9, + "layer" : 9, + "computedX" : 90.195, + "computedY" : 307.459, + "placementX" : 90.195, + "placementY" : 307.459, + "placementWidth" : 48.941, + "placementHeight" : 7.9, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 48.941, + "contentHeight" : 7.9, + "margin" : { + "top" : 2.831, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/AboutBand[3]/AboutRow[0]/AboutLeft[0]/FeatureTilesLayer[2]/FeatureTilesHolder[0]/FeatureTiles[0]/FeatureCell_2[2]", + "entityName" : "FeatureCell_2", + "entityKind" : "SectionNode", + "parentPath" : "Proposal[0]/AboutBand[3]/AboutRow[0]/AboutLeft[0]/FeatureTilesLayer[2]/FeatureTilesHolder[0]/FeatureTiles[0]", + "childIndex" : 2, + "depth" : 8, + "layer" : 8, + "computedX" : 152.716, + "computedY" : 307.459, + "placementX" : 152.716, + "placementY" : 307.459, + "placementWidth" : 36.395, + "placementHeight" : 54.251, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 36.395, + "contentHeight" : 54.251, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/AboutBand[3]/AboutRow[0]/AboutLeft[0]/FeatureTilesLayer[2]/FeatureTilesHolder[0]/FeatureTiles[0]/FeatureCell_2[2]/FeatureTile_2[0]", + "entityName" : "FeatureTile_2", + "entityKind" : "ShapeContainerNode", + "parentPath" : "Proposal[0]/AboutBand[3]/AboutRow[0]/AboutLeft[0]/FeatureTilesLayer[2]/FeatureTilesHolder[0]/FeatureTiles[0]/FeatureCell_2[2]", + "childIndex" : 0, + "depth" : 9, + "layer" : 9, + "computedX" : 152.716, + "computedY" : 334.601, + "placementX" : 152.716, + "placementY" : 334.601, + "placementWidth" : 27.109, + "placementHeight" : 27.109, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 27.109, + "contentHeight" : 27.109, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/AboutBand[3]/AboutRow[0]/AboutLeft[0]/FeatureTilesLayer[2]/FeatureTilesHolder[0]/FeatureTiles[0]/FeatureCell_2[2]/FeatureTile_2[0]/secureGlyph[0]", + "entityName" : "secureGlyph", + "entityKind" : "SectionNode", + "parentPath" : "Proposal[0]/AboutBand[3]/AboutRow[0]/AboutLeft[0]/FeatureTilesLayer[2]/FeatureTilesHolder[0]/FeatureTiles[0]/FeatureCell_2[2]/FeatureTile_2[0]", + "childIndex" : 0, + "depth" : 10, + "layer" : 10, + "computedX" : 152.716, + "computedY" : 341.378, + "placementX" : 152.716, + "placementY" : 341.378, + "placementWidth" : 27.109, + "placementHeight" : 13.555, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 27.109, + "contentHeight" : 13.555, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/AboutBand[3]/AboutRow[0]/AboutLeft[0]/FeatureTilesLayer[2]/FeatureTilesHolder[0]/FeatureTiles[0]/FeatureCell_2[2]/FeatureTile_2[0]/secureGlyph[0]/Align[0]", + "entityName" : null, + "entityKind" : "Align", + "parentPath" : "Proposal[0]/AboutBand[3]/AboutRow[0]/AboutLeft[0]/FeatureTilesLayer[2]/FeatureTilesHolder[0]/FeatureTiles[0]/FeatureCell_2[2]/FeatureTile_2[0]/secureGlyph[0]", + "childIndex" : 0, + "depth" : 11, + "layer" : 11, + "computedX" : 152.716, + "computedY" : 341.378, + "placementX" : 152.716, + "placementY" : 341.378, + "placementWidth" : 27.109, + "placementHeight" : 13.555, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 27.109, + "contentHeight" : 13.555, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/AboutBand[3]/AboutRow[0]/AboutLeft[0]/FeatureTilesLayer[2]/FeatureTilesHolder[0]/FeatureTiles[0]/FeatureCell_2[2]/FeatureTile_2[0]/secureGlyph[0]/Align[0]/SvgIcon[0]", + "entityName" : "SvgIcon", + "entityKind" : "LayerStackNode", + "parentPath" : "Proposal[0]/AboutBand[3]/AboutRow[0]/AboutLeft[0]/FeatureTilesLayer[2]/FeatureTilesHolder[0]/FeatureTiles[0]/FeatureCell_2[2]/FeatureTile_2[0]/secureGlyph[0]/Align[0]", + "childIndex" : 0, + "depth" : 12, + "layer" : 12, + "computedX" : 159.493, + "computedY" : 341.378, + "placementX" : 159.493, + "placementY" : 341.378, + "placementWidth" : 13.555, + "placementHeight" : 13.555, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 13.555, + "contentHeight" : 13.555, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/AboutBand[3]/AboutRow[0]/AboutLeft[0]/FeatureTilesLayer[2]/FeatureTilesHolder[0]/FeatureTiles[0]/FeatureCell_2[2]/FeatureTile_2[0]/secureGlyph[0]/Align[0]/SvgIcon[0]/SvgLayer0[0]", + "entityName" : "SvgLayer0", + "entityKind" : "Path", + "parentPath" : "Proposal[0]/AboutBand[3]/AboutRow[0]/AboutLeft[0]/FeatureTilesLayer[2]/FeatureTilesHolder[0]/FeatureTiles[0]/FeatureCell_2[2]/FeatureTile_2[0]/secureGlyph[0]/Align[0]/SvgIcon[0]", + "childIndex" : 0, + "depth" : 13, + "layer" : 13, + "computedX" : 159.493, + "computedY" : 341.378, + "placementX" : 159.493, + "placementY" : 341.378, + "placementWidth" : 13.555, + "placementHeight" : 13.555, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 13.555, + "contentHeight" : 13.555, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/AboutBand[3]/AboutRow[0]/AboutLeft[0]/FeatureTilesLayer[2]/FeatureTilesHolder[0]/FeatureTiles[0]/FeatureCell_2[2]/FeatureLine1_2[1]", + "entityName" : "FeatureLine1_2", + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/AboutBand[3]/AboutRow[0]/AboutLeft[0]/FeatureTilesLayer[2]/FeatureTilesHolder[0]/FeatureTiles[0]/FeatureCell_2[2]", + "childIndex" : 1, + "depth" : 9, + "layer" : 9, + "computedX" : 152.716, + "computedY" : 318.19, + "placementX" : 152.716, + "placementY" : 318.19, + "placementWidth" : 31.758, + "placementHeight" : 7.9, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 31.758, + "contentHeight" : 7.9, + "margin" : { + "top" : 8.511, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/AboutBand[3]/AboutRow[0]/AboutLeft[0]/FeatureTilesLayer[2]/FeatureTilesHolder[0]/FeatureTiles[0]/FeatureCell_2[2]/FeatureLine2_2[2]", + "entityName" : "FeatureLine2_2", + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/AboutBand[3]/AboutRow[0]/AboutLeft[0]/FeatureTilesLayer[2]/FeatureTilesHolder[0]/FeatureTiles[0]/FeatureCell_2[2]", + "childIndex" : 2, + "depth" : 9, + "layer" : 9, + "computedX" : 152.716, + "computedY" : 307.459, + "placementX" : 152.716, + "placementY" : 307.459, + "placementWidth" : 36.395, + "placementHeight" : 7.9, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 36.395, + "contentHeight" : 7.9, + "margin" : { + "top" : 2.831, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/AboutBand[3]/AboutRow[0]/AboutLeft[0]/FeatureTilesLayer[2]/FeatureTilesHolder[0]/FeatureTiles[0]/FeatureCell_3[3]", + "entityName" : "FeatureCell_3", + "entityKind" : "SectionNode", + "parentPath" : "Proposal[0]/AboutBand[3]/AboutRow[0]/AboutLeft[0]/FeatureTilesLayer[2]/FeatureTilesHolder[0]/FeatureTiles[0]", + "childIndex" : 3, + "depth" : 8, + "layer" : 8, + "computedX" : 215.237, + "computedY" : 307.459, + "placementX" : 215.237, + "placementY" : 307.459, + "placementWidth" : 36.332, + "placementHeight" : 54.251, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 36.332, + "contentHeight" : 54.251, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/AboutBand[3]/AboutRow[0]/AboutLeft[0]/FeatureTilesLayer[2]/FeatureTilesHolder[0]/FeatureTiles[0]/FeatureCell_3[3]/FeatureTile_3[0]", + "entityName" : "FeatureTile_3", + "entityKind" : "ShapeContainerNode", + "parentPath" : "Proposal[0]/AboutBand[3]/AboutRow[0]/AboutLeft[0]/FeatureTilesLayer[2]/FeatureTilesHolder[0]/FeatureTiles[0]/FeatureCell_3[3]", + "childIndex" : 0, + "depth" : 9, + "layer" : 9, + "computedX" : 215.237, + "computedY" : 334.601, + "placementX" : 215.237, + "placementY" : 334.601, + "placementWidth" : 27.109, + "placementHeight" : 27.109, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 27.109, + "contentHeight" : 27.109, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/AboutBand[3]/AboutRow[0]/AboutLeft[0]/FeatureTilesLayer[2]/FeatureTilesHolder[0]/FeatureTiles[0]/FeatureCell_3[3]/FeatureTile_3[0]/apisGlyph[0]", + "entityName" : "apisGlyph", + "entityKind" : "SectionNode", + "parentPath" : "Proposal[0]/AboutBand[3]/AboutRow[0]/AboutLeft[0]/FeatureTilesLayer[2]/FeatureTilesHolder[0]/FeatureTiles[0]/FeatureCell_3[3]/FeatureTile_3[0]", + "childIndex" : 0, + "depth" : 10, + "layer" : 10, + "computedX" : 215.237, + "computedY" : 341.378, + "placementX" : 215.237, + "placementY" : 341.378, + "placementWidth" : 27.109, + "placementHeight" : 13.555, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 27.109, + "contentHeight" : 13.555, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/AboutBand[3]/AboutRow[0]/AboutLeft[0]/FeatureTilesLayer[2]/FeatureTilesHolder[0]/FeatureTiles[0]/FeatureCell_3[3]/FeatureTile_3[0]/apisGlyph[0]/Align[0]", + "entityName" : null, + "entityKind" : "Align", + "parentPath" : "Proposal[0]/AboutBand[3]/AboutRow[0]/AboutLeft[0]/FeatureTilesLayer[2]/FeatureTilesHolder[0]/FeatureTiles[0]/FeatureCell_3[3]/FeatureTile_3[0]/apisGlyph[0]", + "childIndex" : 0, + "depth" : 11, + "layer" : 11, + "computedX" : 215.237, + "computedY" : 341.378, + "placementX" : 215.237, + "placementY" : 341.378, + "placementWidth" : 27.109, + "placementHeight" : 13.555, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 27.109, + "contentHeight" : 13.555, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/AboutBand[3]/AboutRow[0]/AboutLeft[0]/FeatureTilesLayer[2]/FeatureTilesHolder[0]/FeatureTiles[0]/FeatureCell_3[3]/FeatureTile_3[0]/apisGlyph[0]/Align[0]/SvgIcon[0]", + "entityName" : "SvgIcon", + "entityKind" : "LayerStackNode", + "parentPath" : "Proposal[0]/AboutBand[3]/AboutRow[0]/AboutLeft[0]/FeatureTilesLayer[2]/FeatureTilesHolder[0]/FeatureTiles[0]/FeatureCell_3[3]/FeatureTile_3[0]/apisGlyph[0]/Align[0]", + "childIndex" : 0, + "depth" : 12, + "layer" : 12, + "computedX" : 222.014, + "computedY" : 341.378, + "placementX" : 222.014, + "placementY" : 341.378, + "placementWidth" : 13.555, + "placementHeight" : 13.555, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 13.555, + "contentHeight" : 13.555, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/AboutBand[3]/AboutRow[0]/AboutLeft[0]/FeatureTilesLayer[2]/FeatureTilesHolder[0]/FeatureTiles[0]/FeatureCell_3[3]/FeatureTile_3[0]/apisGlyph[0]/Align[0]/SvgIcon[0]/SvgLayer0[0]", + "entityName" : "SvgLayer0", + "entityKind" : "Path", + "parentPath" : "Proposal[0]/AboutBand[3]/AboutRow[0]/AboutLeft[0]/FeatureTilesLayer[2]/FeatureTilesHolder[0]/FeatureTiles[0]/FeatureCell_3[3]/FeatureTile_3[0]/apisGlyph[0]/Align[0]/SvgIcon[0]", + "childIndex" : 0, + "depth" : 13, + "layer" : 13, + "computedX" : 222.014, + "computedY" : 341.378, + "placementX" : 222.014, + "placementY" : 341.378, + "placementWidth" : 13.555, + "placementHeight" : 13.555, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 13.555, + "contentHeight" : 13.555, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/AboutBand[3]/AboutRow[0]/AboutLeft[0]/FeatureTilesLayer[2]/FeatureTilesHolder[0]/FeatureTiles[0]/FeatureCell_3[3]/FeatureLine1_3[1]", + "entityName" : "FeatureLine1_3", + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/AboutBand[3]/AboutRow[0]/AboutLeft[0]/FeatureTilesLayer[2]/FeatureTilesHolder[0]/FeatureTiles[0]/FeatureCell_3[3]", + "childIndex" : 1, + "depth" : 9, + "layer" : 9, + "computedX" : 215.237, + "computedY" : 318.19, + "placementX" : 215.237, + "placementY" : 318.19, + "placementWidth" : 36.332, + "placementHeight" : 7.9, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 36.332, + "contentHeight" : 7.9, + "margin" : { + "top" : 8.511, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/AboutBand[3]/AboutRow[0]/AboutLeft[0]/FeatureTilesLayer[2]/FeatureTilesHolder[0]/FeatureTiles[0]/FeatureCell_3[3]/FeatureLine2_3[2]", + "entityName" : "FeatureLine2_3", + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/AboutBand[3]/AboutRow[0]/AboutLeft[0]/FeatureTilesLayer[2]/FeatureTilesHolder[0]/FeatureTiles[0]/FeatureCell_3[3]", + "childIndex" : 2, + "depth" : 9, + "layer" : 9, + "computedX" : 215.237, + "computedY" : 307.459, + "placementX" : 215.237, + "placementY" : 307.459, + "placementWidth" : 34.231, + "placementHeight" : 7.9, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 34.231, + "contentHeight" : 7.9, + "margin" : { + "top" : 2.831, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/AboutBand[3]/AboutRow[0]/AboutArtwork[1]", + "entityName" : "AboutArtwork", + "entityKind" : "SectionNode", + "parentPath" : "Proposal[0]/AboutBand[3]/AboutRow[0]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 265.445, + "computedY" : 287.77, + "placementX" : 265.445, + "placementY" : 287.77, + "placementWidth" : 0.0, + "placementHeight" : 173.387, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 0.0, + "contentHeight" : 173.387, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/AboutBand[3]/AboutRow[0]/AboutArtwork[1]/AboutArtworkSpace[0]", + "entityName" : "AboutArtworkSpace", + "entityKind" : "SpacerNode", + "parentPath" : "Proposal[0]/AboutBand[3]/AboutRow[0]/AboutArtwork[1]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 265.445, + "computedY" : 287.77, + "placementX" : 265.445, + "placementY" : 287.77, + "placementWidth" : 0.0, + "placementHeight" : 173.387, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 0.0, + "contentHeight" : 173.387, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/LowerRow[4]", + "entityName" : "LowerRow", + "entityKind" : "RowNode", + "parentPath" : "Proposal[0]", + "childIndex" : 4, + "depth" : 2, + "layer" : 2, + "computedX" : 27.674, + "computedY" : 72.958, + "placementX" : 27.674, + "placementY" : 72.958, + "placementWidth" : 544.446, + "placementHeight" : 193.556, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 544.446, + "contentHeight" : 193.556, + "margin" : { + "top" : 21.256, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/LowerRow[4]/LowerLeft[0]", + "entityName" : "LowerLeft", + "entityKind" : "SectionNode", + "parentPath" : "Proposal[0]/LowerRow[4]", + "childIndex" : 0, + "depth" : 3, + "layer" : 3, + "computedX" : 27.674, + "computedY" : 72.958, + "placementX" : 27.674, + "placementY" : 72.958, + "placementWidth" : 256.409, + "placementHeight" : 193.556, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 256.409, + "contentHeight" : 193.556, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/LowerRow[4]/LowerLeft[0]/PlanHeading[0]", + "entityName" : "PlanHeading", + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/LowerRow[4]/LowerLeft[0]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 27.674, + "computedY" : 258.415, + "placementX" : 27.674, + "placementY" : 258.415, + "placementWidth" : 81.446, + "placementHeight" : 8.1, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 81.446, + "contentHeight" : 8.1, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/LowerRow[4]/LowerLeft[0]/PlanIntro[1]", + "entityName" : "PlanIntro", + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/LowerRow[4]/LowerLeft[0]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 27.674, + "computedY" : 218.644, + "placementX" : 27.674, + "placementY" : 218.644, + "placementWidth" : 224.648, + "placementHeight" : 30.091, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 224.648, + "contentHeight" : 30.091, + "margin" : { + "top" : 9.68, + "right" : 19.202, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/LowerRow[4]/LowerLeft[0]/Step_0Layer[2]", + "entityName" : "Step_0Layer", + "entityKind" : "LayerStackNode", + "parentPath" : "Proposal[0]/LowerRow[4]/LowerLeft[0]", + "childIndex" : 2, + "depth" : 4, + "layer" : 4, + "computedX" : 27.674, + "computedY" : 180.114, + "placementX" : 27.674, + "placementY" : 180.114, + "placementWidth" : 256.409, + "placementHeight" : 38.529, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 256.409, + "contentHeight" : 38.529, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/LowerRow[4]/LowerLeft[0]/Step_0Layer[2]/Step_0Holder[0]", + "entityName" : "Step_0Holder", + "entityKind" : "SectionNode", + "parentPath" : "Proposal[0]/LowerRow[4]/LowerLeft[0]/Step_0Layer[2]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 27.674, + "computedY" : 180.114, + "placementX" : 27.674, + "placementY" : 180.114, + "placementWidth" : 256.409, + "placementHeight" : 38.529, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 256.409, + "contentHeight" : 38.529, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/LowerRow[4]/LowerLeft[0]/Step_0Layer[2]/Step_0Holder[0]/Step_0[0]", + "entityName" : "Step_0", + "entityKind" : "RowNode", + "parentPath" : "Proposal[0]/LowerRow[4]/LowerLeft[0]/Step_0Layer[2]/Step_0Holder[0]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 27.674, + "computedY" : 180.114, + "placementX" : 27.674, + "placementY" : 180.114, + "placementWidth" : 256.409, + "placementHeight" : 27.956, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 256.409, + "contentHeight" : 27.956, + "margin" : { + "top" : 10.573, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/LowerRow[4]/LowerLeft[0]/Step_0Layer[2]/Step_0Holder[0]/Step_0[0]/StepMark_0[0]", + "entityName" : "StepMark_0", + "entityKind" : "SectionNode", + "parentPath" : "Proposal[0]/LowerRow[4]/LowerLeft[0]/Step_0Layer[2]/Step_0Holder[0]/Step_0[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 27.674, + "computedY" : 180.114, + "placementX" : 27.674, + "placementY" : 180.114, + "placementWidth" : 21.462, + "placementHeight" : 27.956, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 21.462, + "contentHeight" : 27.956, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/LowerRow[4]/LowerLeft[0]/Step_0Layer[2]/Step_0Holder[0]/Step_0[0]/StepMark_0[0]/StepCircle_0[0]", + "entityName" : "StepCircle_0", + "entityKind" : "ShapeContainerNode", + "parentPath" : "Proposal[0]/LowerRow[4]/LowerLeft[0]/Step_0Layer[2]/Step_0Holder[0]/Step_0[0]/StepMark_0[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 27.674, + "computedY" : 186.609, + "placementX" : 27.674, + "placementY" : 186.609, + "placementWidth" : 21.462, + "placementHeight" : 21.462, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 21.462, + "contentHeight" : 21.462, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/LowerRow[4]/LowerLeft[0]/Step_0Layer[2]/Step_0Holder[0]/Step_0[0]/StepMark_0[0]/StepCircle_0[0]/StepNumber_0[0]", + "entityName" : "StepNumber_0", + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/LowerRow[4]/LowerLeft[0]/Step_0Layer[2]/Step_0Holder[0]/Step_0[0]/StepMark_0[0]/StepCircle_0[0]", + "childIndex" : 0, + "depth" : 9, + "layer" : 9, + "computedX" : 27.674, + "computedY" : 193.34, + "placementX" : 27.674, + "placementY" : 193.34, + "placementWidth" : 21.462, + "placementHeight" : 8.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 21.462, + "contentHeight" : 8.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/LowerRow[4]/LowerLeft[0]/Step_0Layer[2]/Step_0Holder[0]/Step_0[0]/StepMark_0[0]/StepConnector_0[1]", + "entityName" : "StepConnector_0", + "entityKind" : "LineNode", + "parentPath" : "Proposal[0]/LowerRow[4]/LowerLeft[0]/Step_0Layer[2]/Step_0Holder[0]/Step_0[0]/StepMark_0[0]", + "childIndex" : 1, + "depth" : 8, + "layer" : 8, + "computedX" : 37.84, + "computedY" : 180.114, + "placementX" : 37.84, + "placementY" : 180.114, + "placementWidth" : 1.13, + "placementHeight" : 6.495, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 1.13, + "contentHeight" : 6.495, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 10.166 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/LowerRow[4]/LowerLeft[0]/Step_0Layer[2]/Step_0Holder[0]/Step_0[0]/StepText_0[1]", + "entityName" : "StepText_0", + "entityKind" : "SectionNode", + "parentPath" : "Proposal[0]/LowerRow[4]/LowerLeft[0]/Step_0Layer[2]/Step_0Holder[0]/Step_0[0]", + "childIndex" : 1, + "depth" : 7, + "layer" : 7, + "computedX" : 62.126, + "computedY" : 184.784, + "placementX" : 62.126, + "placementY" : 184.784, + "placementWidth" : 123.684, + "placementHeight" : 23.286, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 123.684, + "contentHeight" : 23.286, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/LowerRow[4]/LowerLeft[0]/Step_0Layer[2]/Step_0Holder[0]/Step_0[0]/StepText_0[1]/StepTitle_0[0]", + "entityName" : "StepTitle_0", + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/LowerRow[4]/LowerLeft[0]/Step_0Layer[2]/Step_0Holder[0]/Step_0[0]/StepText_0[1]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 62.126, + "computedY" : 195.581, + "placementX" : 62.126, + "placementY" : 195.581, + "placementWidth" : 123.356, + "placementHeight" : 7.6, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 123.356, + "contentHeight" : 7.6, + "margin" : { + "top" : 4.89, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/LowerRow[4]/LowerLeft[0]/Step_0Layer[2]/Step_0Holder[0]/Step_0[0]/StepText_0[1]/StepSubtitle_0[1]", + "entityName" : "StepSubtitle_0", + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/LowerRow[4]/LowerLeft[0]/Step_0Layer[2]/Step_0Holder[0]/Step_0[0]/StepText_0[1]", + "childIndex" : 1, + "depth" : 8, + "layer" : 8, + "computedX" : 62.126, + "computedY" : 184.784, + "placementX" : 62.126, + "placementY" : 184.784, + "placementWidth" : 123.684, + "placementHeight" : 7.4, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 123.684, + "contentHeight" : 7.4, + "margin" : { + "top" : 3.396, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/LowerRow[4]/LowerLeft[0]/Step_1Layer[3]", + "entityName" : "Step_1Layer", + "entityKind" : "LayerStackNode", + "parentPath" : "Proposal[0]/LowerRow[4]/LowerLeft[0]", + "childIndex" : 3, + "depth" : 4, + "layer" : 4, + "computedX" : 27.674, + "computedY" : 152.158, + "placementX" : 27.674, + "placementY" : 152.158, + "placementWidth" : 256.409, + "placementHeight" : 27.956, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 256.409, + "contentHeight" : 27.956, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/LowerRow[4]/LowerLeft[0]/Step_1Layer[3]/Step_1Holder[0]", + "entityName" : "Step_1Holder", + "entityKind" : "SectionNode", + "parentPath" : "Proposal[0]/LowerRow[4]/LowerLeft[0]/Step_1Layer[3]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 27.674, + "computedY" : 152.158, + "placementX" : 27.674, + "placementY" : 152.158, + "placementWidth" : 256.409, + "placementHeight" : 27.956, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 256.409, + "contentHeight" : 27.956, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/LowerRow[4]/LowerLeft[0]/Step_1Layer[3]/Step_1Holder[0]/Step_1[0]", + "entityName" : "Step_1", + "entityKind" : "RowNode", + "parentPath" : "Proposal[0]/LowerRow[4]/LowerLeft[0]/Step_1Layer[3]/Step_1Holder[0]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 27.674, + "computedY" : 152.158, + "placementX" : 27.674, + "placementY" : 152.158, + "placementWidth" : 256.409, + "placementHeight" : 27.956, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 256.409, + "contentHeight" : 27.956, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/LowerRow[4]/LowerLeft[0]/Step_1Layer[3]/Step_1Holder[0]/Step_1[0]/StepMark_1[0]", + "entityName" : "StepMark_1", + "entityKind" : "SectionNode", + "parentPath" : "Proposal[0]/LowerRow[4]/LowerLeft[0]/Step_1Layer[3]/Step_1Holder[0]/Step_1[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 27.674, + "computedY" : 152.158, + "placementX" : 27.674, + "placementY" : 152.158, + "placementWidth" : 21.462, + "placementHeight" : 27.956, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 21.462, + "contentHeight" : 27.956, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/LowerRow[4]/LowerLeft[0]/Step_1Layer[3]/Step_1Holder[0]/Step_1[0]/StepMark_1[0]/StepCircle_1[0]", + "entityName" : "StepCircle_1", + "entityKind" : "ShapeContainerNode", + "parentPath" : "Proposal[0]/LowerRow[4]/LowerLeft[0]/Step_1Layer[3]/Step_1Holder[0]/Step_1[0]/StepMark_1[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 27.674, + "computedY" : 158.653, + "placementX" : 27.674, + "placementY" : 158.653, + "placementWidth" : 21.462, + "placementHeight" : 21.462, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 21.462, + "contentHeight" : 21.462, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/LowerRow[4]/LowerLeft[0]/Step_1Layer[3]/Step_1Holder[0]/Step_1[0]/StepMark_1[0]/StepCircle_1[0]/StepNumber_1[0]", + "entityName" : "StepNumber_1", + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/LowerRow[4]/LowerLeft[0]/Step_1Layer[3]/Step_1Holder[0]/Step_1[0]/StepMark_1[0]/StepCircle_1[0]", + "childIndex" : 0, + "depth" : 9, + "layer" : 9, + "computedX" : 27.674, + "computedY" : 165.383, + "placementX" : 27.674, + "placementY" : 165.383, + "placementWidth" : 21.462, + "placementHeight" : 8.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 21.462, + "contentHeight" : 8.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/LowerRow[4]/LowerLeft[0]/Step_1Layer[3]/Step_1Holder[0]/Step_1[0]/StepMark_1[0]/StepConnector_1[1]", + "entityName" : "StepConnector_1", + "entityKind" : "LineNode", + "parentPath" : "Proposal[0]/LowerRow[4]/LowerLeft[0]/Step_1Layer[3]/Step_1Holder[0]/Step_1[0]/StepMark_1[0]", + "childIndex" : 1, + "depth" : 8, + "layer" : 8, + "computedX" : 37.84, + "computedY" : 152.158, + "placementX" : 37.84, + "placementY" : 152.158, + "placementWidth" : 1.13, + "placementHeight" : 6.495, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 1.13, + "contentHeight" : 6.495, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 10.166 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/LowerRow[4]/LowerLeft[0]/Step_1Layer[3]/Step_1Holder[0]/Step_1[0]/StepText_1[1]", + "entityName" : "StepText_1", + "entityKind" : "SectionNode", + "parentPath" : "Proposal[0]/LowerRow[4]/LowerLeft[0]/Step_1Layer[3]/Step_1Holder[0]/Step_1[0]", + "childIndex" : 1, + "depth" : 7, + "layer" : 7, + "computedX" : 62.126, + "computedY" : 156.828, + "placementX" : 62.126, + "placementY" : 156.828, + "placementWidth" : 162.001, + "placementHeight" : 23.286, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 162.001, + "contentHeight" : 23.286, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/LowerRow[4]/LowerLeft[0]/Step_1Layer[3]/Step_1Holder[0]/Step_1[0]/StepText_1[1]/StepTitle_1[0]", + "entityName" : "StepTitle_1", + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/LowerRow[4]/LowerLeft[0]/Step_1Layer[3]/Step_1Holder[0]/Step_1[0]/StepText_1[1]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 62.126, + "computedY" : 167.624, + "placementX" : 62.126, + "placementY" : 167.624, + "placementWidth" : 102.425, + "placementHeight" : 7.6, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 102.425, + "contentHeight" : 7.6, + "margin" : { + "top" : 4.89, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/LowerRow[4]/LowerLeft[0]/Step_1Layer[3]/Step_1Holder[0]/Step_1[0]/StepText_1[1]/StepSubtitle_1[1]", + "entityName" : "StepSubtitle_1", + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/LowerRow[4]/LowerLeft[0]/Step_1Layer[3]/Step_1Holder[0]/Step_1[0]/StepText_1[1]", + "childIndex" : 1, + "depth" : 8, + "layer" : 8, + "computedX" : 62.126, + "computedY" : 156.828, + "placementX" : 62.126, + "placementY" : 156.828, + "placementWidth" : 162.001, + "placementHeight" : 7.4, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 162.001, + "contentHeight" : 7.4, + "margin" : { + "top" : 3.396, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/LowerRow[4]/LowerLeft[0]/Step_2Layer[4]", + "entityName" : "Step_2Layer", + "entityKind" : "LayerStackNode", + "parentPath" : "Proposal[0]/LowerRow[4]/LowerLeft[0]", + "childIndex" : 4, + "depth" : 4, + "layer" : 4, + "computedX" : 27.674, + "computedY" : 124.201, + "placementX" : 27.674, + "placementY" : 124.201, + "placementWidth" : 256.409, + "placementHeight" : 27.956, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 256.409, + "contentHeight" : 27.956, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/LowerRow[4]/LowerLeft[0]/Step_2Layer[4]/Step_2Holder[0]", + "entityName" : "Step_2Holder", + "entityKind" : "SectionNode", + "parentPath" : "Proposal[0]/LowerRow[4]/LowerLeft[0]/Step_2Layer[4]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 27.674, + "computedY" : 124.201, + "placementX" : 27.674, + "placementY" : 124.201, + "placementWidth" : 256.409, + "placementHeight" : 27.956, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 256.409, + "contentHeight" : 27.956, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/LowerRow[4]/LowerLeft[0]/Step_2Layer[4]/Step_2Holder[0]/Step_2[0]", + "entityName" : "Step_2", + "entityKind" : "RowNode", + "parentPath" : "Proposal[0]/LowerRow[4]/LowerLeft[0]/Step_2Layer[4]/Step_2Holder[0]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 27.674, + "computedY" : 124.201, + "placementX" : 27.674, + "placementY" : 124.201, + "placementWidth" : 256.409, + "placementHeight" : 27.956, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 256.409, + "contentHeight" : 27.956, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/LowerRow[4]/LowerLeft[0]/Step_2Layer[4]/Step_2Holder[0]/Step_2[0]/StepMark_2[0]", + "entityName" : "StepMark_2", + "entityKind" : "SectionNode", + "parentPath" : "Proposal[0]/LowerRow[4]/LowerLeft[0]/Step_2Layer[4]/Step_2Holder[0]/Step_2[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 27.674, + "computedY" : 124.201, + "placementX" : 27.674, + "placementY" : 124.201, + "placementWidth" : 21.462, + "placementHeight" : 27.956, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 21.462, + "contentHeight" : 27.956, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/LowerRow[4]/LowerLeft[0]/Step_2Layer[4]/Step_2Holder[0]/Step_2[0]/StepMark_2[0]/StepCircle_2[0]", + "entityName" : "StepCircle_2", + "entityKind" : "ShapeContainerNode", + "parentPath" : "Proposal[0]/LowerRow[4]/LowerLeft[0]/Step_2Layer[4]/Step_2Holder[0]/Step_2[0]/StepMark_2[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 27.674, + "computedY" : 130.696, + "placementX" : 27.674, + "placementY" : 130.696, + "placementWidth" : 21.462, + "placementHeight" : 21.462, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 21.462, + "contentHeight" : 21.462, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/LowerRow[4]/LowerLeft[0]/Step_2Layer[4]/Step_2Holder[0]/Step_2[0]/StepMark_2[0]/StepCircle_2[0]/StepNumber_2[0]", + "entityName" : "StepNumber_2", + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/LowerRow[4]/LowerLeft[0]/Step_2Layer[4]/Step_2Holder[0]/Step_2[0]/StepMark_2[0]/StepCircle_2[0]", + "childIndex" : 0, + "depth" : 9, + "layer" : 9, + "computedX" : 27.674, + "computedY" : 137.427, + "placementX" : 27.674, + "placementY" : 137.427, + "placementWidth" : 21.462, + "placementHeight" : 8.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 21.462, + "contentHeight" : 8.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/LowerRow[4]/LowerLeft[0]/Step_2Layer[4]/Step_2Holder[0]/Step_2[0]/StepMark_2[0]/StepConnector_2[1]", + "entityName" : "StepConnector_2", + "entityKind" : "LineNode", + "parentPath" : "Proposal[0]/LowerRow[4]/LowerLeft[0]/Step_2Layer[4]/Step_2Holder[0]/Step_2[0]/StepMark_2[0]", + "childIndex" : 1, + "depth" : 8, + "layer" : 8, + "computedX" : 37.84, + "computedY" : 124.201, + "placementX" : 37.84, + "placementY" : 124.201, + "placementWidth" : 1.13, + "placementHeight" : 6.495, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 1.13, + "contentHeight" : 6.495, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 10.166 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/LowerRow[4]/LowerLeft[0]/Step_2Layer[4]/Step_2Holder[0]/Step_2[0]/StepText_2[1]", + "entityName" : "StepText_2", + "entityKind" : "SectionNode", + "parentPath" : "Proposal[0]/LowerRow[4]/LowerLeft[0]/Step_2Layer[4]/Step_2Holder[0]/Step_2[0]", + "childIndex" : 1, + "depth" : 7, + "layer" : 7, + "computedX" : 62.126, + "computedY" : 128.871, + "placementX" : 62.126, + "placementY" : 128.871, + "placementWidth" : 179.798, + "placementHeight" : 23.286, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 179.798, + "contentHeight" : 23.286, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/LowerRow[4]/LowerLeft[0]/Step_2Layer[4]/Step_2Holder[0]/Step_2[0]/StepText_2[1]/StepTitle_2[0]", + "entityName" : "StepTitle_2", + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/LowerRow[4]/LowerLeft[0]/Step_2Layer[4]/Step_2Holder[0]/Step_2[0]/StepText_2[1]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 62.126, + "computedY" : 139.668, + "placementX" : 62.126, + "placementY" : 139.668, + "placementWidth" : 84.656, + "placementHeight" : 7.6, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 84.656, + "contentHeight" : 7.6, + "margin" : { + "top" : 4.89, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/LowerRow[4]/LowerLeft[0]/Step_2Layer[4]/Step_2Holder[0]/Step_2[0]/StepText_2[1]/StepSubtitle_2[1]", + "entityName" : "StepSubtitle_2", + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/LowerRow[4]/LowerLeft[0]/Step_2Layer[4]/Step_2Holder[0]/Step_2[0]/StepText_2[1]", + "childIndex" : 1, + "depth" : 8, + "layer" : 8, + "computedX" : 62.126, + "computedY" : 128.871, + "placementX" : 62.126, + "placementY" : 128.871, + "placementWidth" : 179.798, + "placementHeight" : 7.4, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 179.798, + "contentHeight" : 7.4, + "margin" : { + "top" : 3.396, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/LowerRow[4]/LowerLeft[0]/Step_3Layer[5]", + "entityName" : "Step_3Layer", + "entityKind" : "LayerStackNode", + "parentPath" : "Proposal[0]/LowerRow[4]/LowerLeft[0]", + "childIndex" : 5, + "depth" : 4, + "layer" : 4, + "computedX" : 27.674, + "computedY" : 96.245, + "placementX" : 27.674, + "placementY" : 96.245, + "placementWidth" : 256.409, + "placementHeight" : 27.956, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 256.409, + "contentHeight" : 27.956, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/LowerRow[4]/LowerLeft[0]/Step_3Layer[5]/Step_3Holder[0]", + "entityName" : "Step_3Holder", + "entityKind" : "SectionNode", + "parentPath" : "Proposal[0]/LowerRow[4]/LowerLeft[0]/Step_3Layer[5]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 27.674, + "computedY" : 96.245, + "placementX" : 27.674, + "placementY" : 96.245, + "placementWidth" : 256.409, + "placementHeight" : 27.956, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 256.409, + "contentHeight" : 27.956, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/LowerRow[4]/LowerLeft[0]/Step_3Layer[5]/Step_3Holder[0]/Step_3[0]", + "entityName" : "Step_3", + "entityKind" : "RowNode", + "parentPath" : "Proposal[0]/LowerRow[4]/LowerLeft[0]/Step_3Layer[5]/Step_3Holder[0]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 27.674, + "computedY" : 96.245, + "placementX" : 27.674, + "placementY" : 96.245, + "placementWidth" : 256.409, + "placementHeight" : 27.956, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 256.409, + "contentHeight" : 27.956, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/LowerRow[4]/LowerLeft[0]/Step_3Layer[5]/Step_3Holder[0]/Step_3[0]/StepMark_3[0]", + "entityName" : "StepMark_3", + "entityKind" : "SectionNode", + "parentPath" : "Proposal[0]/LowerRow[4]/LowerLeft[0]/Step_3Layer[5]/Step_3Holder[0]/Step_3[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 27.674, + "computedY" : 96.245, + "placementX" : 27.674, + "placementY" : 96.245, + "placementWidth" : 21.462, + "placementHeight" : 27.956, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 21.462, + "contentHeight" : 27.956, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/LowerRow[4]/LowerLeft[0]/Step_3Layer[5]/Step_3Holder[0]/Step_3[0]/StepMark_3[0]/StepCircle_3[0]", + "entityName" : "StepCircle_3", + "entityKind" : "ShapeContainerNode", + "parentPath" : "Proposal[0]/LowerRow[4]/LowerLeft[0]/Step_3Layer[5]/Step_3Holder[0]/Step_3[0]/StepMark_3[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 27.674, + "computedY" : 102.74, + "placementX" : 27.674, + "placementY" : 102.74, + "placementWidth" : 21.462, + "placementHeight" : 21.462, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 21.462, + "contentHeight" : 21.462, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/LowerRow[4]/LowerLeft[0]/Step_3Layer[5]/Step_3Holder[0]/Step_3[0]/StepMark_3[0]/StepCircle_3[0]/StepNumber_3[0]", + "entityName" : "StepNumber_3", + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/LowerRow[4]/LowerLeft[0]/Step_3Layer[5]/Step_3Holder[0]/Step_3[0]/StepMark_3[0]/StepCircle_3[0]", + "childIndex" : 0, + "depth" : 9, + "layer" : 9, + "computedX" : 27.674, + "computedY" : 109.47, + "placementX" : 27.674, + "placementY" : 109.47, + "placementWidth" : 21.462, + "placementHeight" : 8.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 21.462, + "contentHeight" : 8.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/LowerRow[4]/LowerLeft[0]/Step_3Layer[5]/Step_3Holder[0]/Step_3[0]/StepMark_3[0]/StepConnector_3[1]", + "entityName" : "StepConnector_3", + "entityKind" : "LineNode", + "parentPath" : "Proposal[0]/LowerRow[4]/LowerLeft[0]/Step_3Layer[5]/Step_3Holder[0]/Step_3[0]/StepMark_3[0]", + "childIndex" : 1, + "depth" : 8, + "layer" : 8, + "computedX" : 37.84, + "computedY" : 96.245, + "placementX" : 37.84, + "placementY" : 96.245, + "placementWidth" : 1.13, + "placementHeight" : 6.495, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 1.13, + "contentHeight" : 6.495, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 10.166 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/LowerRow[4]/LowerLeft[0]/Step_3Layer[5]/Step_3Holder[0]/Step_3[0]/StepText_3[1]", + "entityName" : "StepText_3", + "entityKind" : "SectionNode", + "parentPath" : "Proposal[0]/LowerRow[4]/LowerLeft[0]/Step_3Layer[5]/Step_3Holder[0]/Step_3[0]", + "childIndex" : 1, + "depth" : 7, + "layer" : 7, + "computedX" : 62.126, + "computedY" : 100.915, + "placementX" : 62.126, + "placementY" : 100.915, + "placementWidth" : 166.929, + "placementHeight" : 23.286, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 166.929, + "contentHeight" : 23.286, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/LowerRow[4]/LowerLeft[0]/Step_3Layer[5]/Step_3Holder[0]/Step_3[0]/StepText_3[1]/StepTitle_3[0]", + "entityName" : "StepTitle_3", + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/LowerRow[4]/LowerLeft[0]/Step_3Layer[5]/Step_3Holder[0]/Step_3[0]/StepText_3[1]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 62.126, + "computedY" : 111.711, + "placementX" : 62.126, + "placementY" : 111.711, + "placementWidth" : 86.192, + "placementHeight" : 7.6, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 86.192, + "contentHeight" : 7.6, + "margin" : { + "top" : 4.89, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/LowerRow[4]/LowerLeft[0]/Step_3Layer[5]/Step_3Holder[0]/Step_3[0]/StepText_3[1]/StepSubtitle_3[1]", + "entityName" : "StepSubtitle_3", + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/LowerRow[4]/LowerLeft[0]/Step_3Layer[5]/Step_3Holder[0]/Step_3[0]/StepText_3[1]", + "childIndex" : 1, + "depth" : 8, + "layer" : 8, + "computedX" : 62.126, + "computedY" : 100.915, + "placementX" : 62.126, + "placementY" : 100.915, + "placementWidth" : 166.929, + "placementHeight" : 7.4, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 166.929, + "contentHeight" : 7.4, + "margin" : { + "top" : 3.396, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/LowerRow[4]/LowerLeft[0]/Step_4Layer[6]", + "entityName" : "Step_4Layer", + "entityKind" : "LayerStackNode", + "parentPath" : "Proposal[0]/LowerRow[4]/LowerLeft[0]", + "childIndex" : 6, + "depth" : 4, + "layer" : 4, + "computedX" : 27.674, + "computedY" : 72.958, + "placementX" : 27.674, + "placementY" : 72.958, + "placementWidth" : 256.409, + "placementHeight" : 23.286, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 256.409, + "contentHeight" : 23.286, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/LowerRow[4]/LowerLeft[0]/Step_4Layer[6]/Step_4Holder[0]", + "entityName" : "Step_4Holder", + "entityKind" : "SectionNode", + "parentPath" : "Proposal[0]/LowerRow[4]/LowerLeft[0]/Step_4Layer[6]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 27.674, + "computedY" : 72.958, + "placementX" : 27.674, + "placementY" : 72.958, + "placementWidth" : 256.409, + "placementHeight" : 23.286, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 256.409, + "contentHeight" : 23.286, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/LowerRow[4]/LowerLeft[0]/Step_4Layer[6]/Step_4Holder[0]/Step_4[0]", + "entityName" : "Step_4", + "entityKind" : "RowNode", + "parentPath" : "Proposal[0]/LowerRow[4]/LowerLeft[0]/Step_4Layer[6]/Step_4Holder[0]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 27.674, + "computedY" : 72.958, + "placementX" : 27.674, + "placementY" : 72.958, + "placementWidth" : 256.409, + "placementHeight" : 23.286, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 256.409, + "contentHeight" : 23.286, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/LowerRow[4]/LowerLeft[0]/Step_4Layer[6]/Step_4Holder[0]/Step_4[0]/StepMark_4[0]", + "entityName" : "StepMark_4", + "entityKind" : "SectionNode", + "parentPath" : "Proposal[0]/LowerRow[4]/LowerLeft[0]/Step_4Layer[6]/Step_4Holder[0]/Step_4[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 27.674, + "computedY" : 74.783, + "placementX" : 27.674, + "placementY" : 74.783, + "placementWidth" : 21.462, + "placementHeight" : 21.462, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 21.462, + "contentHeight" : 21.462, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/LowerRow[4]/LowerLeft[0]/Step_4Layer[6]/Step_4Holder[0]/Step_4[0]/StepMark_4[0]/StepCircle_4[0]", + "entityName" : "StepCircle_4", + "entityKind" : "ShapeContainerNode", + "parentPath" : "Proposal[0]/LowerRow[4]/LowerLeft[0]/Step_4Layer[6]/Step_4Holder[0]/Step_4[0]/StepMark_4[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 27.674, + "computedY" : 74.783, + "placementX" : 27.674, + "placementY" : 74.783, + "placementWidth" : 21.462, + "placementHeight" : 21.462, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 21.462, + "contentHeight" : 21.462, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/LowerRow[4]/LowerLeft[0]/Step_4Layer[6]/Step_4Holder[0]/Step_4[0]/StepMark_4[0]/StepCircle_4[0]/StepNumber_4[0]", + "entityName" : "StepNumber_4", + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/LowerRow[4]/LowerLeft[0]/Step_4Layer[6]/Step_4Holder[0]/Step_4[0]/StepMark_4[0]/StepCircle_4[0]", + "childIndex" : 0, + "depth" : 9, + "layer" : 9, + "computedX" : 27.674, + "computedY" : 81.514, + "placementX" : 27.674, + "placementY" : 81.514, + "placementWidth" : 21.462, + "placementHeight" : 8.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 21.462, + "contentHeight" : 8.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/LowerRow[4]/LowerLeft[0]/Step_4Layer[6]/Step_4Holder[0]/Step_4[0]/StepText_4[1]", + "entityName" : "StepText_4", + "entityKind" : "SectionNode", + "parentPath" : "Proposal[0]/LowerRow[4]/LowerLeft[0]/Step_4Layer[6]/Step_4Holder[0]/Step_4[0]", + "childIndex" : 1, + "depth" : 7, + "layer" : 7, + "computedX" : 62.126, + "computedY" : 72.958, + "placementX" : 62.126, + "placementY" : 72.958, + "placementWidth" : 176.216, + "placementHeight" : 23.286, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 176.216, + "contentHeight" : 23.286, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/LowerRow[4]/LowerLeft[0]/Step_4Layer[6]/Step_4Holder[0]/Step_4[0]/StepText_4[1]/StepTitle_4[0]", + "entityName" : "StepTitle_4", + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/LowerRow[4]/LowerLeft[0]/Step_4Layer[6]/Step_4Holder[0]/Step_4[0]/StepText_4[1]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 62.126, + "computedY" : 83.755, + "placementX" : 62.126, + "placementY" : 83.755, + "placementWidth" : 65.14, + "placementHeight" : 7.6, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 65.14, + "contentHeight" : 7.6, + "margin" : { + "top" : 4.89, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/LowerRow[4]/LowerLeft[0]/Step_4Layer[6]/Step_4Holder[0]/Step_4[0]/StepText_4[1]/StepSubtitle_4[1]", + "entityName" : "StepSubtitle_4", + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/LowerRow[4]/LowerLeft[0]/Step_4Layer[6]/Step_4Holder[0]/Step_4[0]/StepText_4[1]", + "childIndex" : 1, + "depth" : 8, + "layer" : 8, + "computedX" : 62.126, + "computedY" : 72.958, + "placementX" : 62.126, + "placementY" : 72.958, + "placementWidth" : 176.216, + "placementHeight" : 7.4, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 176.216, + "contentHeight" : 7.4, + "margin" : { + "top" : 3.396, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/LowerRow[4]/ColumnDividerLower[1]", + "entityName" : "ColumnDividerLower", + "entityKind" : "SectionNode", + "parentPath" : "Proposal[0]/LowerRow[4]", + "childIndex" : 1, + "depth" : 3, + "layer" : 3, + "computedX" : 284.083, + "computedY" : 73.72, + "placementX" : 284.083, + "placementY" : 73.72, + "placementWidth" : 1.0, + "placementHeight" : 192.795, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 1.0, + "contentHeight" : 192.795, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/LowerRow[4]/ColumnDividerLower[1]/ColumnDividerLowerRule[0]", + "entityName" : "ColumnDividerLowerRule", + "entityKind" : "LineNode", + "parentPath" : "Proposal[0]/LowerRow[4]/ColumnDividerLower[1]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 284.083, + "computedY" : 73.72, + "placementX" : 284.083, + "placementY" : 73.72, + "placementWidth" : 1.0, + "placementHeight" : 192.589, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 1.0, + "contentHeight" : 192.589, + "margin" : { + "top" : 0.206, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/LowerRow[4]/LowerRight[2]", + "entityName" : "LowerRight", + "entityKind" : "SectionNode", + "parentPath" : "Proposal[0]/LowerRow[4]", + "childIndex" : 2, + "depth" : 3, + "layer" : 3, + "computedX" : 303.85, + "computedY" : 73.131, + "placementX" : 303.85, + "placementY" : 73.131, + "placementWidth" : 268.269, + "placementHeight" : 193.384, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 268.269, + "contentHeight" : 193.384, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 9.601, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/LowerRow[4]/LowerRight[2]/Investment[0]", + "entityName" : "Investment", + "entityKind" : "SectionNode", + "parentPath" : "Proposal[0]/LowerRow[4]/LowerRight[2]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 303.85, + "computedY" : 177.145, + "placementX" : 303.85, + "placementY" : 177.145, + "placementWidth" : 258.668, + "placementHeight" : 89.369, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 258.668, + "contentHeight" : 89.369, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/LowerRow[4]/LowerRight[2]/Investment[0]/InvestmentHeading[0]", + "entityName" : "InvestmentHeading", + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/LowerRow[4]/LowerRight[2]/Investment[0]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 303.85, + "computedY" : 258.415, + "placementX" : 303.85, + "placementY" : 258.415, + "placementWidth" : 96.333, + "placementHeight" : 8.1, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 96.333, + "contentHeight" : 8.1, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/LowerRow[4]/LowerRight[2]/Investment[0]/InvestmentTable[1]", + "entityName" : "InvestmentTable", + "entityKind" : "TableNode", + "parentPath" : "Proposal[0]/LowerRow[4]/LowerRight[2]/Investment[0]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 303.85, + "computedY" : 177.145, + "placementX" : 303.85, + "placementY" : 177.145, + "placementWidth" : 258.668, + "placementHeight" : 66.513, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 258.668, + "contentHeight" : 66.513, + "margin" : { + "top" : 14.756, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/LowerRow[4]/LowerRight[2]/TotalCard[1]", + "entityName" : "TotalCard", + "entityKind" : "SectionNode", + "parentPath" : "Proposal[0]/LowerRow[4]/LowerRight[2]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 303.85, + "computedY" : 132.563, + "placementX" : 303.85, + "placementY" : 132.563, + "placementWidth" : 258.668, + "placementHeight" : 33.887, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 258.668, + "contentHeight" : 33.887, + "margin" : { + "top" : 10.696, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 11.86, + "bottom" : 8.597, + "left" : 10.731 + } + }, { + "path" : "Proposal[0]/LowerRow[4]/LowerRight[2]/TotalCard[1]/TotalRowLayer[0]", + "entityName" : "TotalRowLayer", + "entityKind" : "LayerStackNode", + "parentPath" : "Proposal[0]/LowerRow[4]/LowerRight[2]/TotalCard[1]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 314.581, + "computedY" : 141.16, + "placementX" : 314.581, + "placementY" : 141.16, + "placementWidth" : 236.077, + "placementHeight" : 25.29, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 236.077, + "contentHeight" : 25.29, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/LowerRow[4]/LowerRight[2]/TotalCard[1]/TotalRowLayer[0]/TotalRowHolder[0]", + "entityName" : "TotalRowHolder", + "entityKind" : "SectionNode", + "parentPath" : "Proposal[0]/LowerRow[4]/LowerRight[2]/TotalCard[1]/TotalRowLayer[0]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 314.581, + "computedY" : 141.16, + "placementX" : 314.581, + "placementY" : 141.16, + "placementWidth" : 236.077, + "placementHeight" : 25.29, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 236.077, + "contentHeight" : 25.29, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/LowerRow[4]/LowerRight[2]/TotalCard[1]/TotalRowLayer[0]/TotalRowHolder[0]/TotalRow[0]", + "entityName" : "TotalRow", + "entityKind" : "RowNode", + "parentPath" : "Proposal[0]/LowerRow[4]/LowerRight[2]/TotalCard[1]/TotalRowLayer[0]/TotalRowHolder[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 314.581, + "computedY" : 141.16, + "placementX" : 314.581, + "placementY" : 141.16, + "placementWidth" : 236.077, + "placementHeight" : 13.2, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 236.077, + "contentHeight" : 13.2, + "margin" : { + "top" : 12.09, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/LowerRow[4]/LowerRight[2]/TotalCard[1]/TotalRowLayer[0]/TotalRowHolder[0]/TotalRow[0]/TotalLabel[0]", + "entityName" : "TotalLabel", + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/LowerRow[4]/LowerRight[2]/TotalCard[1]/TotalRowLayer[0]/TotalRowHolder[0]/TotalRow[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 314.581, + "computedY" : 144.21, + "placementX" : 314.581, + "placementY" : 144.21, + "placementWidth" : 102.609, + "placementHeight" : 7.1, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 102.609, + "contentHeight" : 7.1, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/LowerRow[4]/LowerRight[2]/TotalCard[1]/TotalRowLayer[0]/TotalRowHolder[0]/TotalRow[0]/TotalFigure[1]", + "entityName" : "TotalFigure", + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/LowerRow[4]/LowerRight[2]/TotalCard[1]/TotalRowLayer[0]/TotalRowHolder[0]/TotalRow[0]", + "childIndex" : 1, + "depth" : 8, + "layer" : 8, + "computedX" : 432.62, + "computedY" : 141.16, + "placementX" : 432.62, + "placementY" : 141.16, + "placementWidth" : 118.039, + "placementHeight" : 13.2, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 118.039, + "contentHeight" : 13.2, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/LowerRow[4]/LowerRight[2]/NotesHeading[2]", + "entityName" : "NotesHeading", + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/LowerRow[4]/LowerRight[2]", + "childIndex" : 2, + "depth" : 4, + "layer" : 4, + "computedX" : 303.85, + "computedY" : 108.29, + "placementX" : 303.85, + "placementY" : 108.29, + "placementWidth" : 27.2, + "placementHeight" : 8.1, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 27.2, + "contentHeight" : 8.1, + "margin" : { + "top" : 16.173, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/LowerRow[4]/LowerRight[2]/NotesList[3]", + "entityName" : "NotesList", + "entityKind" : "ListNode", + "parentPath" : "Proposal[0]/LowerRow[4]/LowerRight[2]", + "childIndex" : 3, + "depth" : 4, + "layer" : 4, + "computedX" : 303.85, + "computedY" : 73.131, + "placementX" : 303.85, + "placementY" : 73.131, + "placementWidth" : 185.511, + "placementHeight" : 28.297, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 185.511, + "contentHeight" : 28.297, + "margin" : { + "top" : 6.863, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/FooterRule[5]", + "entityName" : "FooterRule", + "entityKind" : "LineNode", + "parentPath" : "Proposal[0]", + "childIndex" : 5, + "depth" : 2, + "layer" : 2, + "computedX" : 27.674, + "computedY" : 54.016, + "placementX" : 27.674, + "placementY" : 54.016, + "placementWidth" : 544.446, + "placementHeight" : 1.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 544.446, + "contentHeight" : 1.0, + "margin" : { + "top" : 17.942, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/Footer[6]", + "entityName" : "Footer", + "entityKind" : "RowNode", + "parentPath" : "Proposal[0]", + "childIndex" : 6, + "depth" : 2, + "layer" : 2, + "computedX" : 27.674, + "computedY" : 14.442, + "placementX" : 27.674, + "placementY" : 14.442, + "placementWidth" : 544.446, + "placementHeight" : 28.741, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 544.446, + "contentHeight" : 28.741, + "margin" : { + "top" : 10.833, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/Footer[6]/FooterMark[0]", + "entityName" : "FooterMark", + "entityKind" : "SectionNode", + "parentPath" : "Proposal[0]/Footer[6]", + "childIndex" : 0, + "depth" : 3, + "layer" : 3, + "computedX" : 27.674, + "computedY" : 23.103, + "placementX" : 27.674, + "placementY" : 23.103, + "placementWidth" : 13.612, + "placementHeight" : 20.08, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 13.612, + "contentHeight" : 20.08, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/Footer[6]/FooterMark[0]/FooterMonogram[0]", + "entityName" : "FooterMonogram", + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/Footer[6]/FooterMark[0]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 27.674, + "computedY" : 23.103, + "placementX" : 27.674, + "placementY" : 23.103, + "placementWidth" : 13.612, + "placementHeight" : 19.9, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 13.612, + "contentHeight" : 19.9, + "margin" : { + "top" : 0.18, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/Footer[6]/FooterIdentity[1]", + "entityName" : "FooterIdentity", + "entityKind" : "SectionNode", + "parentPath" : "Proposal[0]/Footer[6]", + "childIndex" : 1, + "depth" : 3, + "layer" : 3, + "computedX" : 60.996, + "computedY" : 14.442, + "placementX" : 60.996, + "placementY" : 14.442, + "placementWidth" : 245.854, + "placementHeight" : 28.741, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 245.854, + "contentHeight" : 28.741, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/Footer[6]/FooterIdentity[1]/FooterName[0]", + "entityName" : "FooterName", + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/Footer[6]/FooterIdentity[1]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 60.996, + "computedY" : 36.083, + "placementX" : 60.996, + "placementY" : 36.083, + "placementWidth" : 72.825, + "placementHeight" : 7.1, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 72.825, + "contentHeight" : 7.1, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/Footer[6]/FooterIdentity[1]/FooterAddress[1]", + "entityName" : "FooterAddress", + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/Footer[6]/FooterIdentity[1]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 60.996, + "computedY" : 25.737, + "placementX" : 60.996, + "placementY" : 25.737, + "placementWidth" : 211.505, + "placementHeight" : 7.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 211.505, + "contentHeight" : 7.0, + "margin" : { + "top" : 3.346, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/Footer[6]/FooterIdentity[1]/FooterChannels[2]", + "entityName" : "FooterChannels", + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/Footer[6]/FooterIdentity[1]", + "childIndex" : 2, + "depth" : 4, + "layer" : 4, + "computedX" : 60.996, + "computedY" : 14.442, + "placementX" : 60.996, + "placementY" : 14.442, + "placementWidth" : 245.854, + "placementHeight" : 7.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 245.854, + "contentHeight" : 7.0, + "margin" : { + "top" : 4.296, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + } ] +} diff --git a/qa/src/test/resources/layout-snapshots/canonical-templates/proposal/northline_proposal_layout.json b/qa/src/test/resources/layout-snapshots/canonical-templates/proposal/northline_proposal_layout.json new file mode 100644 index 000000000..7c0c4fc6a --- /dev/null +++ b/qa/src/test/resources/layout-snapshots/canonical-templates/proposal/northline_proposal_layout.json @@ -0,0 +1,5267 @@ +{ + "formatVersion" : "2.0", + "canvas" : { + "pageWidth" : 595.276, + "pageHeight" : 841.89, + "innerWidth" : 523.276, + "innerHeight" : 785.89, + "margin" : { + "top" : 8.0, + "right" : 36.0, + "bottom" : 48.0, + "left" : 36.0 + } + }, + "totalPages" : 2, + "nodes" : [ { + "path" : "Proposal[0]", + "entityName" : "Proposal", + "entityKind" : "ContainerNode", + "parentPath" : null, + "childIndex" : 0, + "depth" : 1, + "layer" : 1, + "computedX" : 36.0, + "computedY" : -690.811, + "placementX" : 36.0, + "placementY" : -690.811, + "placementWidth" : 523.276, + "placementHeight" : 1524.701, + "startPage" : 0, + "endPage" : 1, + "contentWidth" : 523.276, + "contentHeight" : 1524.701, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/Header[0]", + "entityName" : "Header", + "entityKind" : "SectionNode", + "parentPath" : "Proposal[0]", + "childIndex" : 0, + "depth" : 2, + "layer" : 2, + "computedX" : 36.0, + "computedY" : 775.89, + "placementX" : 36.0, + "placementY" : 775.89, + "placementWidth" : 523.276, + "placementHeight" : 58.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 523.276, + "contentHeight" : 58.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/Header[0]/HeaderRow[0]", + "entityName" : "HeaderRow", + "entityKind" : "RowNode", + "parentPath" : "Proposal[0]/Header[0]", + "childIndex" : 0, + "depth" : 3, + "layer" : 3, + "computedX" : 36.0, + "computedY" : 783.89, + "placementX" : 36.0, + "placementY" : 783.89, + "placementWidth" : 523.276, + "placementHeight" : 50.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 523.276, + "contentHeight" : 50.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/Header[0]/HeaderRow[0]/LogoMark[0]", + "entityName" : "LogoMark", + "entityKind" : "ShapeContainerNode", + "parentPath" : "Proposal[0]/Header[0]/HeaderRow[0]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 36.0, + "computedY" : 783.89, + "placementX" : 36.0, + "placementY" : 783.89, + "placementWidth" : 50.0, + "placementHeight" : 50.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 50.0, + "contentHeight" : 50.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/Header[0]/HeaderRow[0]/LogoMark[0]/Monogram[0]", + "entityName" : "Monogram", + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/Header[0]/HeaderRow[0]/LogoMark[0]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 36.0, + "computedY" : 790.89, + "placementX" : 36.0, + "placementY" : 790.89, + "placementWidth" : 50.0, + "placementHeight" : 36.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 50.0, + "contentHeight" : 36.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/Header[0]/HeaderRow[0]/Wordmark[1]", + "entityName" : "Wordmark", + "entityKind" : "SectionNode", + "parentPath" : "Proposal[0]/Header[0]/HeaderRow[0]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 98.5, + "computedY" : 789.69, + "placementX" : 98.5, + "placementY" : 789.69, + "placementWidth" : 92.096, + "placementHeight" : 38.4, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 92.096, + "contentHeight" : 38.4, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/Header[0]/HeaderRow[0]/Wordmark[1]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/Header[0]/HeaderRow[0]/Wordmark[1]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 98.5, + "computedY" : 808.89, + "placementX" : 98.5, + "placementY" : 808.89, + "placementWidth" : 92.096, + "placementHeight" : 19.2, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 92.096, + "contentHeight" : 19.2, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/Header[0]/HeaderRow[0]/Wordmark[1]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/Header[0]/HeaderRow[0]/Wordmark[1]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 98.5, + "computedY" : 789.69, + "placementX" : 98.5, + "placementY" : 789.69, + "placementWidth" : 60.128, + "placementHeight" : 19.2, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 60.128, + "contentHeight" : 19.2, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/Header[0]/HeaderRow[0]/SpacerNode[2]", + "entityName" : null, + "entityKind" : "SpacerNode", + "parentPath" : "Proposal[0]/Header[0]/HeaderRow[0]", + "childIndex" : 2, + "depth" : 4, + "layer" : 4, + "computedX" : 210.422, + "computedY" : 808.89, + "placementX" : 210.422, + "placementY" : 808.89, + "placementWidth" : 0.0, + "placementHeight" : 0.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 0.0, + "contentHeight" : 0.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/Header[0]/HeaderRow[0]/DocumentLabel[3]", + "entityName" : "DocumentLabel", + "entityKind" : "SectionNode", + "parentPath" : "Proposal[0]/Header[0]/HeaderRow[0]", + "childIndex" : 3, + "depth" : 4, + "layer" : 4, + "computedX" : 475.552, + "computedY" : 799.39, + "placementX" : 475.552, + "placementY" : 799.39, + "placementWidth" : 83.724, + "placementHeight" : 19.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 83.724, + "contentHeight" : 19.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/Header[0]/HeaderRow[0]/DocumentLabel[3]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/Header[0]/HeaderRow[0]/DocumentLabel[3]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 475.552, + "computedY" : 807.59, + "placementX" : 475.552, + "placementY" : 807.59, + "placementWidth" : 83.724, + "placementHeight" : 10.8, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 83.724, + "contentHeight" : 10.8, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/Header[0]/HeaderRow[0]/DocumentLabel[3]/Align[1]", + "entityName" : null, + "entityKind" : "Align", + "parentPath" : "Proposal[0]/Header[0]/HeaderRow[0]/DocumentLabel[3]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 475.552, + "computedY" : 799.39, + "placementX" : 475.552, + "placementY" : 799.39, + "placementWidth" : 83.724, + "placementHeight" : 8.2, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 83.724, + "contentHeight" : 8.2, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/Header[0]/HeaderRow[0]/DocumentLabel[3]/Align[1]/DocumentLabelRule[0]", + "entityName" : "DocumentLabelRule", + "entityKind" : "LineNode", + "parentPath" : "Proposal[0]/Header[0]/HeaderRow[0]/DocumentLabel[3]/Align[1]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 524.176, + "computedY" : 799.39, + "placementX" : 524.176, + "placementY" : 799.39, + "placementWidth" : 35.1, + "placementHeight" : 2.2, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 35.1, + "contentHeight" : 2.2, + "margin" : { + "top" : 6.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/Header[0]/LineNode[1]", + "entityName" : null, + "entityKind" : "LineNode", + "parentPath" : "Proposal[0]/Header[0]", + "childIndex" : 1, + "depth" : 3, + "layer" : 3, + "computedX" : 36.0, + "computedY" : 775.89, + "placementX" : 36.0, + "placementY" : 775.89, + "placementWidth" : 523.276, + "placementHeight" : 1.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 523.276, + "contentHeight" : 1.0, + "margin" : { + "top" : 7.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/TitleBlock[1]", + "entityName" : "TitleBlock", + "entityKind" : "SectionNode", + "parentPath" : "Proposal[0]", + "childIndex" : 1, + "depth" : 2, + "layer" : 2, + "computedX" : 36.0, + "computedY" : 613.59, + "placementX" : 36.0, + "placementY" : 613.59, + "placementWidth" : 523.276, + "placementHeight" : 144.3, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 523.276, + "contentHeight" : 144.3, + "margin" : { + "top" : 18.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/TitleBlock[1]/LineNode[0]", + "entityName" : null, + "entityKind" : "LineNode", + "parentPath" : "Proposal[0]/TitleBlock[1]", + "childIndex" : 0, + "depth" : 3, + "layer" : 3, + "computedX" : 36.0, + "computedY" : 755.29, + "placementX" : 36.0, + "placementY" : 755.29, + "placementWidth" : 45.002, + "placementHeight" : 2.6, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 45.002, + "contentHeight" : 2.6, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 2.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/TitleBlock[1]/TitleLines[1]", + "entityName" : "TitleLines", + "entityKind" : "ShapeContainerNode", + "parentPath" : "Proposal[0]/TitleBlock[1]", + "childIndex" : 1, + "depth" : 3, + "layer" : 3, + "computedX" : 36.0, + "computedY" : 613.59, + "placementX" : 36.0, + "placementY" : 613.59, + "placementWidth" : 523.276, + "placementHeight" : 139.7, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 523.276, + "contentHeight" : 139.7, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/TitleBlock[1]/TitleLines[1]/TitleLead[0]", + "entityName" : "TitleLead", + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/TitleBlock[1]/TitleLines[1]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 36.0, + "computedY" : 683.278, + "placementX" : 36.0, + "placementY" : 683.278, + "placementWidth" : 221.72, + "placementHeight" : 70.012, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 221.72, + "contentHeight" : 70.012, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/TitleBlock[1]/TitleLines[1]/TitleLine1[1]", + "entityName" : "TitleLine1", + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/TitleBlock[1]/TitleLines[1]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 36.0, + "computedY" : 635.278, + "placementX" : 36.0, + "placementY" : 635.278, + "placementWidth" : 353.372, + "placementHeight" : 70.012, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 353.372, + "contentHeight" : 70.012, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/TitleBlock[1]/TitleLines[1]/TitleLine2[2]", + "entityName" : "TitleLine2", + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/TitleBlock[1]/TitleLines[1]", + "childIndex" : 2, + "depth" : 4, + "layer" : 4, + "computedX" : 36.0, + "computedY" : 587.278, + "placementX" : 36.0, + "placementY" : 587.278, + "placementWidth" : 379.638, + "placementHeight" : 70.012, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 379.638, + "contentHeight" : 70.012, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/TitleMeta[2]", + "entityName" : "TitleMeta", + "entityKind" : "SectionNode", + "parentPath" : "Proposal[0]", + "childIndex" : 2, + "depth" : 2, + "layer" : 2, + "computedX" : 36.0, + "computedY" : 585.99, + "placementX" : 36.0, + "placementY" : 585.99, + "placementWidth" : 366.336, + "placementHeight" : 9.6, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 366.336, + "contentHeight" : 9.6, + "margin" : { + "top" : 18.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/TitleMeta[2]/MetaLine[0]", + "entityName" : "MetaLine", + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/TitleMeta[2]", + "childIndex" : 0, + "depth" : 3, + "layer" : 3, + "computedX" : 36.0, + "computedY" : 585.99, + "placementX" : 36.0, + "placementY" : 585.99, + "placementWidth" : 366.336, + "placementHeight" : 9.6, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 366.336, + "contentHeight" : 9.6, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/SummaryBand[3]", + "entityName" : "SummaryBand", + "entityKind" : "RowNode", + "parentPath" : "Proposal[0]", + "childIndex" : 3, + "depth" : 2, + "layer" : 2, + "computedX" : 36.0, + "computedY" : 341.89, + "placementX" : 36.0, + "placementY" : 341.89, + "placementWidth" : 523.276, + "placementHeight" : 226.1, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 523.276, + "contentHeight" : 226.1, + "margin" : { + "top" : 18.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/SummaryBand[3]/SummaryColumn[0]", + "entityName" : "SummaryColumn", + "entityKind" : "SectionNode", + "parentPath" : "Proposal[0]/SummaryBand[3]", + "childIndex" : 0, + "depth" : 3, + "layer" : 3, + "computedX" : 36.0, + "computedY" : 360.04, + "placementX" : 36.0, + "placementY" : 360.04, + "placementWidth" : 324.431, + "placementHeight" : 207.95, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 324.431, + "contentHeight" : 207.95, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/SummaryBand[3]/SummaryColumn[0]/SectionHeading[0]", + "entityName" : "SectionHeading", + "entityKind" : "ShapeContainerNode", + "parentPath" : "Proposal[0]/SummaryBand[3]/SummaryColumn[0]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 36.0, + "computedY" : 540.99, + "placementX" : 36.0, + "placementY" : 540.99, + "placementWidth" : 324.431, + "placementHeight" : 27.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 324.431, + "contentHeight" : 27.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/SummaryBand[3]/SummaryColumn[0]/SectionHeading[0]/SectionBadge[0]", + "entityName" : "SectionBadge", + "entityKind" : "ShapeContainerNode", + "parentPath" : "Proposal[0]/SummaryBand[3]/SummaryColumn[0]/SectionHeading[0]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 36.0, + "computedY" : 540.99, + "placementX" : 36.0, + "placementY" : 540.99, + "placementWidth" : 27.0, + "placementHeight" : 27.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 27.0, + "contentHeight" : 27.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/SummaryBand[3]/SummaryColumn[0]/SectionHeading[0]/SectionBadge[0]/Icon-badge-summary[0]", + "entityName" : "Icon-badge-summary", + "entityKind" : "ImageNode", + "parentPath" : "Proposal[0]/SummaryBand[3]/SummaryColumn[0]/SectionHeading[0]/SectionBadge[0]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 42.48, + "computedY" : 547.47, + "placementX" : 42.48, + "placementY" : 547.47, + "placementWidth" : 14.04, + "placementHeight" : 14.04, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 14.04, + "contentHeight" : 14.04, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/SummaryBand[3]/SummaryColumn[0]/SectionHeading[0]/SectionHeadingTitle[1]", + "entityName" : "SectionHeadingTitle", + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/SummaryBand[3]/SummaryColumn[0]/SectionHeading[0]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 74.34, + "computedY" : 546.39, + "placementX" : 74.34, + "placementY" : 546.39, + "placementWidth" : 145.854, + "placementHeight" : 16.2, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 145.854, + "contentHeight" : 16.2, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/SummaryBand[3]/SummaryColumn[0]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/SummaryBand[3]/SummaryColumn[0]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 36.0, + "computedY" : 468.19, + "placementX" : 36.0, + "placementY" : 468.19, + "placementWidth" : 318.63, + "placementHeight" : 65.8, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 318.63, + "contentHeight" : 65.8, + "margin" : { + "top" : 7.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/SummaryBand[3]/SummaryColumn[0]/ParagraphNode[2]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/SummaryBand[3]/SummaryColumn[0]", + "childIndex" : 2, + "depth" : 4, + "layer" : 4, + "computedX" : 36.0, + "computedY" : 405.04, + "placementX" : 36.0, + "placementY" : 405.04, + "placementWidth" : 314.24, + "placementHeight" : 52.35, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 314.24, + "contentHeight" : 52.35, + "margin" : { + "top" : 10.8, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/SummaryBand[3]/SummaryColumn[0]/SpacerNode[3]", + "entityName" : null, + "entityKind" : "SpacerNode", + "parentPath" : "Proposal[0]/SummaryBand[3]/SummaryColumn[0]", + "childIndex" : 3, + "depth" : 4, + "layer" : 4, + "computedX" : 36.0, + "computedY" : 387.04, + "placementX" : 36.0, + "placementY" : 387.04, + "placementWidth" : 0.0, + "placementHeight" : 18.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 0.0, + "contentHeight" : 18.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/SummaryBand[3]/SummaryColumn[0]/SectionHeading[4]", + "entityName" : "SectionHeading", + "entityKind" : "ShapeContainerNode", + "parentPath" : "Proposal[0]/SummaryBand[3]/SummaryColumn[0]", + "childIndex" : 4, + "depth" : 4, + "layer" : 4, + "computedX" : 36.0, + "computedY" : 360.04, + "placementX" : 36.0, + "placementY" : 360.04, + "placementWidth" : 324.431, + "placementHeight" : 27.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 324.431, + "contentHeight" : 27.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/SummaryBand[3]/SummaryColumn[0]/SectionHeading[4]/SectionBadge[0]", + "entityName" : "SectionBadge", + "entityKind" : "ShapeContainerNode", + "parentPath" : "Proposal[0]/SummaryBand[3]/SummaryColumn[0]/SectionHeading[4]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 36.0, + "computedY" : 360.04, + "placementX" : 36.0, + "placementY" : 360.04, + "placementWidth" : 27.0, + "placementHeight" : 27.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 27.0, + "contentHeight" : 27.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/SummaryBand[3]/SummaryColumn[0]/SectionHeading[4]/SectionBadge[0]/Icon-badge-goals[0]", + "entityName" : "Icon-badge-goals", + "entityKind" : "ImageNode", + "parentPath" : "Proposal[0]/SummaryBand[3]/SummaryColumn[0]/SectionHeading[4]/SectionBadge[0]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 42.48, + "computedY" : 366.52, + "placementX" : 42.48, + "placementY" : 366.52, + "placementWidth" : 14.04, + "placementHeight" : 14.04, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 14.04, + "contentHeight" : 14.04, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/SummaryBand[3]/SummaryColumn[0]/SectionHeading[4]/SectionHeadingTitle[1]", + "entityName" : "SectionHeadingTitle", + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/SummaryBand[3]/SummaryColumn[0]/SectionHeading[4]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 74.34, + "computedY" : 365.44, + "placementX" : 74.34, + "placementY" : 365.44, + "placementWidth" : 106.353, + "placementHeight" : 16.2, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 106.353, + "contentHeight" : 16.2, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/SummaryBand[3]/GlanceColumn[1]", + "entityName" : "GlanceColumn", + "entityKind" : "SectionNode", + "parentPath" : "Proposal[0]/SummaryBand[3]", + "childIndex" : 1, + "depth" : 3, + "layer" : 3, + "computedX" : 376.129, + "computedY" : 341.89, + "placementX" : 376.129, + "placementY" : 341.89, + "placementWidth" : 183.147, + "placementHeight" : 226.1, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 183.147, + "contentHeight" : 226.1, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/SummaryBand[3]/GlanceColumn[1]/GlancePanel[0]", + "entityName" : "GlancePanel", + "entityKind" : "SectionNode", + "parentPath" : "Proposal[0]/SummaryBand[3]/GlanceColumn[1]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 376.129, + "computedY" : 341.89, + "placementX" : 376.129, + "placementY" : 341.89, + "placementWidth" : 183.147, + "placementHeight" : 226.1, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 183.147, + "contentHeight" : 226.1, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 16.0, + "right" : 16.0, + "bottom" : 16.0, + "left" : 16.0 + } + }, { + "path" : "Proposal[0]/SummaryBand[3]/GlanceColumn[1]/GlancePanel[0]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/SummaryBand[3]/GlanceColumn[1]/GlancePanel[0]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 392.129, + "computedY" : 539.39, + "placementX" : 392.129, + "placementY" : 539.39, + "placementWidth" : 115.553, + "placementHeight" : 12.6, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 115.553, + "contentHeight" : 12.6, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 7.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/SummaryBand[3]/GlanceColumn[1]/GlancePanel[0]/GlanceFact[1]", + "entityName" : "GlanceFact", + "entityKind" : "ShapeContainerNode", + "parentPath" : "Proposal[0]/SummaryBand[3]/GlanceColumn[1]/GlancePanel[0]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 392.129, + "computedY" : 502.99, + "placementX" : 392.129, + "placementY" : 502.99, + "placementWidth" : 151.147, + "placementHeight" : 29.4, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 151.147, + "contentHeight" : 29.4, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/SummaryBand[3]/GlanceColumn[1]/GlancePanel[0]/GlanceFact[1]/Icon-fact-duration[0]", + "entityName" : "Icon-fact-duration", + "entityKind" : "ImageNode", + "parentPath" : "Proposal[0]/SummaryBand[3]/GlanceColumn[1]/GlancePanel[0]/GlanceFact[1]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 392.129, + "computedY" : 505.19, + "placementX" : 392.129, + "placementY" : 505.19, + "placementWidth" : 25.0, + "placementHeight" : 25.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 25.0, + "contentHeight" : 25.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/SummaryBand[3]/GlanceColumn[1]/GlancePanel[0]/GlanceFact[1]/FactText[1]", + "entityName" : "FactText", + "entityKind" : "SectionNode", + "parentPath" : "Proposal[0]/SummaryBand[3]/GlanceColumn[1]/GlancePanel[0]/GlanceFact[1]", + "childIndex" : 1, + "depth" : 6, + "layer" : 6, + "computedX" : 429.129, + "computedY" : 504.49, + "placementX" : 429.129, + "placementY" : 504.49, + "placementWidth" : 79.184, + "placementHeight" : 26.4, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 79.184, + "contentHeight" : 26.4, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/SummaryBand[3]/GlanceColumn[1]/GlancePanel[0]/GlanceFact[1]/FactText[1]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/SummaryBand[3]/GlanceColumn[1]/GlancePanel[0]/GlanceFact[1]/FactText[1]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 429.129, + "computedY" : 521.29, + "placementX" : 429.129, + "placementY" : 521.29, + "placementWidth" : 79.184, + "placementHeight" : 9.6, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 79.184, + "contentHeight" : 9.6, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/SummaryBand[3]/GlanceColumn[1]/GlancePanel[0]/GlanceFact[1]/FactText[1]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/SummaryBand[3]/GlanceColumn[1]/GlancePanel[0]/GlanceFact[1]/FactText[1]", + "childIndex" : 1, + "depth" : 7, + "layer" : 7, + "computedX" : 429.129, + "computedY" : 504.49, + "placementX" : 429.129, + "placementY" : 504.49, + "placementWidth" : 51.366, + "placementHeight" : 16.8, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 51.366, + "contentHeight" : 16.8, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/SummaryBand[3]/GlanceColumn[1]/GlancePanel[0]/LineNode[2]", + "entityName" : null, + "entityKind" : "LineNode", + "parentPath" : "Proposal[0]/SummaryBand[3]/GlanceColumn[1]/GlancePanel[0]", + "childIndex" : 2, + "depth" : 5, + "layer" : 5, + "computedX" : 392.129, + "computedY" : 494.99, + "placementX" : 392.129, + "placementY" : 494.99, + "placementWidth" : 151.147, + "placementHeight" : 1.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 151.147, + "contentHeight" : 1.0, + "margin" : { + "top" : 7.0, + "right" : 0.0, + "bottom" : 7.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/SummaryBand[3]/GlanceColumn[1]/GlancePanel[0]/GlanceFact[3]", + "entityName" : "GlanceFact", + "entityKind" : "ShapeContainerNode", + "parentPath" : "Proposal[0]/SummaryBand[3]/GlanceColumn[1]/GlancePanel[0]", + "childIndex" : 3, + "depth" : 5, + "layer" : 5, + "computedX" : 392.129, + "computedY" : 458.59, + "placementX" : 392.129, + "placementY" : 458.59, + "placementWidth" : 151.147, + "placementHeight" : 29.4, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 151.147, + "contentHeight" : 29.4, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/SummaryBand[3]/GlanceColumn[1]/GlancePanel[0]/GlanceFact[3]/Icon-fact-start[0]", + "entityName" : "Icon-fact-start", + "entityKind" : "ImageNode", + "parentPath" : "Proposal[0]/SummaryBand[3]/GlanceColumn[1]/GlancePanel[0]/GlanceFact[3]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 392.129, + "computedY" : 460.79, + "placementX" : 392.129, + "placementY" : 460.79, + "placementWidth" : 25.0, + "placementHeight" : 25.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 25.0, + "contentHeight" : 25.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/SummaryBand[3]/GlanceColumn[1]/GlancePanel[0]/GlanceFact[3]/FactText[1]", + "entityName" : "FactText", + "entityKind" : "SectionNode", + "parentPath" : "Proposal[0]/SummaryBand[3]/GlanceColumn[1]/GlancePanel[0]/GlanceFact[3]", + "childIndex" : 1, + "depth" : 6, + "layer" : 6, + "computedX" : 429.129, + "computedY" : 460.09, + "placementX" : 429.129, + "placementY" : 460.09, + "placementWidth" : 104.062, + "placementHeight" : 26.4, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 104.062, + "contentHeight" : 26.4, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/SummaryBand[3]/GlanceColumn[1]/GlancePanel[0]/GlanceFact[3]/FactText[1]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/SummaryBand[3]/GlanceColumn[1]/GlancePanel[0]/GlanceFact[3]/FactText[1]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 429.129, + "computedY" : 476.89, + "placementX" : 429.129, + "placementY" : 476.89, + "placementWidth" : 64.552, + "placementHeight" : 9.6, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 64.552, + "contentHeight" : 9.6, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/SummaryBand[3]/GlanceColumn[1]/GlancePanel[0]/GlanceFact[3]/FactText[1]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/SummaryBand[3]/GlanceColumn[1]/GlancePanel[0]/GlanceFact[3]/FactText[1]", + "childIndex" : 1, + "depth" : 7, + "layer" : 7, + "computedX" : 429.129, + "computedY" : 460.09, + "placementX" : 429.129, + "placementY" : 460.09, + "placementWidth" : 104.062, + "placementHeight" : 16.8, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 104.062, + "contentHeight" : 16.8, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/SummaryBand[3]/GlanceColumn[1]/GlancePanel[0]/LineNode[4]", + "entityName" : null, + "entityKind" : "LineNode", + "parentPath" : "Proposal[0]/SummaryBand[3]/GlanceColumn[1]/GlancePanel[0]", + "childIndex" : 4, + "depth" : 5, + "layer" : 5, + "computedX" : 392.129, + "computedY" : 450.59, + "placementX" : 392.129, + "placementY" : 450.59, + "placementWidth" : 151.147, + "placementHeight" : 1.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 151.147, + "contentHeight" : 1.0, + "margin" : { + "top" : 7.0, + "right" : 0.0, + "bottom" : 7.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/SummaryBand[3]/GlanceColumn[1]/GlancePanel[0]/GlanceFact[5]", + "entityName" : "GlanceFact", + "entityKind" : "ShapeContainerNode", + "parentPath" : "Proposal[0]/SummaryBand[3]/GlanceColumn[1]/GlancePanel[0]", + "childIndex" : 5, + "depth" : 5, + "layer" : 5, + "computedX" : 392.129, + "computedY" : 414.19, + "placementX" : 392.129, + "placementY" : 414.19, + "placementWidth" : 151.147, + "placementHeight" : 29.4, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 151.147, + "contentHeight" : 29.4, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/SummaryBand[3]/GlanceColumn[1]/GlancePanel[0]/GlanceFact[5]/Icon-fact-contact[0]", + "entityName" : "Icon-fact-contact", + "entityKind" : "ImageNode", + "parentPath" : "Proposal[0]/SummaryBand[3]/GlanceColumn[1]/GlancePanel[0]/GlanceFact[5]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 392.129, + "computedY" : 416.39, + "placementX" : 392.129, + "placementY" : 416.39, + "placementWidth" : 25.0, + "placementHeight" : 25.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 25.0, + "contentHeight" : 25.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/SummaryBand[3]/GlanceColumn[1]/GlancePanel[0]/GlanceFact[5]/FactText[1]", + "entityName" : "FactText", + "entityKind" : "SectionNode", + "parentPath" : "Proposal[0]/SummaryBand[3]/GlanceColumn[1]/GlancePanel[0]/GlanceFact[5]", + "childIndex" : 1, + "depth" : 6, + "layer" : 6, + "computedX" : 429.129, + "computedY" : 415.69, + "placementX" : 429.129, + "placementY" : 415.69, + "placementWidth" : 78.148, + "placementHeight" : 26.4, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 78.148, + "contentHeight" : 26.4, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/SummaryBand[3]/GlanceColumn[1]/GlancePanel[0]/GlanceFact[5]/FactText[1]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/SummaryBand[3]/GlanceColumn[1]/GlancePanel[0]/GlanceFact[5]/FactText[1]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 429.129, + "computedY" : 432.49, + "placementX" : 429.129, + "placementY" : 432.49, + "placementWidth" : 76.248, + "placementHeight" : 9.6, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 76.248, + "contentHeight" : 9.6, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/SummaryBand[3]/GlanceColumn[1]/GlancePanel[0]/GlanceFact[5]/FactText[1]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/SummaryBand[3]/GlanceColumn[1]/GlancePanel[0]/GlanceFact[5]/FactText[1]", + "childIndex" : 1, + "depth" : 7, + "layer" : 7, + "computedX" : 429.129, + "computedY" : 415.69, + "placementX" : 429.129, + "placementY" : 415.69, + "placementWidth" : 78.148, + "placementHeight" : 16.8, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 78.148, + "contentHeight" : 16.8, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/SummaryBand[3]/GlanceColumn[1]/GlancePanel[0]/LineNode[6]", + "entityName" : null, + "entityKind" : "LineNode", + "parentPath" : "Proposal[0]/SummaryBand[3]/GlanceColumn[1]/GlancePanel[0]", + "childIndex" : 6, + "depth" : 5, + "layer" : 5, + "computedX" : 392.129, + "computedY" : 406.19, + "placementX" : 392.129, + "placementY" : 406.19, + "placementWidth" : 151.147, + "placementHeight" : 1.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 151.147, + "contentHeight" : 1.0, + "margin" : { + "top" : 7.0, + "right" : 0.0, + "bottom" : 7.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/SummaryBand[3]/GlanceColumn[1]/GlancePanel[0]/GlanceFact[7]", + "entityName" : "GlanceFact", + "entityKind" : "ShapeContainerNode", + "parentPath" : "Proposal[0]/SummaryBand[3]/GlanceColumn[1]/GlancePanel[0]", + "childIndex" : 7, + "depth" : 5, + "layer" : 5, + "computedX" : 392.129, + "computedY" : 357.89, + "placementX" : 392.129, + "placementY" : 357.89, + "placementWidth" : 151.147, + "placementHeight" : 41.3, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 151.147, + "contentHeight" : 41.3, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/SummaryBand[3]/GlanceColumn[1]/GlancePanel[0]/GlanceFact[7]/Icon-fact-validity[0]", + "entityName" : "Icon-fact-validity", + "entityKind" : "ImageNode", + "parentPath" : "Proposal[0]/SummaryBand[3]/GlanceColumn[1]/GlancePanel[0]/GlanceFact[7]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 392.129, + "computedY" : 366.04, + "placementX" : 392.129, + "placementY" : 366.04, + "placementWidth" : 25.0, + "placementHeight" : 25.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 25.0, + "contentHeight" : 25.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/SummaryBand[3]/GlanceColumn[1]/GlancePanel[0]/GlanceFact[7]/FactText[1]", + "entityName" : "FactText", + "entityKind" : "SectionNode", + "parentPath" : "Proposal[0]/SummaryBand[3]/GlanceColumn[1]/GlancePanel[0]/GlanceFact[7]", + "childIndex" : 1, + "depth" : 6, + "layer" : 6, + "computedX" : 429.129, + "computedY" : 360.24, + "placementX" : 429.129, + "placementY" : 360.24, + "placementWidth" : 93.05, + "placementHeight" : 36.6, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 93.05, + "contentHeight" : 36.6, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/SummaryBand[3]/GlanceColumn[1]/GlancePanel[0]/GlanceFact[7]/FactText[1]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/SummaryBand[3]/GlanceColumn[1]/GlancePanel[0]/GlanceFact[7]/FactText[1]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 429.129, + "computedY" : 387.24, + "placementX" : 429.129, + "placementY" : 387.24, + "placementWidth" : 80.136, + "placementHeight" : 9.6, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 80.136, + "contentHeight" : 9.6, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/SummaryBand[3]/GlanceColumn[1]/GlancePanel[0]/GlanceFact[7]/FactText[1]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/SummaryBand[3]/GlanceColumn[1]/GlancePanel[0]/GlanceFact[7]/FactText[1]", + "childIndex" : 1, + "depth" : 7, + "layer" : 7, + "computedX" : 429.129, + "computedY" : 370.44, + "placementX" : 429.129, + "placementY" : 370.44, + "placementWidth" : 48.258, + "placementHeight" : 16.8, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 48.258, + "contentHeight" : 16.8, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/SummaryBand[3]/GlanceColumn[1]/GlancePanel[0]/GlanceFact[7]/FactText[1]/ParagraphNode[2]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/SummaryBand[3]/GlanceColumn[1]/GlancePanel[0]/GlanceFact[7]/FactText[1]", + "childIndex" : 2, + "depth" : 7, + "layer" : 7, + "computedX" : 429.129, + "computedY" : 360.24, + "placementX" : 429.129, + "placementY" : 360.24, + "placementWidth" : 93.05, + "placementHeight" : 10.2, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 93.05, + "contentHeight" : 10.2, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/GoalCells[4]", + "entityName" : "GoalCells", + "entityKind" : "RowNode", + "parentPath" : "Proposal[0]", + "childIndex" : 4, + "depth" : 2, + "layer" : 2, + "computedX" : 36.0, + "computedY" : 286.789, + "placementX" : 36.0, + "placementY" : 286.789, + "placementWidth" : 523.276, + "placementHeight" : 37.101, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 523.276, + "contentHeight" : 37.101, + "margin" : { + "top" : 18.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/GoalCells[4]/Icon-goal-brand[0]", + "entityName" : "Icon-goal-brand", + "entityKind" : "ImageNode", + "parentPath" : "Proposal[0]/GoalCells[4]", + "childIndex" : 0, + "depth" : 3, + "layer" : 3, + "computedX" : 36.0, + "computedY" : 303.89, + "placementX" : 36.0, + "placementY" : 303.89, + "placementWidth" : 20.0, + "placementHeight" : 20.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 20.0, + "contentHeight" : 20.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/GoalCells[4]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/GoalCells[4]", + "childIndex" : 1, + "depth" : 3, + "layer" : 3, + "computedX" : 63.0, + "computedY" : 288.59, + "placementX" : 63.0, + "placementY" : 288.59, + "placementWidth" : 80.352, + "placementHeight" : 35.3, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 80.352, + "contentHeight" : 35.3, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/GoalCells[4]/LineNode[2]", + "entityName" : null, + "entityKind" : "LineNode", + "parentPath" : "Proposal[0]/GoalCells[4]", + "childIndex" : 2, + "depth" : 3, + "layer" : 3, + "computedX" : 162.794, + "computedY" : 286.789, + "placementX" : 162.794, + "placementY" : 286.789, + "placementWidth" : 1.0, + "placementHeight" : 37.101, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 1.0, + "contentHeight" : 37.101, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/GoalCells[4]/Icon-goal-usability[3]", + "entityName" : "Icon-goal-usability", + "entityKind" : "ImageNode", + "parentPath" : "Proposal[0]/GoalCells[4]", + "childIndex" : 3, + "depth" : 3, + "layer" : 3, + "computedX" : 170.494, + "computedY" : 303.89, + "placementX" : 170.494, + "placementY" : 303.89, + "placementWidth" : 20.0, + "placementHeight" : 20.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 20.0, + "contentHeight" : 20.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/GoalCells[4]/ParagraphNode[4]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/GoalCells[4]", + "childIndex" : 4, + "depth" : 3, + "layer" : 3, + "computedX" : 197.494, + "computedY" : 288.59, + "placementX" : 197.494, + "placementY" : 288.59, + "placementWidth" : 72.54, + "placementHeight" : 35.3, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 72.54, + "contentHeight" : 35.3, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/GoalCells[4]/LineNode[5]", + "entityName" : null, + "entityKind" : "LineNode", + "parentPath" : "Proposal[0]/GoalCells[4]", + "childIndex" : 5, + "depth" : 3, + "layer" : 3, + "computedX" : 297.288, + "computedY" : 286.789, + "placementX" : 297.288, + "placementY" : 286.789, + "placementWidth" : 1.0, + "placementHeight" : 37.101, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 1.0, + "contentHeight" : 37.101, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/GoalCells[4]/Icon-goal-message[6]", + "entityName" : "Icon-goal-message", + "entityKind" : "ImageNode", + "parentPath" : "Proposal[0]/GoalCells[4]", + "childIndex" : 6, + "depth" : 3, + "layer" : 3, + "computedX" : 304.988, + "computedY" : 303.89, + "placementX" : 304.988, + "placementY" : 303.89, + "placementWidth" : 20.0, + "placementHeight" : 20.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 20.0, + "contentHeight" : 20.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/GoalCells[4]/ParagraphNode[7]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/GoalCells[4]", + "childIndex" : 7, + "depth" : 3, + "layer" : 3, + "computedX" : 331.988, + "computedY" : 288.59, + "placementX" : 331.988, + "placementY" : 288.59, + "placementWidth" : 87.291, + "placementHeight" : 35.3, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 87.291, + "contentHeight" : 35.3, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/GoalCells[4]/LineNode[8]", + "entityName" : null, + "entityKind" : "LineNode", + "parentPath" : "Proposal[0]/GoalCells[4]", + "childIndex" : 8, + "depth" : 3, + "layer" : 3, + "computedX" : 431.782, + "computedY" : 286.789, + "placementX" : 431.782, + "placementY" : 286.789, + "placementWidth" : 1.0, + "placementHeight" : 37.101, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 1.0, + "contentHeight" : 37.101, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/GoalCells[4]/Icon-goal-growth[9]", + "entityName" : "Icon-goal-growth", + "entityKind" : "ImageNode", + "parentPath" : "Proposal[0]/GoalCells[4]", + "childIndex" : 9, + "depth" : 3, + "layer" : 3, + "computedX" : 439.482, + "computedY" : 303.89, + "placementX" : 439.482, + "placementY" : 303.89, + "placementWidth" : 20.0, + "placementHeight" : 20.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 20.0, + "contentHeight" : 20.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/GoalCells[4]/ParagraphNode[10]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/GoalCells[4]", + "childIndex" : 10, + "depth" : 3, + "layer" : 3, + "computedX" : 466.482, + "computedY" : 288.59, + "placementX" : 466.482, + "placementY" : 288.59, + "placementWidth" : 86.607, + "placementHeight" : 35.3, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 86.607, + "contentHeight" : 35.3, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/ScopeOfWork[5]", + "entityName" : "ScopeOfWork", + "entityKind" : "SectionNode", + "parentPath" : "Proposal[0]", + "childIndex" : 5, + "depth" : 2, + "layer" : 2, + "computedX" : 36.0, + "computedY" : 64.539, + "placementX" : 36.0, + "placementY" : 64.539, + "placementWidth" : 523.276, + "placementHeight" : 204.25, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 523.276, + "contentHeight" : 204.25, + "margin" : { + "top" : 18.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/ScopeOfWork[5]/SectionHeading[0]", + "entityName" : "SectionHeading", + "entityKind" : "ShapeContainerNode", + "parentPath" : "Proposal[0]/ScopeOfWork[5]", + "childIndex" : 0, + "depth" : 3, + "layer" : 3, + "computedX" : 36.0, + "computedY" : 241.789, + "placementX" : 36.0, + "placementY" : 241.789, + "placementWidth" : 523.276, + "placementHeight" : 27.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 523.276, + "contentHeight" : 27.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/ScopeOfWork[5]/SectionHeading[0]/SectionBadge[0]", + "entityName" : "SectionBadge", + "entityKind" : "ShapeContainerNode", + "parentPath" : "Proposal[0]/ScopeOfWork[5]/SectionHeading[0]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 36.0, + "computedY" : 241.789, + "placementX" : 36.0, + "placementY" : 241.789, + "placementWidth" : 27.0, + "placementHeight" : 27.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 27.0, + "contentHeight" : 27.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/ScopeOfWork[5]/SectionHeading[0]/SectionBadge[0]/Icon-badge-scope[0]", + "entityName" : "Icon-badge-scope", + "entityKind" : "ImageNode", + "parentPath" : "Proposal[0]/ScopeOfWork[5]/SectionHeading[0]/SectionBadge[0]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 42.48, + "computedY" : 248.269, + "placementX" : 42.48, + "placementY" : 248.269, + "placementWidth" : 14.04, + "placementHeight" : 14.04, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 14.04, + "contentHeight" : 14.04, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/ScopeOfWork[5]/SectionHeading[0]/SectionHeadingTitle[1]", + "entityName" : "SectionHeadingTitle", + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/ScopeOfWork[5]/SectionHeading[0]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 74.34, + "computedY" : 247.189, + "placementX" : 74.34, + "placementY" : 247.189, + "placementWidth" : 111.523, + "placementHeight" : 16.2, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 111.523, + "contentHeight" : 16.2, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/ScopeOfWork[5]/LineNode[1]", + "entityName" : null, + "entityKind" : "LineNode", + "parentPath" : "Proposal[0]/ScopeOfWork[5]", + "childIndex" : 1, + "depth" : 3, + "layer" : 3, + "computedX" : 36.0, + "computedY" : 233.789, + "placementX" : 36.0, + "placementY" : 233.789, + "placementWidth" : 523.276, + "placementHeight" : 1.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 523.276, + "contentHeight" : 1.0, + "margin" : { + "top" : 7.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/ScopeOfWork[5]/ScopeRow01[2]", + "entityName" : "ScopeRow01", + "entityKind" : "RowNode", + "parentPath" : "Proposal[0]/ScopeOfWork[5]", + "childIndex" : 2, + "depth" : 3, + "layer" : 3, + "computedX" : 36.0, + "computedY" : 200.739, + "placementX" : 36.0, + "placementY" : 200.739, + "placementWidth" : 523.276, + "placementHeight" : 33.05, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 523.276, + "contentHeight" : 33.05, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 5.0, + "right" : 0.0, + "bottom" : 5.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/ScopeOfWork[5]/ScopeRow01[2]/ScopeBadge[0]", + "entityName" : "ScopeBadge", + "entityKind" : "ShapeContainerNode", + "parentPath" : "Proposal[0]/ScopeOfWork[5]/ScopeRow01[2]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 36.0, + "computedY" : 213.789, + "placementX" : 36.0, + "placementY" : 213.789, + "placementWidth" : 26.0, + "placementHeight" : 15.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 26.0, + "contentHeight" : 15.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/ScopeOfWork[5]/ScopeRow01[2]/ScopeBadge[0]/ScopeNumber[0]", + "entityName" : "ScopeNumber", + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/ScopeOfWork[5]/ScopeRow01[2]/ScopeBadge[0]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 36.0, + "computedY" : 216.189, + "placementX" : 36.0, + "placementY" : 216.189, + "placementWidth" : 26.0, + "placementHeight" : 10.2, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 26.0, + "contentHeight" : 10.2, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/ScopeOfWork[5]/ScopeRow01[2]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/ScopeOfWork[5]/ScopeRow01[2]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 74.0, + "computedY" : 217.389, + "placementX" : 74.0, + "placementY" : 217.389, + "placementWidth" : 92.748, + "placementHeight" : 11.4, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 92.748, + "contentHeight" : 11.4, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/ScopeOfWork[5]/ScopeRow01[2]/ParagraphNode[2]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/ScopeOfWork[5]/ScopeRow01[2]", + "childIndex" : 2, + "depth" : 4, + "layer" : 4, + "computedX" : 270.428, + "computedY" : 205.739, + "placementX" : 270.428, + "placementY" : 205.739, + "placementWidth" : 273.564, + "placementHeight" : 23.05, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 273.564, + "contentHeight" : 23.05, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/ScopeOfWork[5]/LineNode[3]", + "entityName" : null, + "entityKind" : "LineNode", + "parentPath" : "Proposal[0]/ScopeOfWork[5]", + "childIndex" : 3, + "depth" : 3, + "layer" : 3, + "computedX" : 36.0, + "computedY" : 199.739, + "placementX" : 36.0, + "placementY" : 199.739, + "placementWidth" : 523.276, + "placementHeight" : 1.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 523.276, + "contentHeight" : 1.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/ScopeOfWork[5]/ScopeRow02[4]", + "entityName" : "ScopeRow02", + "entityKind" : "RowNode", + "parentPath" : "Proposal[0]/ScopeOfWork[5]", + "childIndex" : 4, + "depth" : 3, + "layer" : 3, + "computedX" : 36.0, + "computedY" : 166.689, + "placementX" : 36.0, + "placementY" : 166.689, + "placementWidth" : 523.276, + "placementHeight" : 33.05, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 523.276, + "contentHeight" : 33.05, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 5.0, + "right" : 0.0, + "bottom" : 5.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/ScopeOfWork[5]/ScopeRow02[4]/ScopeBadge[0]", + "entityName" : "ScopeBadge", + "entityKind" : "ShapeContainerNode", + "parentPath" : "Proposal[0]/ScopeOfWork[5]/ScopeRow02[4]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 36.0, + "computedY" : 179.739, + "placementX" : 36.0, + "placementY" : 179.739, + "placementWidth" : 26.0, + "placementHeight" : 15.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 26.0, + "contentHeight" : 15.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/ScopeOfWork[5]/ScopeRow02[4]/ScopeBadge[0]/ScopeNumber[0]", + "entityName" : "ScopeNumber", + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/ScopeOfWork[5]/ScopeRow02[4]/ScopeBadge[0]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 36.0, + "computedY" : 182.139, + "placementX" : 36.0, + "placementY" : 182.139, + "placementWidth" : 26.0, + "placementHeight" : 10.2, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 26.0, + "contentHeight" : 10.2, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/ScopeOfWork[5]/ScopeRow02[4]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/ScopeOfWork[5]/ScopeRow02[4]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 74.0, + "computedY" : 183.339, + "placementX" : 74.0, + "placementY" : 183.339, + "placementWidth" : 112.87, + "placementHeight" : 11.4, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 112.87, + "contentHeight" : 11.4, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/ScopeOfWork[5]/ScopeRow02[4]/ParagraphNode[2]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/ScopeOfWork[5]/ScopeRow02[4]", + "childIndex" : 2, + "depth" : 4, + "layer" : 4, + "computedX" : 270.428, + "computedY" : 171.689, + "placementX" : 270.428, + "placementY" : 171.689, + "placementWidth" : 284.661, + "placementHeight" : 23.05, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 284.661, + "contentHeight" : 23.05, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/ScopeOfWork[5]/LineNode[5]", + "entityName" : null, + "entityKind" : "LineNode", + "parentPath" : "Proposal[0]/ScopeOfWork[5]", + "childIndex" : 5, + "depth" : 3, + "layer" : 3, + "computedX" : 36.0, + "computedY" : 165.689, + "placementX" : 36.0, + "placementY" : 165.689, + "placementWidth" : 523.276, + "placementHeight" : 1.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 523.276, + "contentHeight" : 1.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/ScopeOfWork[5]/ScopeRow03[6]", + "entityName" : "ScopeRow03", + "entityKind" : "RowNode", + "parentPath" : "Proposal[0]/ScopeOfWork[5]", + "childIndex" : 6, + "depth" : 3, + "layer" : 3, + "computedX" : 36.0, + "computedY" : 132.639, + "placementX" : 36.0, + "placementY" : 132.639, + "placementWidth" : 523.276, + "placementHeight" : 33.05, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 523.276, + "contentHeight" : 33.05, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 5.0, + "right" : 0.0, + "bottom" : 5.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/ScopeOfWork[5]/ScopeRow03[6]/ScopeBadge[0]", + "entityName" : "ScopeBadge", + "entityKind" : "ShapeContainerNode", + "parentPath" : "Proposal[0]/ScopeOfWork[5]/ScopeRow03[6]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 36.0, + "computedY" : 145.689, + "placementX" : 36.0, + "placementY" : 145.689, + "placementWidth" : 26.0, + "placementHeight" : 15.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 26.0, + "contentHeight" : 15.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/ScopeOfWork[5]/ScopeRow03[6]/ScopeBadge[0]/ScopeNumber[0]", + "entityName" : "ScopeNumber", + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/ScopeOfWork[5]/ScopeRow03[6]/ScopeBadge[0]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 36.0, + "computedY" : 148.089, + "placementX" : 36.0, + "placementY" : 148.089, + "placementWidth" : 26.0, + "placementHeight" : 10.2, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 26.0, + "contentHeight" : 10.2, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/ScopeOfWork[5]/ScopeRow03[6]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/ScopeOfWork[5]/ScopeRow03[6]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 74.0, + "computedY" : 149.289, + "placementX" : 74.0, + "placementY" : 149.289, + "placementWidth" : 97.28, + "placementHeight" : 11.4, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 97.28, + "contentHeight" : 11.4, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/ScopeOfWork[5]/ScopeRow03[6]/ParagraphNode[2]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/ScopeOfWork[5]/ScopeRow03[6]", + "childIndex" : 2, + "depth" : 4, + "layer" : 4, + "computedX" : 270.428, + "computedY" : 137.639, + "placementX" : 270.428, + "placementY" : 137.639, + "placementWidth" : 250.938, + "placementHeight" : 23.05, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 250.938, + "contentHeight" : 23.05, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/ScopeOfWork[5]/LineNode[7]", + "entityName" : null, + "entityKind" : "LineNode", + "parentPath" : "Proposal[0]/ScopeOfWork[5]", + "childIndex" : 7, + "depth" : 3, + "layer" : 3, + "computedX" : 36.0, + "computedY" : 131.639, + "placementX" : 36.0, + "placementY" : 131.639, + "placementWidth" : 523.276, + "placementHeight" : 1.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 523.276, + "contentHeight" : 1.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/ScopeOfWork[5]/ScopeRow04[8]", + "entityName" : "ScopeRow04", + "entityKind" : "RowNode", + "parentPath" : "Proposal[0]/ScopeOfWork[5]", + "childIndex" : 8, + "depth" : 3, + "layer" : 3, + "computedX" : 36.0, + "computedY" : 98.589, + "placementX" : 36.0, + "placementY" : 98.589, + "placementWidth" : 523.276, + "placementHeight" : 33.05, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 523.276, + "contentHeight" : 33.05, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 5.0, + "right" : 0.0, + "bottom" : 5.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/ScopeOfWork[5]/ScopeRow04[8]/ScopeBadge[0]", + "entityName" : "ScopeBadge", + "entityKind" : "ShapeContainerNode", + "parentPath" : "Proposal[0]/ScopeOfWork[5]/ScopeRow04[8]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 36.0, + "computedY" : 111.639, + "placementX" : 36.0, + "placementY" : 111.639, + "placementWidth" : 26.0, + "placementHeight" : 15.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 26.0, + "contentHeight" : 15.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/ScopeOfWork[5]/ScopeRow04[8]/ScopeBadge[0]/ScopeNumber[0]", + "entityName" : "ScopeNumber", + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/ScopeOfWork[5]/ScopeRow04[8]/ScopeBadge[0]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 36.0, + "computedY" : 114.039, + "placementX" : 36.0, + "placementY" : 114.039, + "placementWidth" : 26.0, + "placementHeight" : 10.2, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 26.0, + "contentHeight" : 10.2, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/ScopeOfWork[5]/ScopeRow04[8]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/ScopeOfWork[5]/ScopeRow04[8]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 74.0, + "computedY" : 115.239, + "placementX" : 74.0, + "placementY" : 115.239, + "placementWidth" : 133.19, + "placementHeight" : 11.4, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 133.19, + "contentHeight" : 11.4, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/ScopeOfWork[5]/ScopeRow04[8]/ParagraphNode[2]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/ScopeOfWork[5]/ScopeRow04[8]", + "childIndex" : 2, + "depth" : 4, + "layer" : 4, + "computedX" : 270.428, + "computedY" : 103.589, + "placementX" : 270.428, + "placementY" : 103.589, + "placementWidth" : 256.446, + "placementHeight" : 23.05, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 256.446, + "contentHeight" : 23.05, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/ScopeOfWork[5]/LineNode[9]", + "entityName" : null, + "entityKind" : "LineNode", + "parentPath" : "Proposal[0]/ScopeOfWork[5]", + "childIndex" : 9, + "depth" : 3, + "layer" : 3, + "computedX" : 36.0, + "computedY" : 97.589, + "placementX" : 36.0, + "placementY" : 97.589, + "placementWidth" : 523.276, + "placementHeight" : 1.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 523.276, + "contentHeight" : 1.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/ScopeOfWork[5]/ScopeRow05[10]", + "entityName" : "ScopeRow05", + "entityKind" : "RowNode", + "parentPath" : "Proposal[0]/ScopeOfWork[5]", + "childIndex" : 10, + "depth" : 3, + "layer" : 3, + "computedX" : 36.0, + "computedY" : 64.539, + "placementX" : 36.0, + "placementY" : 64.539, + "placementWidth" : 523.276, + "placementHeight" : 33.05, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 523.276, + "contentHeight" : 33.05, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 5.0, + "right" : 0.0, + "bottom" : 5.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/ScopeOfWork[5]/ScopeRow05[10]/ScopeBadge[0]", + "entityName" : "ScopeBadge", + "entityKind" : "ShapeContainerNode", + "parentPath" : "Proposal[0]/ScopeOfWork[5]/ScopeRow05[10]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 36.0, + "computedY" : 77.589, + "placementX" : 36.0, + "placementY" : 77.589, + "placementWidth" : 26.0, + "placementHeight" : 15.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 26.0, + "contentHeight" : 15.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/ScopeOfWork[5]/ScopeRow05[10]/ScopeBadge[0]/ScopeNumber[0]", + "entityName" : "ScopeNumber", + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/ScopeOfWork[5]/ScopeRow05[10]/ScopeBadge[0]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 36.0, + "computedY" : 79.989, + "placementX" : 36.0, + "placementY" : 79.989, + "placementWidth" : 26.0, + "placementHeight" : 10.2, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 26.0, + "contentHeight" : 10.2, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/ScopeOfWork[5]/ScopeRow05[10]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/ScopeOfWork[5]/ScopeRow05[10]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 74.0, + "computedY" : 81.189, + "placementX" : 74.0, + "placementY" : 81.189, + "placementWidth" : 66.633, + "placementHeight" : 11.4, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 66.633, + "contentHeight" : 11.4, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/ScopeOfWork[5]/ScopeRow05[10]/ParagraphNode[2]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/ScopeOfWork[5]/ScopeRow05[10]", + "childIndex" : 2, + "depth" : 4, + "layer" : 4, + "computedX" : 270.428, + "computedY" : 69.539, + "placementX" : 270.428, + "placementY" : 69.539, + "placementWidth" : 260.577, + "placementHeight" : 23.05, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 260.577, + "contentHeight" : 23.05, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/Header[6]", + "entityName" : "Header", + "entityKind" : "SectionNode", + "parentPath" : "Proposal[0]", + "childIndex" : 6, + "depth" : 2, + "layer" : 2, + "computedX" : 36.0, + "computedY" : 775.89, + "placementX" : 36.0, + "placementY" : 775.89, + "placementWidth" : 523.276, + "placementHeight" : 58.0, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 523.276, + "contentHeight" : 58.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/Header[6]/HeaderRow[0]", + "entityName" : "HeaderRow", + "entityKind" : "RowNode", + "parentPath" : "Proposal[0]/Header[6]", + "childIndex" : 0, + "depth" : 3, + "layer" : 3, + "computedX" : 36.0, + "computedY" : 783.89, + "placementX" : 36.0, + "placementY" : 783.89, + "placementWidth" : 523.276, + "placementHeight" : 50.0, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 523.276, + "contentHeight" : 50.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/Header[6]/HeaderRow[0]/LogoMark[0]", + "entityName" : "LogoMark", + "entityKind" : "ShapeContainerNode", + "parentPath" : "Proposal[0]/Header[6]/HeaderRow[0]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 36.0, + "computedY" : 783.89, + "placementX" : 36.0, + "placementY" : 783.89, + "placementWidth" : 50.0, + "placementHeight" : 50.0, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 50.0, + "contentHeight" : 50.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/Header[6]/HeaderRow[0]/LogoMark[0]/Monogram[0]", + "entityName" : "Monogram", + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/Header[6]/HeaderRow[0]/LogoMark[0]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 36.0, + "computedY" : 790.89, + "placementX" : 36.0, + "placementY" : 790.89, + "placementWidth" : 50.0, + "placementHeight" : 36.0, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 50.0, + "contentHeight" : 36.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/Header[6]/HeaderRow[0]/Wordmark[1]", + "entityName" : "Wordmark", + "entityKind" : "SectionNode", + "parentPath" : "Proposal[0]/Header[6]/HeaderRow[0]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 98.5, + "computedY" : 789.69, + "placementX" : 98.5, + "placementY" : 789.69, + "placementWidth" : 92.096, + "placementHeight" : 38.4, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 92.096, + "contentHeight" : 38.4, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/Header[6]/HeaderRow[0]/Wordmark[1]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/Header[6]/HeaderRow[0]/Wordmark[1]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 98.5, + "computedY" : 808.89, + "placementX" : 98.5, + "placementY" : 808.89, + "placementWidth" : 92.096, + "placementHeight" : 19.2, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 92.096, + "contentHeight" : 19.2, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/Header[6]/HeaderRow[0]/Wordmark[1]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/Header[6]/HeaderRow[0]/Wordmark[1]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 98.5, + "computedY" : 789.69, + "placementX" : 98.5, + "placementY" : 789.69, + "placementWidth" : 60.128, + "placementHeight" : 19.2, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 60.128, + "contentHeight" : 19.2, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/Header[6]/HeaderRow[0]/SpacerNode[2]", + "entityName" : null, + "entityKind" : "SpacerNode", + "parentPath" : "Proposal[0]/Header[6]/HeaderRow[0]", + "childIndex" : 2, + "depth" : 4, + "layer" : 4, + "computedX" : 210.422, + "computedY" : 808.89, + "placementX" : 210.422, + "placementY" : 808.89, + "placementWidth" : 0.0, + "placementHeight" : 0.0, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 0.0, + "contentHeight" : 0.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/Header[6]/HeaderRow[0]/DocumentLabel[3]", + "entityName" : "DocumentLabel", + "entityKind" : "SectionNode", + "parentPath" : "Proposal[0]/Header[6]/HeaderRow[0]", + "childIndex" : 3, + "depth" : 4, + "layer" : 4, + "computedX" : 475.552, + "computedY" : 799.39, + "placementX" : 475.552, + "placementY" : 799.39, + "placementWidth" : 83.724, + "placementHeight" : 19.0, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 83.724, + "contentHeight" : 19.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/Header[6]/HeaderRow[0]/DocumentLabel[3]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/Header[6]/HeaderRow[0]/DocumentLabel[3]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 475.552, + "computedY" : 807.59, + "placementX" : 475.552, + "placementY" : 807.59, + "placementWidth" : 83.724, + "placementHeight" : 10.8, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 83.724, + "contentHeight" : 10.8, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/Header[6]/HeaderRow[0]/DocumentLabel[3]/Align[1]", + "entityName" : null, + "entityKind" : "Align", + "parentPath" : "Proposal[0]/Header[6]/HeaderRow[0]/DocumentLabel[3]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 475.552, + "computedY" : 799.39, + "placementX" : 475.552, + "placementY" : 799.39, + "placementWidth" : 83.724, + "placementHeight" : 8.2, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 83.724, + "contentHeight" : 8.2, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/Header[6]/HeaderRow[0]/DocumentLabel[3]/Align[1]/DocumentLabelRule[0]", + "entityName" : "DocumentLabelRule", + "entityKind" : "LineNode", + "parentPath" : "Proposal[0]/Header[6]/HeaderRow[0]/DocumentLabel[3]/Align[1]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 524.176, + "computedY" : 799.39, + "placementX" : 524.176, + "placementY" : 799.39, + "placementWidth" : 35.1, + "placementHeight" : 2.2, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 35.1, + "contentHeight" : 2.2, + "margin" : { + "top" : 6.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/Header[6]/LineNode[1]", + "entityName" : null, + "entityKind" : "LineNode", + "parentPath" : "Proposal[0]/Header[6]", + "childIndex" : 1, + "depth" : 3, + "layer" : 3, + "computedX" : 36.0, + "computedY" : 775.89, + "placementX" : 36.0, + "placementY" : 775.89, + "placementWidth" : 523.276, + "placementHeight" : 1.0, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 523.276, + "contentHeight" : 1.0, + "margin" : { + "top" : 7.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/Deliverables[7]", + "entityName" : "Deliverables", + "entityKind" : "SectionNode", + "parentPath" : "Proposal[0]", + "childIndex" : 7, + "depth" : 2, + "layer" : 2, + "computedX" : 36.0, + "computedY" : 638.89, + "placementX" : 36.0, + "placementY" : 638.89, + "placementWidth" : 523.276, + "placementHeight" : 119.0, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 523.276, + "contentHeight" : 119.0, + "margin" : { + "top" : 18.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/Deliverables[7]/SectionHeading[0]", + "entityName" : "SectionHeading", + "entityKind" : "ShapeContainerNode", + "parentPath" : "Proposal[0]/Deliverables[7]", + "childIndex" : 0, + "depth" : 3, + "layer" : 3, + "computedX" : 36.0, + "computedY" : 730.89, + "placementX" : 36.0, + "placementY" : 730.89, + "placementWidth" : 523.276, + "placementHeight" : 27.0, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 523.276, + "contentHeight" : 27.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/Deliverables[7]/SectionHeading[0]/SectionBadge[0]", + "entityName" : "SectionBadge", + "entityKind" : "ShapeContainerNode", + "parentPath" : "Proposal[0]/Deliverables[7]/SectionHeading[0]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 36.0, + "computedY" : 730.89, + "placementX" : 36.0, + "placementY" : 730.89, + "placementWidth" : 27.0, + "placementHeight" : 27.0, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 27.0, + "contentHeight" : 27.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/Deliverables[7]/SectionHeading[0]/SectionBadge[0]/Icon-badge-deliverables[0]", + "entityName" : "Icon-badge-deliverables", + "entityKind" : "ImageNode", + "parentPath" : "Proposal[0]/Deliverables[7]/SectionHeading[0]/SectionBadge[0]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 42.48, + "computedY" : 737.37, + "placementX" : 42.48, + "placementY" : 737.37, + "placementWidth" : 14.04, + "placementHeight" : 14.04, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 14.04, + "contentHeight" : 14.04, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/Deliverables[7]/SectionHeading[0]/SectionHeadingTitle[1]", + "entityName" : "SectionHeadingTitle", + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/Deliverables[7]/SectionHeading[0]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 74.34, + "computedY" : 736.29, + "placementX" : 74.34, + "placementY" : 736.29, + "placementWidth" : 95.364, + "placementHeight" : 16.2, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 95.364, + "contentHeight" : 16.2, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/Deliverables[7]/DeliverableColumns[1]", + "entityName" : "DeliverableColumns", + "entityKind" : "RowNode", + "parentPath" : "Proposal[0]/Deliverables[7]", + "childIndex" : 1, + "depth" : 3, + "layer" : 3, + "computedX" : 36.0, + "computedY" : 638.89, + "placementX" : 36.0, + "placementY" : 638.89, + "placementWidth" : 523.276, + "placementHeight" : 92.0, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 523.276, + "contentHeight" : 92.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 7.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/Deliverables[7]/DeliverableColumns[1]/DeliverablesLeft[0]", + "entityName" : "DeliverablesLeft", + "entityKind" : "SectionNode", + "parentPath" : "Proposal[0]/Deliverables[7]/DeliverableColumns[1]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 36.0, + "computedY" : 638.89, + "placementX" : 36.0, + "placementY" : 638.89, + "placementWidth" : 175.2, + "placementHeight" : 85.0, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 175.2, + "contentHeight" : 85.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/Deliverables[7]/DeliverableColumns[1]/DeliverablesLeft[0]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/Deliverables[7]/DeliverableColumns[1]/DeliverablesLeft[0]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 36.0, + "computedY" : 711.89, + "placementX" : 36.0, + "placementY" : 711.89, + "placementWidth" : 119.16, + "placementHeight" : 12.0, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 119.16, + "contentHeight" : 12.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 5.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/Deliverables[7]/DeliverableColumns[1]/DeliverablesLeft[0]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/Deliverables[7]/DeliverableColumns[1]/DeliverablesLeft[0]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 36.0, + "computedY" : 694.89, + "placementX" : 36.0, + "placementY" : 694.89, + "placementWidth" : 175.2, + "placementHeight" : 12.0, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 175.2, + "contentHeight" : 12.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 5.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/Deliverables[7]/DeliverableColumns[1]/DeliverablesLeft[0]/ParagraphNode[2]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/Deliverables[7]/DeliverableColumns[1]/DeliverablesLeft[0]", + "childIndex" : 2, + "depth" : 5, + "layer" : 5, + "computedX" : 36.0, + "computedY" : 677.89, + "placementX" : 36.0, + "placementY" : 677.89, + "placementWidth" : 165.03, + "placementHeight" : 12.0, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 165.03, + "contentHeight" : 12.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 5.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/Deliverables[7]/DeliverableColumns[1]/DeliverablesLeft[0]/ParagraphNode[3]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/Deliverables[7]/DeliverableColumns[1]/DeliverablesLeft[0]", + "childIndex" : 3, + "depth" : 5, + "layer" : 5, + "computedX" : 36.0, + "computedY" : 660.89, + "placementX" : 36.0, + "placementY" : 660.89, + "placementWidth" : 116.83, + "placementHeight" : 12.0, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 116.83, + "contentHeight" : 12.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 5.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/Deliverables[7]/DeliverableColumns[1]/DeliverablesLeft[0]/ParagraphNode[4]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/Deliverables[7]/DeliverableColumns[1]/DeliverablesLeft[0]", + "childIndex" : 4, + "depth" : 5, + "layer" : 5, + "computedX" : 36.0, + "computedY" : 643.89, + "placementX" : 36.0, + "placementY" : 643.89, + "placementWidth" : 138.9, + "placementHeight" : 12.0, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 138.9, + "contentHeight" : 12.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 5.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/Deliverables[7]/DeliverableColumns[1]/DeliverablesRight[1]", + "entityName" : "DeliverablesRight", + "entityKind" : "SectionNode", + "parentPath" : "Proposal[0]/Deliverables[7]/DeliverableColumns[1]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 287.172, + "computedY" : 655.89, + "placementX" : 287.172, + "placementY" : 655.89, + "placementWidth" : 217.84, + "placementHeight" : 68.0, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 217.84, + "contentHeight" : 68.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/Deliverables[7]/DeliverableColumns[1]/DeliverablesRight[1]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/Deliverables[7]/DeliverableColumns[1]/DeliverablesRight[1]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 287.172, + "computedY" : 711.89, + "placementX" : 287.172, + "placementY" : 711.89, + "placementWidth" : 217.84, + "placementHeight" : 12.0, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 217.84, + "contentHeight" : 12.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 5.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/Deliverables[7]/DeliverableColumns[1]/DeliverablesRight[1]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/Deliverables[7]/DeliverableColumns[1]/DeliverablesRight[1]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 287.172, + "computedY" : 694.89, + "placementX" : 287.172, + "placementY" : 694.89, + "placementWidth" : 165.27, + "placementHeight" : 12.0, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 165.27, + "contentHeight" : 12.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 5.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/Deliverables[7]/DeliverableColumns[1]/DeliverablesRight[1]/ParagraphNode[2]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/Deliverables[7]/DeliverableColumns[1]/DeliverablesRight[1]", + "childIndex" : 2, + "depth" : 5, + "layer" : 5, + "computedX" : 287.172, + "computedY" : 677.89, + "placementX" : 287.172, + "placementY" : 677.89, + "placementWidth" : 187.4, + "placementHeight" : 12.0, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 187.4, + "contentHeight" : 12.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 5.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/Deliverables[7]/DeliverableColumns[1]/DeliverablesRight[1]/ParagraphNode[3]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/Deliverables[7]/DeliverableColumns[1]/DeliverablesRight[1]", + "childIndex" : 3, + "depth" : 5, + "layer" : 5, + "computedX" : 287.172, + "computedY" : 660.89, + "placementX" : 287.172, + "placementY" : 660.89, + "placementWidth" : 164.33, + "placementHeight" : 12.0, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 164.33, + "contentHeight" : 12.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 5.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/Timeline[8]", + "entityName" : "Timeline", + "entityKind" : "SectionNode", + "parentPath" : "Proposal[0]", + "childIndex" : 8, + "depth" : 2, + "layer" : 2, + "computedX" : 36.0, + "computedY" : 410.74, + "placementX" : 36.0, + "placementY" : 410.74, + "placementWidth" : 523.276, + "placementHeight" : 210.15, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 523.276, + "contentHeight" : 210.15, + "margin" : { + "top" : 18.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/Timeline[8]/SectionHeading[0]", + "entityName" : "SectionHeading", + "entityKind" : "ShapeContainerNode", + "parentPath" : "Proposal[0]/Timeline[8]", + "childIndex" : 0, + "depth" : 3, + "layer" : 3, + "computedX" : 36.0, + "computedY" : 593.89, + "placementX" : 36.0, + "placementY" : 593.89, + "placementWidth" : 523.276, + "placementHeight" : 27.0, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 523.276, + "contentHeight" : 27.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/Timeline[8]/SectionHeading[0]/SectionBadge[0]", + "entityName" : "SectionBadge", + "entityKind" : "ShapeContainerNode", + "parentPath" : "Proposal[0]/Timeline[8]/SectionHeading[0]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 36.0, + "computedY" : 593.89, + "placementX" : 36.0, + "placementY" : 593.89, + "placementWidth" : 27.0, + "placementHeight" : 27.0, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 27.0, + "contentHeight" : 27.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/Timeline[8]/SectionHeading[0]/SectionBadge[0]/Icon-badge-timeline[0]", + "entityName" : "Icon-badge-timeline", + "entityKind" : "ImageNode", + "parentPath" : "Proposal[0]/Timeline[8]/SectionHeading[0]/SectionBadge[0]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 42.48, + "computedY" : 600.37, + "placementX" : 42.48, + "placementY" : 600.37, + "placementWidth" : 14.04, + "placementHeight" : 14.04, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 14.04, + "contentHeight" : 14.04, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/Timeline[8]/SectionHeading[0]/SectionHeadingTitle[1]", + "entityName" : "SectionHeadingTitle", + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/Timeline[8]/SectionHeading[0]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 74.34, + "computedY" : 599.29, + "placementX" : 74.34, + "placementY" : 599.29, + "placementWidth" : 61.83, + "placementHeight" : 16.2, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 61.83, + "contentHeight" : 16.2, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/Timeline[8]/TimelineTable[1]", + "entityName" : "TimelineTable", + "entityKind" : "TableNode", + "parentPath" : "Proposal[0]/Timeline[8]", + "childIndex" : 1, + "depth" : 3, + "layer" : 3, + "computedX" : 36.0, + "computedY" : 410.74, + "placementX" : 36.0, + "placementY" : 410.74, + "placementWidth" : 523.276, + "placementHeight" : 176.15, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 523.276, + "contentHeight" : 176.15, + "margin" : { + "top" : 7.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/MoneyBand[9]", + "entityName" : "MoneyBand", + "entityKind" : "RowNode", + "parentPath" : "Proposal[0]", + "childIndex" : 9, + "depth" : 2, + "layer" : 2, + "computedX" : 36.0, + "computedY" : 188.34, + "placementX" : 36.0, + "placementY" : 188.34, + "placementWidth" : 523.276, + "placementHeight" : 204.4, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 523.276, + "contentHeight" : 204.4, + "margin" : { + "top" : 18.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/MoneyBand[9]/InvestmentColumn[0]", + "entityName" : "InvestmentColumn", + "entityKind" : "SectionNode", + "parentPath" : "Proposal[0]/MoneyBand[9]", + "childIndex" : 0, + "depth" : 3, + "layer" : 3, + "computedX" : 36.0, + "computedY" : 188.34, + "placementX" : 36.0, + "placementY" : 188.34, + "placementWidth" : 308.733, + "placementHeight" : 204.4, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 308.733, + "contentHeight" : 204.4, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/MoneyBand[9]/InvestmentColumn[0]/SectionHeading[0]", + "entityName" : "SectionHeading", + "entityKind" : "ShapeContainerNode", + "parentPath" : "Proposal[0]/MoneyBand[9]/InvestmentColumn[0]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 36.0, + "computedY" : 365.74, + "placementX" : 36.0, + "placementY" : 365.74, + "placementWidth" : 308.733, + "placementHeight" : 27.0, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 308.733, + "contentHeight" : 27.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/MoneyBand[9]/InvestmentColumn[0]/SectionHeading[0]/SectionBadge[0]", + "entityName" : "SectionBadge", + "entityKind" : "ShapeContainerNode", + "parentPath" : "Proposal[0]/MoneyBand[9]/InvestmentColumn[0]/SectionHeading[0]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 36.0, + "computedY" : 365.74, + "placementX" : 36.0, + "placementY" : 365.74, + "placementWidth" : 27.0, + "placementHeight" : 27.0, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 27.0, + "contentHeight" : 27.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/MoneyBand[9]/InvestmentColumn[0]/SectionHeading[0]/SectionBadge[0]/Icon-badge-investment[0]", + "entityName" : "Icon-badge-investment", + "entityKind" : "ImageNode", + "parentPath" : "Proposal[0]/MoneyBand[9]/InvestmentColumn[0]/SectionHeading[0]/SectionBadge[0]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 42.48, + "computedY" : 372.22, + "placementX" : 42.48, + "placementY" : 372.22, + "placementWidth" : 14.04, + "placementHeight" : 14.04, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 14.04, + "contentHeight" : 14.04, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/MoneyBand[9]/InvestmentColumn[0]/SectionHeading[0]/SectionHeadingTitle[1]", + "entityName" : "SectionHeadingTitle", + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/MoneyBand[9]/InvestmentColumn[0]/SectionHeading[0]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 74.34, + "computedY" : 371.14, + "placementX" : 74.34, + "placementY" : 371.14, + "placementWidth" : 86.103, + "placementHeight" : 16.2, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 86.103, + "contentHeight" : 16.2, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/MoneyBand[9]/InvestmentColumn[0]/InvestmentTable[1]", + "entityName" : "InvestmentTable", + "entityKind" : "TableNode", + "parentPath" : "Proposal[0]/MoneyBand[9]/InvestmentColumn[0]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 36.0, + "computedY" : 188.34, + "placementX" : 36.0, + "placementY" : 188.34, + "placementWidth" : 308.733, + "placementHeight" : 170.4, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 308.733, + "contentHeight" : 170.4, + "margin" : { + "top" : 7.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/MoneyBand[9]/TermsColumn[1]", + "entityName" : "TermsColumn", + "entityKind" : "SectionNode", + "parentPath" : "Proposal[0]/MoneyBand[9]", + "childIndex" : 1, + "depth" : 3, + "layer" : 3, + "computedX" : 376.129, + "computedY" : 218.74, + "placementX" : 376.129, + "placementY" : 218.74, + "placementWidth" : 183.147, + "placementHeight" : 174.0, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 183.147, + "contentHeight" : 174.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/MoneyBand[9]/TermsColumn[1]/SectionHeading[0]", + "entityName" : "SectionHeading", + "entityKind" : "ShapeContainerNode", + "parentPath" : "Proposal[0]/MoneyBand[9]/TermsColumn[1]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 376.129, + "computedY" : 365.74, + "placementX" : 376.129, + "placementY" : 365.74, + "placementWidth" : 183.147, + "placementHeight" : 27.0, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 183.147, + "contentHeight" : 27.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/MoneyBand[9]/TermsColumn[1]/SectionHeading[0]/SectionBadge[0]", + "entityName" : "SectionBadge", + "entityKind" : "ShapeContainerNode", + "parentPath" : "Proposal[0]/MoneyBand[9]/TermsColumn[1]/SectionHeading[0]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 376.129, + "computedY" : 365.74, + "placementX" : 376.129, + "placementY" : 365.74, + "placementWidth" : 27.0, + "placementHeight" : 27.0, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 27.0, + "contentHeight" : 27.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/MoneyBand[9]/TermsColumn[1]/SectionHeading[0]/SectionBadge[0]/Icon-badge-terms[0]", + "entityName" : "Icon-badge-terms", + "entityKind" : "ImageNode", + "parentPath" : "Proposal[0]/MoneyBand[9]/TermsColumn[1]/SectionHeading[0]/SectionBadge[0]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 382.609, + "computedY" : 372.22, + "placementX" : 382.609, + "placementY" : 372.22, + "placementWidth" : 14.04, + "placementHeight" : 14.04, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 14.04, + "contentHeight" : 14.04, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/MoneyBand[9]/TermsColumn[1]/SectionHeading[0]/SectionHeadingTitle[1]", + "entityName" : "SectionHeadingTitle", + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/MoneyBand[9]/TermsColumn[1]/SectionHeading[0]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 414.469, + "computedY" : 371.14, + "placementX" : 414.469, + "placementY" : 371.14, + "placementWidth" : 44.712, + "placementHeight" : 16.2, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 44.712, + "contentHeight" : 16.2, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/MoneyBand[9]/TermsColumn[1]/TermsList[1]", + "entityName" : "TermsList", + "entityKind" : "SectionNode", + "parentPath" : "Proposal[0]/MoneyBand[9]/TermsColumn[1]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 376.129, + "computedY" : 218.74, + "placementX" : 376.129, + "placementY" : 218.74, + "placementWidth" : 182.676, + "placementHeight" : 147.0, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 182.676, + "contentHeight" : 147.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 7.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/MoneyBand[9]/TermsColumn[1]/TermsList[1]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/MoneyBand[9]/TermsColumn[1]/TermsList[1]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 376.129, + "computedY" : 332.34, + "placementX" : 376.129, + "placementY" : 332.34, + "placementWidth" : 175.69, + "placementHeight" : 26.4, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 175.69, + "contentHeight" : 26.4, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 5.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/MoneyBand[9]/TermsColumn[1]/TermsList[1]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/MoneyBand[9]/TermsColumn[1]/TermsList[1]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 376.129, + "computedY" : 300.94, + "placementX" : 376.129, + "placementY" : 300.94, + "placementWidth" : 171.01, + "placementHeight" : 26.4, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 171.01, + "contentHeight" : 26.4, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 5.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/MoneyBand[9]/TermsColumn[1]/TermsList[1]/ParagraphNode[2]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/MoneyBand[9]/TermsColumn[1]/TermsList[1]", + "childIndex" : 2, + "depth" : 5, + "layer" : 5, + "computedX" : 376.129, + "computedY" : 269.54, + "placementX" : 376.129, + "placementY" : 269.54, + "placementWidth" : 177.14, + "placementHeight" : 26.4, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 177.14, + "contentHeight" : 26.4, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 5.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/MoneyBand[9]/TermsColumn[1]/TermsList[1]/ParagraphNode[3]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/MoneyBand[9]/TermsColumn[1]/TermsList[1]", + "childIndex" : 3, + "depth" : 5, + "layer" : 5, + "computedX" : 376.129, + "computedY" : 223.74, + "placementX" : 376.129, + "placementY" : 223.74, + "placementWidth" : 182.676, + "placementHeight" : 40.8, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 182.676, + "contentHeight" : 40.8, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 5.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/Acceptance[10]", + "entityName" : "Acceptance", + "entityKind" : "SectionNode", + "parentPath" : "Proposal[0]", + "childIndex" : 10, + "depth" : 2, + "layer" : 2, + "computedX" : 36.0, + "computedY" : 78.54, + "placementX" : 36.0, + "placementY" : 78.54, + "placementWidth" : 523.276, + "placementHeight" : 91.8, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 523.276, + "contentHeight" : 91.8, + "margin" : { + "top" : 18.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 11.0, + "right" : 11.0, + "bottom" : 11.0, + "left" : 11.0 + } + }, { + "path" : "Proposal[0]/Acceptance[10]/SectionHeading[0]", + "entityName" : "SectionHeading", + "entityKind" : "ShapeContainerNode", + "parentPath" : "Proposal[0]/Acceptance[10]", + "childIndex" : 0, + "depth" : 3, + "layer" : 3, + "computedX" : 47.0, + "computedY" : 132.34, + "placementX" : 47.0, + "placementY" : 132.34, + "placementWidth" : 501.276, + "placementHeight" : 27.0, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 501.276, + "contentHeight" : 27.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/Acceptance[10]/SectionHeading[0]/SectionBadge[0]", + "entityName" : "SectionBadge", + "entityKind" : "ShapeContainerNode", + "parentPath" : "Proposal[0]/Acceptance[10]/SectionHeading[0]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 47.0, + "computedY" : 132.34, + "placementX" : 47.0, + "placementY" : 132.34, + "placementWidth" : 27.0, + "placementHeight" : 27.0, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 27.0, + "contentHeight" : 27.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/Acceptance[10]/SectionHeading[0]/SectionBadge[0]/Icon-badge-acceptance[0]", + "entityName" : "Icon-badge-acceptance", + "entityKind" : "ImageNode", + "parentPath" : "Proposal[0]/Acceptance[10]/SectionHeading[0]/SectionBadge[0]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 53.48, + "computedY" : 138.82, + "placementX" : 53.48, + "placementY" : 138.82, + "placementWidth" : 14.04, + "placementHeight" : 14.04, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 14.04, + "contentHeight" : 14.04, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/Acceptance[10]/SectionHeading[0]/SectionHeadingTitle[1]", + "entityName" : "SectionHeadingTitle", + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/Acceptance[10]/SectionHeading[0]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 85.34, + "computedY" : 137.74, + "placementX" : 85.34, + "placementY" : 137.74, + "placementWidth" : 87.966, + "placementHeight" : 16.2, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 87.966, + "contentHeight" : 16.2, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/Acceptance[10]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/Acceptance[10]", + "childIndex" : 1, + "depth" : 3, + "layer" : 3, + "computedX" : 47.0, + "computedY" : 113.34, + "placementX" : 47.0, + "placementY" : 113.34, + "placementWidth" : 432.25, + "placementHeight" : 12.0, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 432.25, + "contentHeight" : 12.0, + "margin" : { + "top" : 7.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/Acceptance[10]/SignatureFields[2]", + "entityName" : "SignatureFields", + "entityKind" : "RowNode", + "parentPath" : "Proposal[0]/Acceptance[10]", + "childIndex" : 2, + "depth" : 3, + "layer" : 3, + "computedX" : 47.0, + "computedY" : 89.54, + "placementX" : 47.0, + "placementY" : 89.54, + "placementWidth" : 501.276, + "placementHeight" : 23.8, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 501.276, + "contentHeight" : 23.8, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 13.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/Acceptance[10]/SignatureFields[2]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/Acceptance[10]/SignatureFields[2]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 47.0, + "computedY" : 89.54, + "placementX" : 47.0, + "placementY" : 89.54, + "placementWidth" : 51.759, + "placementHeight" : 10.8, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 51.759, + "contentHeight" : 10.8, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/Acceptance[10]/SignatureFields[2]/LineNode[1]", + "entityName" : null, + "entityKind" : "LineNode", + "parentPath" : "Proposal[0]/Acceptance[10]/SignatureFields[2]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 113.177, + "computedY" : 89.54, + "placementX" : 113.177, + "placementY" : 89.54, + "placementWidth" : 96.915, + "placementHeight" : 1.0, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 96.915, + "contentHeight" : 1.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/Acceptance[10]/SignatureFields[2]/ParagraphNode[2]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/Acceptance[10]/SignatureFields[2]", + "childIndex" : 2, + "depth" : 4, + "layer" : 4, + "computedX" : 216.092, + "computedY" : 89.54, + "placementX" : 216.092, + "placementY" : 89.54, + "placementWidth" : 39.753, + "placementHeight" : 10.8, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 39.753, + "contentHeight" : 10.8, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/Acceptance[10]/SignatureFields[2]/LineNode[3]", + "entityName" : null, + "entityKind" : "LineNode", + "parentPath" : "Proposal[0]/Acceptance[10]/SignatureFields[2]", + "childIndex" : 3, + "depth" : 4, + "layer" : 4, + "computedX" : 282.269, + "computedY" : 89.54, + "placementX" : 282.269, + "placementY" : 89.54, + "placementWidth" : 96.915, + "placementHeight" : 1.0, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 96.915, + "contentHeight" : 1.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/Acceptance[10]/SignatureFields[2]/ParagraphNode[4]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "Proposal[0]/Acceptance[10]/SignatureFields[2]", + "childIndex" : 4, + "depth" : 4, + "layer" : 4, + "computedX" : 385.184, + "computedY" : 89.54, + "placementX" : 385.184, + "placementY" : 89.54, + "placementWidth" : 21.555, + "placementHeight" : 10.8, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 21.555, + "contentHeight" : 10.8, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "Proposal[0]/Acceptance[10]/SignatureFields[2]/LineNode[5]", + "entityName" : null, + "entityKind" : "LineNode", + "parentPath" : "Proposal[0]/Acceptance[10]/SignatureFields[2]", + "childIndex" : 5, + "depth" : 4, + "layer" : 4, + "computedX" : 451.361, + "computedY" : 89.54, + "placementX" : 451.361, + "placementY" : 89.54, + "placementWidth" : 96.915, + "placementHeight" : 1.0, + "startPage" : 1, + "endPage" : 1, + "contentWidth" : 96.915, + "contentHeight" : 1.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + } ] +} diff --git a/qa/src/test/resources/layout-snapshots/canonical-templates/receipt/modern_receipt_layout.json b/qa/src/test/resources/layout-snapshots/canonical-templates/receipt/modern_receipt_layout.json new file mode 100644 index 000000000..b6515f956 --- /dev/null +++ b/qa/src/test/resources/layout-snapshots/canonical-templates/receipt/modern_receipt_layout.json @@ -0,0 +1,3917 @@ +{ + "formatVersion" : "2.0", + "canvas" : { + "pageWidth" : 595.276, + "pageHeight" : 841.89, + "innerWidth" : 527.276, + "innerHeight" : 773.89, + "margin" : { + "top" : 34.0, + "right" : 34.0, + "bottom" : 34.0, + "left" : 34.0 + } + }, + "totalPages" : 1, + "nodes" : [ { + "path" : "ReceiptModernRoot[0]", + "entityName" : "ReceiptModernRoot", + "entityKind" : "ContainerNode", + "parentPath" : null, + "childIndex" : 0, + "depth" : 1, + "layer" : 1, + "computedX" : 34.0, + "computedY" : 34.5, + "placementX" : 34.0, + "placementY" : 34.5, + "placementWidth" : 527.276, + "placementHeight" : 773.39, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 527.276, + "contentHeight" : 773.39, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ReceiptModernRoot[0]/ReceiptMasthead[0]", + "entityName" : "ReceiptMasthead", + "entityKind" : "SectionNode", + "parentPath" : "ReceiptModernRoot[0]", + "childIndex" : 0, + "depth" : 2, + "layer" : 2, + "computedX" : 34.0, + "computedY" : 763.66, + "placementX" : 34.0, + "placementY" : 763.66, + "placementWidth" : 527.276, + "placementHeight" : 44.23, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 527.276, + "contentHeight" : 44.23, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ReceiptModernRoot[0]/ReceiptMasthead[0]/ReceiptMastheadRow[0]", + "entityName" : "ReceiptMastheadRow", + "entityKind" : "RowNode", + "parentPath" : "ReceiptModernRoot[0]/ReceiptMasthead[0]", + "childIndex" : 0, + "depth" : 3, + "layer" : 3, + "computedX" : 34.0, + "computedY" : 774.66, + "placementX" : 34.0, + "placementY" : 774.66, + "placementWidth" : 527.276, + "placementHeight" : 33.23, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 527.276, + "contentHeight" : 33.23, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ReceiptModernRoot[0]/ReceiptMasthead[0]/ReceiptMastheadRow[0]/ReceiptIssuer[0]", + "entityName" : "ReceiptIssuer", + "entityKind" : "SectionNode", + "parentPath" : "ReceiptModernRoot[0]/ReceiptMasthead[0]/ReceiptMastheadRow[0]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 34.0, + "computedY" : 784.8, + "placementX" : 34.0, + "placementY" : 784.8, + "placementWidth" : 152.53, + "placementHeight" : 12.95, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 152.53, + "contentHeight" : 12.95, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ReceiptModernRoot[0]/ReceiptMasthead[0]/ReceiptMastheadRow[0]/ReceiptIssuer[0]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "ReceiptModernRoot[0]/ReceiptMasthead[0]/ReceiptMastheadRow[0]/ReceiptIssuer[0]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 34.0, + "computedY" : 784.8, + "placementX" : 34.0, + "placementY" : 784.8, + "placementWidth" : 152.53, + "placementHeight" : 12.95, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 152.53, + "contentHeight" : 12.95, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ReceiptModernRoot[0]/ReceiptMasthead[0]/ReceiptMastheadRow[0]/ReceiptDocumentTitle[1]", + "entityName" : "ReceiptDocumentTitle", + "entityKind" : "SectionNode", + "parentPath" : "ReceiptModernRoot[0]/ReceiptMasthead[0]/ReceiptMastheadRow[0]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 306.638, + "computedY" : 774.66, + "placementX" : 306.638, + "placementY" : 774.66, + "placementWidth" : 254.638, + "placementHeight" : 33.23, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 254.638, + "contentHeight" : 33.23, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ReceiptModernRoot[0]/ReceiptMasthead[0]/ReceiptMastheadRow[0]/ReceiptDocumentTitle[1]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "ReceiptModernRoot[0]/ReceiptMasthead[0]/ReceiptMastheadRow[0]/ReceiptDocumentTitle[1]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 306.638, + "computedY" : 794.94, + "placementX" : 306.638, + "placementY" : 794.94, + "placementWidth" : 254.638, + "placementHeight" : 12.95, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 254.638, + "contentHeight" : 12.95, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ReceiptModernRoot[0]/ReceiptMasthead[0]/ReceiptMastheadRow[0]/ReceiptDocumentTitle[1]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "ReceiptModernRoot[0]/ReceiptMasthead[0]/ReceiptMastheadRow[0]/ReceiptDocumentTitle[1]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 306.638, + "computedY" : 784.8, + "placementX" : 306.638, + "placementY" : 784.8, + "placementWidth" : 254.638, + "placementHeight" : 8.14, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 254.638, + "contentHeight" : 8.14, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ReceiptModernRoot[0]/ReceiptMasthead[0]/ReceiptMastheadRow[0]/ReceiptDocumentTitle[1]/ParagraphNode[2]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "ReceiptModernRoot[0]/ReceiptMasthead[0]/ReceiptMastheadRow[0]/ReceiptDocumentTitle[1]", + "childIndex" : 2, + "depth" : 5, + "layer" : 5, + "computedX" : 306.638, + "computedY" : 774.66, + "placementX" : 306.638, + "placementY" : 774.66, + "placementWidth" : 254.638, + "placementHeight" : 8.14, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 254.638, + "contentHeight" : 8.14, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ReceiptModernRoot[0]/ReceiptMasthead[0]/LineNode[1]", + "entityName" : null, + "entityKind" : "LineNode", + "parentPath" : "ReceiptModernRoot[0]/ReceiptMasthead[0]", + "childIndex" : 1, + "depth" : 3, + "layer" : 3, + "computedX" : 34.0, + "computedY" : 763.66, + "placementX" : 34.0, + "placementY" : 763.66, + "placementWidth" : 527.276, + "placementHeight" : 1.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 527.276, + "contentHeight" : 1.0, + "margin" : { + "top" : 10.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ReceiptModernRoot[0]/ReceiptHero[1]", + "entityName" : "ReceiptHero", + "entityKind" : "SectionNode", + "parentPath" : "ReceiptModernRoot[0]", + "childIndex" : 1, + "depth" : 2, + "layer" : 2, + "computedX" : 34.0, + "computedY" : 665.51, + "placementX" : 34.0, + "placementY" : 665.51, + "placementWidth" : 527.276, + "placementHeight" : 82.15, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 527.276, + "contentHeight" : 82.15, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ReceiptModernRoot[0]/ReceiptHero[1]/ReceiptAmountHero[0]", + "entityName" : "ReceiptAmountHero", + "entityKind" : "SectionNode", + "parentPath" : "ReceiptModernRoot[0]/ReceiptHero[1]", + "childIndex" : 0, + "depth" : 3, + "layer" : 3, + "computedX" : 34.0, + "computedY" : 665.51, + "placementX" : 34.0, + "placementY" : 665.51, + "placementWidth" : 527.276, + "placementHeight" : 82.15, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 527.276, + "contentHeight" : 82.15, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 16.0, + "right" : 16.0, + "bottom" : 16.0, + "left" : 16.0 + } + }, { + "path" : "ReceiptModernRoot[0]/ReceiptHero[1]/ReceiptAmountHero[0]/ReceiptAmountRow[0]", + "entityName" : "ReceiptAmountRow", + "entityKind" : "RowNode", + "parentPath" : "ReceiptModernRoot[0]/ReceiptHero[1]/ReceiptAmountHero[0]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 50.0, + "computedY" : 681.51, + "placementX" : 50.0, + "placementY" : 681.51, + "placementWidth" : 495.276, + "placementHeight" : 50.15, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 495.276, + "contentHeight" : 50.15, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ReceiptModernRoot[0]/ReceiptHero[1]/ReceiptAmountHero[0]/ReceiptAmountRow[0]/ReceiptAmountColumn[0]", + "entityName" : "ReceiptAmountColumn", + "entityKind" : "SectionNode", + "parentPath" : "ReceiptModernRoot[0]/ReceiptHero[1]/ReceiptAmountHero[0]/ReceiptAmountRow[0]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 50.0, + "computedY" : 682.37, + "placementX" : 50.0, + "placementY" : 682.37, + "placementWidth" : 177.531, + "placementHeight" : 49.29, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 177.531, + "contentHeight" : 49.29, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ReceiptModernRoot[0]/ReceiptHero[1]/ReceiptAmountHero[0]/ReceiptAmountRow[0]/ReceiptAmountColumn[0]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "ReceiptModernRoot[0]/ReceiptHero[1]/ReceiptAmountHero[0]/ReceiptAmountRow[0]/ReceiptAmountColumn[0]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 50.0, + "computedY" : 724.26, + "placementX" : 50.0, + "placementY" : 724.26, + "placementWidth" : 107.936, + "placementHeight" : 7.4, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 107.936, + "contentHeight" : 7.4, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ReceiptModernRoot[0]/ReceiptHero[1]/ReceiptAmountHero[0]/ReceiptAmountRow[0]/ReceiptAmountColumn[0]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "ReceiptModernRoot[0]/ReceiptHero[1]/ReceiptAmountHero[0]/ReceiptAmountRow[0]/ReceiptAmountColumn[0]", + "childIndex" : 1, + "depth" : 6, + "layer" : 6, + "computedX" : 50.0, + "computedY" : 693.51, + "placementX" : 50.0, + "placementY" : 693.51, + "placementWidth" : 91.74, + "placementHeight" : 27.75, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 91.74, + "contentHeight" : 27.75, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ReceiptModernRoot[0]/ReceiptHero[1]/ReceiptAmountHero[0]/ReceiptAmountRow[0]/ReceiptAmountColumn[0]/ParagraphNode[2]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "ReceiptModernRoot[0]/ReceiptHero[1]/ReceiptAmountHero[0]/ReceiptAmountRow[0]/ReceiptAmountColumn[0]", + "childIndex" : 2, + "depth" : 6, + "layer" : 6, + "computedX" : 50.0, + "computedY" : 682.37, + "placementX" : 50.0, + "placementY" : 682.37, + "placementWidth" : 177.531, + "placementHeight" : 8.14, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 177.531, + "contentHeight" : 8.14, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ReceiptModernRoot[0]/ReceiptHero[1]/ReceiptAmountHero[0]/ReceiptAmountRow[0]/ReceiptStatusColumn[1]", + "entityName" : "ReceiptStatusColumn", + "entityKind" : "SectionNode", + "parentPath" : "ReceiptModernRoot[0]/ReceiptHero[1]/ReceiptAmountHero[0]/ReceiptAmountRow[0]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 343.031, + "computedY" : 681.51, + "placementX" : 343.031, + "placementY" : 681.51, + "placementWidth" : 202.245, + "placementHeight" : 50.15, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 202.245, + "contentHeight" : 50.15, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ReceiptModernRoot[0]/ReceiptHero[1]/ReceiptAmountHero[0]/ReceiptAmountRow[0]/ReceiptStatusColumn[1]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "ReceiptModernRoot[0]/ReceiptHero[1]/ReceiptAmountHero[0]/ReceiptAmountRow[0]/ReceiptStatusColumn[1]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 343.031, + "computedY" : 724.26, + "placementX" : 343.031, + "placementY" : 724.26, + "placementWidth" : 202.245, + "placementHeight" : 7.4, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 202.245, + "contentHeight" : 7.4, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ReceiptModernRoot[0]/ReceiptHero[1]/ReceiptAmountHero[0]/ReceiptAmountRow[0]/ReceiptStatusColumn[1]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "ReceiptModernRoot[0]/ReceiptHero[1]/ReceiptAmountHero[0]/ReceiptAmountRow[0]/ReceiptStatusColumn[1]", + "childIndex" : 1, + "depth" : 6, + "layer" : 6, + "computedX" : 343.031, + "computedY" : 710.01, + "placementX" : 343.031, + "placementY" : 710.01, + "placementWidth" : 202.245, + "placementHeight" : 9.25, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 202.245, + "contentHeight" : 9.25, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ReceiptModernRoot[0]/ReceiptHero[1]/ReceiptAmountHero[0]/ReceiptAmountRow[0]/ReceiptStatusColumn[1]/ParagraphNode[2]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "ReceiptModernRoot[0]/ReceiptHero[1]/ReceiptAmountHero[0]/ReceiptAmountRow[0]/ReceiptStatusColumn[1]", + "childIndex" : 2, + "depth" : 6, + "layer" : 6, + "computedX" : 343.031, + "computedY" : 695.76, + "placementX" : 343.031, + "placementY" : 695.76, + "placementWidth" : 202.245, + "placementHeight" : 9.25, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 202.245, + "contentHeight" : 9.25, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ReceiptModernRoot[0]/ReceiptHero[1]/ReceiptAmountHero[0]/ReceiptAmountRow[0]/ReceiptStatusColumn[1]/ParagraphNode[3]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "ReceiptModernRoot[0]/ReceiptHero[1]/ReceiptAmountHero[0]/ReceiptAmountRow[0]/ReceiptStatusColumn[1]", + "childIndex" : 3, + "depth" : 6, + "layer" : 6, + "computedX" : 343.031, + "computedY" : 681.51, + "placementX" : 343.031, + "placementY" : 681.51, + "placementWidth" : 202.245, + "placementHeight" : 9.25, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 202.245, + "contentHeight" : 9.25, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ReceiptModernRoot[0]/ReceiptParties[2]", + "entityName" : "ReceiptParties", + "entityKind" : "SectionNode", + "parentPath" : "ReceiptModernRoot[0]", + "childIndex" : 2, + "depth" : 2, + "layer" : 2, + "computedX" : 34.0, + "computedY" : 535.61, + "placementX" : 34.0, + "placementY" : 535.61, + "placementWidth" : 527.276, + "placementHeight" : 113.9, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 527.276, + "contentHeight" : 113.9, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ReceiptModernRoot[0]/ReceiptParties[2]/ReceiptPartyPanel[0]", + "entityName" : "ReceiptPartyPanel", + "entityKind" : "SectionNode", + "parentPath" : "ReceiptModernRoot[0]/ReceiptParties[2]", + "childIndex" : 0, + "depth" : 3, + "layer" : 3, + "computedX" : 34.0, + "computedY" : 535.61, + "placementX" : 34.0, + "placementY" : 535.61, + "placementWidth" : 527.276, + "placementHeight" : 113.9, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 527.276, + "contentHeight" : 113.9, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 16.0, + "right" : 16.0, + "bottom" : 16.0, + "left" : 16.0 + } + }, { + "path" : "ReceiptModernRoot[0]/ReceiptParties[2]/ReceiptPartyPanel[0]/ReceiptPartyRow[0]", + "entityName" : "ReceiptPartyRow", + "entityKind" : "RowNode", + "parentPath" : "ReceiptModernRoot[0]/ReceiptParties[2]/ReceiptPartyPanel[0]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 50.0, + "computedY" : 551.61, + "placementX" : 50.0, + "placementY" : 551.61, + "placementWidth" : 495.276, + "placementHeight" : 81.9, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 495.276, + "contentHeight" : 81.9, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ReceiptModernRoot[0]/ReceiptParties[2]/ReceiptPartyPanel[0]/ReceiptPartyRow[0]/ReceiptPayer[0]", + "entityName" : "ReceiptPayer", + "entityKind" : "SectionNode", + "parentPath" : "ReceiptModernRoot[0]/ReceiptParties[2]/ReceiptPartyPanel[0]/ReceiptPartyRow[0]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 50.0, + "computedY" : 551.61, + "placementX" : 50.0, + "placementY" : 551.61, + "placementWidth" : 88.38, + "placementHeight" : 81.9, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 88.38, + "contentHeight" : 81.9, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ReceiptModernRoot[0]/ReceiptParties[2]/ReceiptPartyPanel[0]/ReceiptPartyRow[0]/ReceiptPayer[0]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "ReceiptModernRoot[0]/ReceiptParties[2]/ReceiptPartyPanel[0]/ReceiptPartyRow[0]/ReceiptPayer[0]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 50.0, + "computedY" : 626.11, + "placementX" : 50.0, + "placementY" : 626.11, + "placementWidth" : 57.408, + "placementHeight" : 7.4, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 57.408, + "contentHeight" : 7.4, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ReceiptModernRoot[0]/ReceiptParties[2]/ReceiptPartyPanel[0]/ReceiptPartyRow[0]/ReceiptPayer[0]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "ReceiptModernRoot[0]/ReceiptParties[2]/ReceiptPartyPanel[0]/ReceiptPartyRow[0]/ReceiptPayer[0]", + "childIndex" : 1, + "depth" : 6, + "layer" : 6, + "computedX" : 50.0, + "computedY" : 612.23, + "placementX" : 50.0, + "placementY" : 612.23, + "placementWidth" : 57.091, + "placementHeight" : 8.88, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 57.091, + "contentHeight" : 8.88, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ReceiptModernRoot[0]/ReceiptParties[2]/ReceiptPartyPanel[0]/ReceiptPartyRow[0]/ReceiptPayer[0]/ParagraphNode[2]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "ReceiptModernRoot[0]/ReceiptParties[2]/ReceiptPartyPanel[0]/ReceiptPartyRow[0]/ReceiptPayer[0]", + "childIndex" : 2, + "depth" : 6, + "layer" : 6, + "computedX" : 50.0, + "computedY" : 580.11, + "placementX" : 50.0, + "placementY" : 580.11, + "placementWidth" : 76.296, + "placementHeight" : 27.12, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 76.296, + "contentHeight" : 27.12, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ReceiptModernRoot[0]/ReceiptParties[2]/ReceiptPartyPanel[0]/ReceiptPartyRow[0]/ReceiptPayer[0]/ParagraphNode[3]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "ReceiptModernRoot[0]/ReceiptParties[2]/ReceiptPartyPanel[0]/ReceiptPartyRow[0]/ReceiptPayer[0]", + "childIndex" : 3, + "depth" : 6, + "layer" : 6, + "computedX" : 50.0, + "computedY" : 565.86, + "placementX" : 50.0, + "placementY" : 565.86, + "placementWidth" : 80.71, + "placementHeight" : 9.25, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 80.71, + "contentHeight" : 9.25, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ReceiptModernRoot[0]/ReceiptParties[2]/ReceiptPartyPanel[0]/ReceiptPartyRow[0]/ReceiptPayer[0]/ParagraphNode[4]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "ReceiptModernRoot[0]/ReceiptParties[2]/ReceiptPartyPanel[0]/ReceiptPartyRow[0]/ReceiptPayer[0]", + "childIndex" : 4, + "depth" : 6, + "layer" : 6, + "computedX" : 50.0, + "computedY" : 551.61, + "placementX" : 50.0, + "placementY" : 551.61, + "placementWidth" : 88.38, + "placementHeight" : 9.25, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 88.38, + "contentHeight" : 9.25, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ReceiptModernRoot[0]/ReceiptParties[2]/ReceiptPartyPanel[0]/ReceiptPartyRow[0]/ReceiptDirection[1]", + "entityName" : "ReceiptDirection", + "entityKind" : "SectionNode", + "parentPath" : "ReceiptModernRoot[0]/ReceiptParties[2]/ReceiptPartyPanel[0]/ReceiptPartyRow[0]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 284.187, + "computedY" : 586.085, + "placementX" : 284.187, + "placementY" : 586.085, + "placementWidth" : 26.902, + "placementHeight" : 12.95, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 26.902, + "contentHeight" : 12.95, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ReceiptModernRoot[0]/ReceiptParties[2]/ReceiptPartyPanel[0]/ReceiptPartyRow[0]/ReceiptDirection[1]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "ReceiptModernRoot[0]/ReceiptParties[2]/ReceiptPartyPanel[0]/ReceiptPartyRow[0]/ReceiptDirection[1]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 284.187, + "computedY" : 586.085, + "placementX" : 284.187, + "placementY" : 586.085, + "placementWidth" : 26.902, + "placementHeight" : 12.95, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 26.902, + "contentHeight" : 12.95, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ReceiptModernRoot[0]/ReceiptParties[2]/ReceiptPartyPanel[0]/ReceiptPartyRow[0]/ReceiptBeneficiary[2]", + "entityName" : "ReceiptBeneficiary", + "entityKind" : "SectionNode", + "parentPath" : "ReceiptModernRoot[0]/ReceiptParties[2]/ReceiptPartyPanel[0]/ReceiptPartyRow[0]", + "childIndex" : 2, + "depth" : 5, + "layer" : 5, + "computedX" : 321.089, + "computedY" : 556.355, + "placementX" : 321.089, + "placementY" : 556.355, + "placementWidth" : 93.878, + "placementHeight" : 72.41, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 93.878, + "contentHeight" : 72.41, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ReceiptModernRoot[0]/ReceiptParties[2]/ReceiptPartyPanel[0]/ReceiptPartyRow[0]/ReceiptBeneficiary[2]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "ReceiptModernRoot[0]/ReceiptParties[2]/ReceiptPartyPanel[0]/ReceiptPartyRow[0]/ReceiptBeneficiary[2]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 321.089, + "computedY" : 621.365, + "placementX" : 321.089, + "placementY" : 621.365, + "placementWidth" : 42.088, + "placementHeight" : 7.4, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 42.088, + "contentHeight" : 7.4, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ReceiptModernRoot[0]/ReceiptParties[2]/ReceiptPartyPanel[0]/ReceiptPartyRow[0]/ReceiptBeneficiary[2]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "ReceiptModernRoot[0]/ReceiptParties[2]/ReceiptPartyPanel[0]/ReceiptPartyRow[0]/ReceiptBeneficiary[2]", + "childIndex" : 1, + "depth" : 6, + "layer" : 6, + "computedX" : 321.089, + "computedY" : 607.485, + "placementX" : 321.089, + "placementY" : 607.485, + "placementWidth" : 93.878, + "placementHeight" : 8.88, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 93.878, + "contentHeight" : 8.88, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ReceiptModernRoot[0]/ReceiptParties[2]/ReceiptPartyPanel[0]/ReceiptPartyRow[0]/ReceiptBeneficiary[2]/ParagraphNode[2]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "ReceiptModernRoot[0]/ReceiptParties[2]/ReceiptPartyPanel[0]/ReceiptPartyRow[0]/ReceiptBeneficiary[2]", + "childIndex" : 2, + "depth" : 6, + "layer" : 6, + "computedX" : 321.089, + "computedY" : 584.855, + "placementX" : 321.089, + "placementY" : 584.855, + "placementWidth" : 79.719, + "placementHeight" : 17.63, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 79.719, + "contentHeight" : 17.63, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ReceiptModernRoot[0]/ReceiptParties[2]/ReceiptPartyPanel[0]/ReceiptPartyRow[0]/ReceiptBeneficiary[2]/ParagraphNode[3]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "ReceiptModernRoot[0]/ReceiptParties[2]/ReceiptPartyPanel[0]/ReceiptPartyRow[0]/ReceiptBeneficiary[2]", + "childIndex" : 3, + "depth" : 6, + "layer" : 6, + "computedX" : 321.089, + "computedY" : 570.605, + "placementX" : 321.089, + "placementY" : 570.605, + "placementWidth" : 80.71, + "placementHeight" : 9.25, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 80.71, + "contentHeight" : 9.25, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ReceiptModernRoot[0]/ReceiptParties[2]/ReceiptPartyPanel[0]/ReceiptPartyRow[0]/ReceiptBeneficiary[2]/ParagraphNode[4]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "ReceiptModernRoot[0]/ReceiptParties[2]/ReceiptPartyPanel[0]/ReceiptPartyRow[0]/ReceiptBeneficiary[2]", + "childIndex" : 4, + "depth" : 6, + "layer" : 6, + "computedX" : 321.089, + "computedY" : 556.355, + "placementX" : 321.089, + "placementY" : 556.355, + "placementWidth" : 88.38, + "placementHeight" : 9.25, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 88.38, + "contentHeight" : 9.25, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ReceiptModernRoot[0]/ReceiptDetails[3]", + "entityName" : "ReceiptDetails", + "entityKind" : "SectionNode", + "parentPath" : "ReceiptModernRoot[0]", + "childIndex" : 3, + "depth" : 2, + "layer" : 2, + "computedX" : 34.0, + "computedY" : 452.21, + "placementX" : 34.0, + "placementY" : 452.21, + "placementWidth" : 527.276, + "placementHeight" : 67.4, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 527.276, + "contentHeight" : 67.4, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ReceiptModernRoot[0]/ReceiptDetails[3]/ReceiptDetailGroup[0]", + "entityName" : "ReceiptDetailGroup", + "entityKind" : "SectionNode", + "parentPath" : "ReceiptModernRoot[0]/ReceiptDetails[3]", + "childIndex" : 0, + "depth" : 3, + "layer" : 3, + "computedX" : 34.0, + "computedY" : 452.21, + "placementX" : 34.0, + "placementY" : 452.21, + "placementWidth" : 527.276, + "placementHeight" : 67.4, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 527.276, + "contentHeight" : 67.4, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ReceiptModernRoot[0]/ReceiptDetails[3]/ReceiptDetailGroup[0]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "ReceiptModernRoot[0]/ReceiptDetails[3]/ReceiptDetailGroup[0]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 34.0, + "computedY" : 512.21, + "placementX" : 34.0, + "placementY" : 512.21, + "placementWidth" : 103.04, + "placementHeight" : 7.4, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 103.04, + "contentHeight" : 7.4, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 7.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ReceiptModernRoot[0]/ReceiptDetails[3]/ReceiptDetailGroup[0]/ReceiptFieldRow[1]", + "entityName" : "ReceiptFieldRow", + "entityKind" : "RowNode", + "parentPath" : "ReceiptModernRoot[0]/ReceiptDetails[3]/ReceiptDetailGroup[0]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 34.0, + "computedY" : 491.96, + "placementX" : 34.0, + "placementY" : 491.96, + "placementWidth" : 527.276, + "placementHeight" : 13.25, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 527.276, + "contentHeight" : 13.25, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 4.0, + "left" : 0.0 + } + }, { + "path" : "ReceiptModernRoot[0]/ReceiptDetails[3]/ReceiptDetailGroup[0]/ReceiptFieldRow[1]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "ReceiptModernRoot[0]/ReceiptDetails[3]/ReceiptDetailGroup[0]/ReceiptFieldRow[1]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 34.0, + "computedY" : 495.96, + "placementX" : 34.0, + "placementY" : 495.96, + "placementWidth" : 83.93, + "placementHeight" : 9.25, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 83.93, + "contentHeight" : 9.25, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ReceiptModernRoot[0]/ReceiptDetails[3]/ReceiptDetailGroup[0]/ReceiptFieldRow[1]/LineNode[1]", + "entityName" : null, + "entityKind" : "LineNode", + "parentPath" : "ReceiptModernRoot[0]/ReceiptDetails[3]/ReceiptDetailGroup[0]/ReceiptFieldRow[1]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 123.93, + "computedY" : 497.96, + "placementX" : 123.93, + "placementY" : 497.96, + "placementWidth" : 331.866, + "placementHeight" : 1.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 331.866, + "contentHeight" : 1.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 2.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ReceiptModernRoot[0]/ReceiptDetails[3]/ReceiptDetailGroup[0]/ReceiptFieldRow[1]/ParagraphNode[2]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "ReceiptModernRoot[0]/ReceiptDetails[3]/ReceiptDetailGroup[0]/ReceiptFieldRow[1]", + "childIndex" : 2, + "depth" : 5, + "layer" : 5, + "computedX" : 461.796, + "computedY" : 495.96, + "placementX" : 461.796, + "placementY" : 495.96, + "placementWidth" : 99.48, + "placementHeight" : 9.25, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 99.48, + "contentHeight" : 9.25, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ReceiptModernRoot[0]/ReceiptDetails[3]/ReceiptDetailGroup[0]/ReceiptFieldRow[2]", + "entityName" : "ReceiptFieldRow", + "entityKind" : "RowNode", + "parentPath" : "ReceiptModernRoot[0]/ReceiptDetails[3]/ReceiptDetailGroup[0]", + "childIndex" : 2, + "depth" : 4, + "layer" : 4, + "computedX" : 34.0, + "computedY" : 478.71, + "placementX" : 34.0, + "placementY" : 478.71, + "placementWidth" : 527.276, + "placementHeight" : 13.25, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 527.276, + "contentHeight" : 13.25, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 4.0, + "left" : 0.0 + } + }, { + "path" : "ReceiptModernRoot[0]/ReceiptDetails[3]/ReceiptDetailGroup[0]/ReceiptFieldRow[2]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "ReceiptModernRoot[0]/ReceiptDetails[3]/ReceiptDetailGroup[0]/ReceiptFieldRow[2]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 34.0, + "computedY" : 482.71, + "placementX" : 34.0, + "placementY" : 482.71, + "placementWidth" : 65.02, + "placementHeight" : 9.25, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 65.02, + "contentHeight" : 9.25, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ReceiptModernRoot[0]/ReceiptDetails[3]/ReceiptDetailGroup[0]/ReceiptFieldRow[2]/LineNode[1]", + "entityName" : null, + "entityKind" : "LineNode", + "parentPath" : "ReceiptModernRoot[0]/ReceiptDetails[3]/ReceiptDetailGroup[0]/ReceiptFieldRow[2]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 105.02, + "computedY" : 484.71, + "placementX" : 105.02, + "placementY" : 484.71, + "placementWidth" : 265.696, + "placementHeight" : 1.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 265.696, + "contentHeight" : 1.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 2.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ReceiptModernRoot[0]/ReceiptDetails[3]/ReceiptDetailGroup[0]/ReceiptFieldRow[2]/ParagraphNode[2]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "ReceiptModernRoot[0]/ReceiptDetails[3]/ReceiptDetailGroup[0]/ReceiptFieldRow[2]", + "childIndex" : 2, + "depth" : 5, + "layer" : 5, + "computedX" : 376.716, + "computedY" : 482.71, + "placementX" : 376.716, + "placementY" : 482.71, + "placementWidth" : 184.56, + "placementHeight" : 9.25, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 184.56, + "contentHeight" : 9.25, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ReceiptModernRoot[0]/ReceiptDetails[3]/ReceiptDetailGroup[0]/ReceiptFieldRow[3]", + "entityName" : "ReceiptFieldRow", + "entityKind" : "RowNode", + "parentPath" : "ReceiptModernRoot[0]/ReceiptDetails[3]/ReceiptDetailGroup[0]", + "childIndex" : 3, + "depth" : 4, + "layer" : 4, + "computedX" : 34.0, + "computedY" : 465.46, + "placementX" : 34.0, + "placementY" : 465.46, + "placementWidth" : 527.276, + "placementHeight" : 13.25, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 527.276, + "contentHeight" : 13.25, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 4.0, + "left" : 0.0 + } + }, { + "path" : "ReceiptModernRoot[0]/ReceiptDetails[3]/ReceiptDetailGroup[0]/ReceiptFieldRow[3]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "ReceiptModernRoot[0]/ReceiptDetails[3]/ReceiptDetailGroup[0]/ReceiptFieldRow[3]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 34.0, + "computedY" : 469.46, + "placementX" : 34.0, + "placementY" : 469.46, + "placementWidth" : 77.25, + "placementHeight" : 9.25, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 77.25, + "contentHeight" : 9.25, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ReceiptModernRoot[0]/ReceiptDetails[3]/ReceiptDetailGroup[0]/ReceiptFieldRow[3]/LineNode[1]", + "entityName" : null, + "entityKind" : "LineNode", + "parentPath" : "ReceiptModernRoot[0]/ReceiptDetails[3]/ReceiptDetailGroup[0]/ReceiptFieldRow[3]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 117.25, + "computedY" : 471.46, + "placementX" : 117.25, + "placementY" : 471.46, + "placementWidth" : 360.786, + "placementHeight" : 1.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 360.786, + "contentHeight" : 1.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 2.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ReceiptModernRoot[0]/ReceiptDetails[3]/ReceiptDetailGroup[0]/ReceiptFieldRow[3]/ParagraphNode[2]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "ReceiptModernRoot[0]/ReceiptDetails[3]/ReceiptDetailGroup[0]/ReceiptFieldRow[3]", + "childIndex" : 2, + "depth" : 5, + "layer" : 5, + "computedX" : 484.036, + "computedY" : 469.46, + "placementX" : 484.036, + "placementY" : 469.46, + "placementWidth" : 77.24, + "placementHeight" : 9.25, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 77.24, + "contentHeight" : 9.25, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ReceiptModernRoot[0]/ReceiptDetails[3]/ReceiptDetailGroup[0]/ReceiptFieldRow[4]", + "entityName" : "ReceiptFieldRow", + "entityKind" : "RowNode", + "parentPath" : "ReceiptModernRoot[0]/ReceiptDetails[3]/ReceiptDetailGroup[0]", + "childIndex" : 4, + "depth" : 4, + "layer" : 4, + "computedX" : 34.0, + "computedY" : 452.21, + "placementX" : 34.0, + "placementY" : 452.21, + "placementWidth" : 527.276, + "placementHeight" : 13.25, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 527.276, + "contentHeight" : 13.25, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 4.0, + "left" : 0.0 + } + }, { + "path" : "ReceiptModernRoot[0]/ReceiptDetails[3]/ReceiptDetailGroup[0]/ReceiptFieldRow[4]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "ReceiptModernRoot[0]/ReceiptDetails[3]/ReceiptDetailGroup[0]/ReceiptFieldRow[4]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 34.0, + "computedY" : 456.21, + "placementX" : 34.0, + "placementY" : 456.21, + "placementWidth" : 96.71, + "placementHeight" : 9.25, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 96.71, + "contentHeight" : 9.25, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ReceiptModernRoot[0]/ReceiptDetails[3]/ReceiptDetailGroup[0]/ReceiptFieldRow[4]/LineNode[1]", + "entityName" : null, + "entityKind" : "LineNode", + "parentPath" : "ReceiptModernRoot[0]/ReceiptDetails[3]/ReceiptDetailGroup[0]/ReceiptFieldRow[4]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 136.71, + "computedY" : 458.21, + "placementX" : 136.71, + "placementY" : 458.21, + "placementWidth" : 324.656, + "placementHeight" : 1.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 324.656, + "contentHeight" : 1.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 2.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ReceiptModernRoot[0]/ReceiptDetails[3]/ReceiptDetailGroup[0]/ReceiptFieldRow[4]/ParagraphNode[2]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "ReceiptModernRoot[0]/ReceiptDetails[3]/ReceiptDetailGroup[0]/ReceiptFieldRow[4]", + "childIndex" : 2, + "depth" : 5, + "layer" : 5, + "computedX" : 467.366, + "computedY" : 456.21, + "placementX" : 467.366, + "placementY" : 456.21, + "placementWidth" : 93.91, + "placementHeight" : 9.25, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 93.91, + "contentHeight" : 9.25, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ReceiptModernRoot[0]/ReceiptDetails[4]", + "entityName" : "ReceiptDetails", + "entityKind" : "SectionNode", + "parentPath" : "ReceiptModernRoot[0]", + "childIndex" : 4, + "depth" : 2, + "layer" : 2, + "computedX" : 34.0, + "computedY" : 368.81, + "placementX" : 34.0, + "placementY" : 368.81, + "placementWidth" : 527.276, + "placementHeight" : 67.4, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 527.276, + "contentHeight" : 67.4, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ReceiptModernRoot[0]/ReceiptDetails[4]/ReceiptDetailGroup[0]", + "entityName" : "ReceiptDetailGroup", + "entityKind" : "SectionNode", + "parentPath" : "ReceiptModernRoot[0]/ReceiptDetails[4]", + "childIndex" : 0, + "depth" : 3, + "layer" : 3, + "computedX" : 34.0, + "computedY" : 368.81, + "placementX" : 34.0, + "placementY" : 368.81, + "placementWidth" : 527.276, + "placementHeight" : 67.4, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 527.276, + "contentHeight" : 67.4, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ReceiptModernRoot[0]/ReceiptDetails[4]/ReceiptDetailGroup[0]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "ReceiptModernRoot[0]/ReceiptDetails[4]/ReceiptDetailGroup[0]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 34.0, + "computedY" : 428.81, + "placementX" : 34.0, + "placementY" : 428.81, + "placementWidth" : 114.136, + "placementHeight" : 7.4, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 114.136, + "contentHeight" : 7.4, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 7.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ReceiptModernRoot[0]/ReceiptDetails[4]/ReceiptDetailGroup[0]/ReceiptFieldRow[1]", + "entityName" : "ReceiptFieldRow", + "entityKind" : "RowNode", + "parentPath" : "ReceiptModernRoot[0]/ReceiptDetails[4]/ReceiptDetailGroup[0]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 34.0, + "computedY" : 408.56, + "placementX" : 34.0, + "placementY" : 408.56, + "placementWidth" : 527.276, + "placementHeight" : 13.25, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 527.276, + "contentHeight" : 13.25, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 4.0, + "left" : 0.0 + } + }, { + "path" : "ReceiptModernRoot[0]/ReceiptDetails[4]/ReceiptDetailGroup[0]/ReceiptFieldRow[1]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "ReceiptModernRoot[0]/ReceiptDetails[4]/ReceiptDetailGroup[0]/ReceiptFieldRow[1]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 34.0, + "computedY" : 412.56, + "placementX" : 34.0, + "placementY" : 412.56, + "placementWidth" : 34.46, + "placementHeight" : 9.25, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 34.46, + "contentHeight" : 9.25, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ReceiptModernRoot[0]/ReceiptDetails[4]/ReceiptDetailGroup[0]/ReceiptFieldRow[1]/LineNode[1]", + "entityName" : null, + "entityKind" : "LineNode", + "parentPath" : "ReceiptModernRoot[0]/ReceiptDetails[4]/ReceiptDetailGroup[0]/ReceiptFieldRow[1]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 74.46, + "computedY" : 414.56, + "placementX" : 74.46, + "placementY" : 414.56, + "placementWidth" : 450.236, + "placementHeight" : 1.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 450.236, + "contentHeight" : 1.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 2.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ReceiptModernRoot[0]/ReceiptDetails[4]/ReceiptDetailGroup[0]/ReceiptFieldRow[1]/ParagraphNode[2]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "ReceiptModernRoot[0]/ReceiptDetails[4]/ReceiptDetailGroup[0]/ReceiptFieldRow[1]", + "childIndex" : 2, + "depth" : 5, + "layer" : 5, + "computedX" : 530.696, + "computedY" : 412.56, + "placementX" : 530.696, + "placementY" : 412.56, + "placementWidth" : 30.58, + "placementHeight" : 9.25, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 30.58, + "contentHeight" : 9.25, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ReceiptModernRoot[0]/ReceiptDetails[4]/ReceiptDetailGroup[0]/ReceiptFieldRow[2]", + "entityName" : "ReceiptFieldRow", + "entityKind" : "RowNode", + "parentPath" : "ReceiptModernRoot[0]/ReceiptDetails[4]/ReceiptDetailGroup[0]", + "childIndex" : 2, + "depth" : 4, + "layer" : 4, + "computedX" : 34.0, + "computedY" : 395.31, + "placementX" : 34.0, + "placementY" : 395.31, + "placementWidth" : 527.276, + "placementHeight" : 13.25, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 527.276, + "contentHeight" : 13.25, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 4.0, + "left" : 0.0 + } + }, { + "path" : "ReceiptModernRoot[0]/ReceiptDetails[4]/ReceiptDetailGroup[0]/ReceiptFieldRow[2]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "ReceiptModernRoot[0]/ReceiptDetails[4]/ReceiptDetailGroup[0]/ReceiptFieldRow[2]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 34.0, + "computedY" : 399.31, + "placementX" : 34.0, + "placementY" : 399.31, + "placementWidth" : 53.91, + "placementHeight" : 9.25, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 53.91, + "contentHeight" : 9.25, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ReceiptModernRoot[0]/ReceiptDetails[4]/ReceiptDetailGroup[0]/ReceiptFieldRow[2]/LineNode[1]", + "entityName" : null, + "entityKind" : "LineNode", + "parentPath" : "ReceiptModernRoot[0]/ReceiptDetails[4]/ReceiptDetailGroup[0]/ReceiptFieldRow[2]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 93.91, + "computedY" : 401.31, + "placementX" : 93.91, + "placementY" : 401.31, + "placementWidth" : 436.346, + "placementHeight" : 1.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 436.346, + "contentHeight" : 1.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 2.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ReceiptModernRoot[0]/ReceiptDetails[4]/ReceiptDetailGroup[0]/ReceiptFieldRow[2]/ParagraphNode[2]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "ReceiptModernRoot[0]/ReceiptDetails[4]/ReceiptDetailGroup[0]/ReceiptFieldRow[2]", + "childIndex" : 2, + "depth" : 5, + "layer" : 5, + "computedX" : 536.256, + "computedY" : 399.31, + "placementX" : 536.256, + "placementY" : 399.31, + "placementWidth" : 25.02, + "placementHeight" : 9.25, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 25.02, + "contentHeight" : 9.25, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ReceiptModernRoot[0]/ReceiptDetails[4]/ReceiptDetailGroup[0]/ReceiptFieldRow[3]", + "entityName" : "ReceiptFieldRow", + "entityKind" : "RowNode", + "parentPath" : "ReceiptModernRoot[0]/ReceiptDetails[4]/ReceiptDetailGroup[0]", + "childIndex" : 3, + "depth" : 4, + "layer" : 4, + "computedX" : 34.0, + "computedY" : 382.06, + "placementX" : 34.0, + "placementY" : 382.06, + "placementWidth" : 527.276, + "placementHeight" : 13.25, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 527.276, + "contentHeight" : 13.25, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 4.0, + "left" : 0.0 + } + }, { + "path" : "ReceiptModernRoot[0]/ReceiptDetails[4]/ReceiptDetailGroup[0]/ReceiptFieldRow[3]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "ReceiptModernRoot[0]/ReceiptDetails[4]/ReceiptDetailGroup[0]/ReceiptFieldRow[3]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 34.0, + "computedY" : 386.06, + "placementX" : 34.0, + "placementY" : 386.06, + "placementWidth" : 64.48, + "placementHeight" : 9.25, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 64.48, + "contentHeight" : 9.25, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ReceiptModernRoot[0]/ReceiptDetails[4]/ReceiptDetailGroup[0]/ReceiptFieldRow[3]/LineNode[1]", + "entityName" : null, + "entityKind" : "LineNode", + "parentPath" : "ReceiptModernRoot[0]/ReceiptDetails[4]/ReceiptDetailGroup[0]/ReceiptFieldRow[3]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 104.48, + "computedY" : 388.06, + "placementX" : 104.48, + "placementY" : 388.06, + "placementWidth" : 377.976, + "placementHeight" : 1.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 377.976, + "contentHeight" : 1.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 2.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ReceiptModernRoot[0]/ReceiptDetails[4]/ReceiptDetailGroup[0]/ReceiptFieldRow[3]/ParagraphNode[2]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "ReceiptModernRoot[0]/ReceiptDetails[4]/ReceiptDetailGroup[0]/ReceiptFieldRow[3]", + "childIndex" : 2, + "depth" : 5, + "layer" : 5, + "computedX" : 488.456, + "computedY" : 386.06, + "placementX" : 488.456, + "placementY" : 386.06, + "placementWidth" : 72.82, + "placementHeight" : 9.25, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 72.82, + "contentHeight" : 9.25, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ReceiptModernRoot[0]/ReceiptDetails[4]/ReceiptDetailGroup[0]/ReceiptFieldRow[4]", + "entityName" : "ReceiptFieldRow", + "entityKind" : "RowNode", + "parentPath" : "ReceiptModernRoot[0]/ReceiptDetails[4]/ReceiptDetailGroup[0]", + "childIndex" : 4, + "depth" : 4, + "layer" : 4, + "computedX" : 34.0, + "computedY" : 368.81, + "placementX" : 34.0, + "placementY" : 368.81, + "placementWidth" : 527.276, + "placementHeight" : 13.25, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 527.276, + "contentHeight" : 13.25, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 4.0, + "left" : 0.0 + } + }, { + "path" : "ReceiptModernRoot[0]/ReceiptDetails[4]/ReceiptDetailGroup[0]/ReceiptFieldRow[4]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "ReceiptModernRoot[0]/ReceiptDetails[4]/ReceiptDetailGroup[0]/ReceiptFieldRow[4]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 34.0, + "computedY" : 372.81, + "placementX" : 34.0, + "placementY" : 372.81, + "placementWidth" : 57.81, + "placementHeight" : 9.25, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 57.81, + "contentHeight" : 9.25, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ReceiptModernRoot[0]/ReceiptDetails[4]/ReceiptDetailGroup[0]/ReceiptFieldRow[4]/LineNode[1]", + "entityName" : null, + "entityKind" : "LineNode", + "parentPath" : "ReceiptModernRoot[0]/ReceiptDetails[4]/ReceiptDetailGroup[0]/ReceiptFieldRow[4]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 97.81, + "computedY" : 374.81, + "placementX" : 97.81, + "placementY" : 374.81, + "placementWidth" : 426.886, + "placementHeight" : 1.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 426.886, + "contentHeight" : 1.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 2.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ReceiptModernRoot[0]/ReceiptDetails[4]/ReceiptDetailGroup[0]/ReceiptFieldRow[4]/ParagraphNode[2]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "ReceiptModernRoot[0]/ReceiptDetails[4]/ReceiptDetailGroup[0]/ReceiptFieldRow[4]", + "childIndex" : 2, + "depth" : 5, + "layer" : 5, + "computedX" : 530.696, + "computedY" : 372.81, + "placementX" : 530.696, + "placementY" : 372.81, + "placementWidth" : 30.58, + "placementHeight" : 9.25, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 30.58, + "contentHeight" : 9.25, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ReceiptModernRoot[0]/ReceiptTrail[5]", + "entityName" : "ReceiptTrail", + "entityKind" : "SectionNode", + "parentPath" : "ReceiptModernRoot[0]", + "childIndex" : 5, + "depth" : 2, + "layer" : 2, + "computedX" : 34.0, + "computedY" : 267.24, + "placementX" : 34.0, + "placementY" : 267.24, + "placementWidth" : 527.276, + "placementHeight" : 85.57, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 527.276, + "contentHeight" : 85.57, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ReceiptModernRoot[0]/ReceiptTrail[5]/ReceiptStatusTrail[0]", + "entityName" : "ReceiptStatusTrail", + "entityKind" : "SectionNode", + "parentPath" : "ReceiptModernRoot[0]/ReceiptTrail[5]", + "childIndex" : 0, + "depth" : 3, + "layer" : 3, + "computedX" : 34.0, + "computedY" : 267.24, + "placementX" : 34.0, + "placementY" : 267.24, + "placementWidth" : 527.276, + "placementHeight" : 85.57, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 527.276, + "contentHeight" : 85.57, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ReceiptModernRoot[0]/ReceiptTrail[5]/ReceiptStatusTrail[0]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "ReceiptModernRoot[0]/ReceiptTrail[5]/ReceiptStatusTrail[0]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 34.0, + "computedY" : 345.41, + "placementX" : 34.0, + "placementY" : 345.41, + "placementWidth" : 109.72, + "placementHeight" : 7.4, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 109.72, + "contentHeight" : 7.4, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 8.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ReceiptModernRoot[0]/ReceiptTrail[5]/ReceiptStatusTrail[0]/SectionNode[1]", + "entityName" : null, + "entityKind" : "SectionNode", + "parentPath" : "ReceiptModernRoot[0]/ReceiptTrail[5]/ReceiptStatusTrail[0]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 34.0, + "computedY" : 267.24, + "placementX" : 34.0, + "placementY" : 267.24, + "placementWidth" : 527.276, + "placementHeight" : 70.17, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 527.276, + "contentHeight" : 70.17, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ReceiptModernRoot[0]/ReceiptTrail[5]/ReceiptStatusTrail[0]/SectionNode[1]/LayoutAnchorNode[0]", + "entityName" : null, + "entityKind" : "LayoutAnchorNode", + "parentPath" : "ReceiptModernRoot[0]/ReceiptTrail[5]/ReceiptStatusTrail[0]/SectionNode[1]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 34.0, + "computedY" : 312.02, + "placementX" : 34.0, + "placementY" : 312.02, + "placementWidth" : 527.276, + "placementHeight" : 25.39, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 527.276, + "contentHeight" : 25.39, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ReceiptModernRoot[0]/ReceiptTrail[5]/ReceiptStatusTrail[0]/SectionNode[1]/LayoutAnchorNode[0]/SectionNode[0]", + "entityName" : null, + "entityKind" : "SectionNode", + "parentPath" : "ReceiptModernRoot[0]/ReceiptTrail[5]/ReceiptStatusTrail[0]/SectionNode[1]/LayoutAnchorNode[0]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 34.0, + "computedY" : 312.02, + "placementX" : 34.0, + "placementY" : 312.02, + "placementWidth" : 527.276, + "placementHeight" : 25.39, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 527.276, + "contentHeight" : 25.39, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 6.0, + "left" : 12.0 + } + }, { + "path" : "ReceiptModernRoot[0]/ReceiptTrail[5]/ReceiptStatusTrail[0]/SectionNode[1]/LayoutAnchorNode[0]/SectionNode[0]/RowNode[0]", + "entityName" : null, + "entityKind" : "RowNode", + "parentPath" : "ReceiptModernRoot[0]/ReceiptTrail[5]/ReceiptStatusTrail[0]/SectionNode[1]/LayoutAnchorNode[0]/SectionNode[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 46.0, + "computedY" : 318.02, + "placementX" : 46.0, + "placementY" : 318.02, + "placementWidth" : 515.276, + "placementHeight" : 19.39, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 515.276, + "contentHeight" : 19.39, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ReceiptModernRoot[0]/ReceiptTrail[5]/ReceiptStatusTrail[0]/SectionNode[1]/LayoutAnchorNode[0]/SectionNode[0]/RowNode[0]/SectionNode[0]", + "entityName" : null, + "entityKind" : "SectionNode", + "parentPath" : "ReceiptModernRoot[0]/ReceiptTrail[5]/ReceiptStatusTrail[0]/SectionNode[1]/LayoutAnchorNode[0]/SectionNode[0]/RowNode[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 46.0, + "computedY" : 330.41, + "placementX" : 46.0, + "placementY" : 330.41, + "placementWidth" : 10.941, + "placementHeight" : 7.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 10.941, + "contentHeight" : 7.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ReceiptModernRoot[0]/ReceiptTrail[5]/ReceiptStatusTrail[0]/SectionNode[1]/LayoutAnchorNode[0]/SectionNode[0]/RowNode[0]/SectionNode[0]/Align[0]", + "entityName" : null, + "entityKind" : "Align", + "parentPath" : "ReceiptModernRoot[0]/ReceiptTrail[5]/ReceiptStatusTrail[0]/SectionNode[1]/LayoutAnchorNode[0]/SectionNode[0]/RowNode[0]/SectionNode[0]", + "childIndex" : 0, + "depth" : 9, + "layer" : 9, + "computedX" : 46.0, + "computedY" : 330.41, + "placementX" : 46.0, + "placementY" : 330.41, + "placementWidth" : 10.941, + "placementHeight" : 7.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 10.941, + "contentHeight" : 7.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ReceiptModernRoot[0]/ReceiptTrail[5]/ReceiptStatusTrail[0]/SectionNode[1]/LayoutAnchorNode[0]/SectionNode[0]/RowNode[0]/SectionNode[0]/Align[0]/LayoutAnchorNode[0]", + "entityName" : null, + "entityKind" : "LayoutAnchorNode", + "parentPath" : "ReceiptModernRoot[0]/ReceiptTrail[5]/ReceiptStatusTrail[0]/SectionNode[1]/LayoutAnchorNode[0]/SectionNode[0]/RowNode[0]/SectionNode[0]/Align[0]", + "childIndex" : 0, + "depth" : 10, + "layer" : 10, + "computedX" : 46.0, + "computedY" : 330.41, + "placementX" : 46.0, + "placementY" : 330.41, + "placementWidth" : 7.0, + "placementHeight" : 7.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 7.0, + "contentHeight" : 7.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ReceiptModernRoot[0]/ReceiptTrail[5]/ReceiptStatusTrail[0]/SectionNode[1]/LayoutAnchorNode[0]/SectionNode[0]/RowNode[0]/SectionNode[0]/Align[0]/LayoutAnchorNode[0]/marker[0]", + "entityName" : "marker", + "entityKind" : "CanvasLayerNode", + "parentPath" : "ReceiptModernRoot[0]/ReceiptTrail[5]/ReceiptStatusTrail[0]/SectionNode[1]/LayoutAnchorNode[0]/SectionNode[0]/RowNode[0]/SectionNode[0]/Align[0]/LayoutAnchorNode[0]", + "childIndex" : 0, + "depth" : 11, + "layer" : 11, + "computedX" : 46.0, + "computedY" : 330.41, + "placementX" : 46.0, + "placementY" : 330.41, + "placementWidth" : 7.0, + "placementHeight" : 7.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 7.0, + "contentHeight" : 7.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ReceiptModernRoot[0]/ReceiptTrail[5]/ReceiptStatusTrail[0]/SectionNode[1]/LayoutAnchorNode[0]/SectionNode[0]/RowNode[0]/SectionNode[0]/Align[0]/LayoutAnchorNode[0]/marker[0]/SectionNode[0]", + "entityName" : null, + "entityKind" : "SectionNode", + "parentPath" : "ReceiptModernRoot[0]/ReceiptTrail[5]/ReceiptStatusTrail[0]/SectionNode[1]/LayoutAnchorNode[0]/SectionNode[0]/RowNode[0]/SectionNode[0]/Align[0]/LayoutAnchorNode[0]/marker[0]", + "childIndex" : 0, + "depth" : 12, + "layer" : 12, + "computedX" : 46.0, + "computedY" : 330.41, + "placementX" : 46.0, + "placementY" : 330.41, + "placementWidth" : 7.0, + "placementHeight" : 7.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 7.0, + "contentHeight" : 7.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ReceiptModernRoot[0]/ReceiptTrail[5]/ReceiptStatusTrail[0]/SectionNode[1]/LayoutAnchorNode[0]/SectionNode[0]/RowNode[0]/SectionNode[0]/Align[0]/LayoutAnchorNode[0]/marker[0]/SectionNode[0]/EllipseNode[0]", + "entityName" : null, + "entityKind" : "EllipseNode", + "parentPath" : "ReceiptModernRoot[0]/ReceiptTrail[5]/ReceiptStatusTrail[0]/SectionNode[1]/LayoutAnchorNode[0]/SectionNode[0]/RowNode[0]/SectionNode[0]/Align[0]/LayoutAnchorNode[0]/marker[0]/SectionNode[0]", + "childIndex" : 0, + "depth" : 13, + "layer" : 13, + "computedX" : 46.0, + "computedY" : 330.41, + "placementX" : 46.0, + "placementY" : 330.41, + "placementWidth" : 7.0, + "placementHeight" : 7.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 7.0, + "contentHeight" : 7.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ReceiptModernRoot[0]/ReceiptTrail[5]/ReceiptStatusTrail[0]/SectionNode[1]/LayoutAnchorNode[0]/SectionNode[0]/RowNode[0]/SectionNode[1]", + "entityName" : null, + "entityKind" : "SectionNode", + "parentPath" : "ReceiptModernRoot[0]/ReceiptTrail[5]/ReceiptStatusTrail[0]/SectionNode[1]/LayoutAnchorNode[0]/SectionNode[0]/RowNode[0]", + "childIndex" : 1, + "depth" : 8, + "layer" : 8, + "computedX" : 63.941, + "computedY" : 318.02, + "placementX" : 63.941, + "placementY" : 318.02, + "placementWidth" : 275.387, + "placementHeight" : 19.39, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 275.387, + "contentHeight" : 19.39, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ReceiptModernRoot[0]/ReceiptTrail[5]/ReceiptStatusTrail[0]/SectionNode[1]/LayoutAnchorNode[0]/SectionNode[0]/RowNode[0]/SectionNode[1]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "ReceiptModernRoot[0]/ReceiptTrail[5]/ReceiptStatusTrail[0]/SectionNode[1]/LayoutAnchorNode[0]/SectionNode[0]/RowNode[0]/SectionNode[1]", + "childIndex" : 0, + "depth" : 9, + "layer" : 9, + "computedX" : 63.941, + "computedY" : 328.16, + "placementX" : 63.941, + "placementY" : 328.16, + "placementWidth" : 48.34, + "placementHeight" : 9.25, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 48.34, + "contentHeight" : 9.25, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ReceiptModernRoot[0]/ReceiptTrail[5]/ReceiptStatusTrail[0]/SectionNode[1]/LayoutAnchorNode[0]/SectionNode[0]/RowNode[0]/SectionNode[1]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "ReceiptModernRoot[0]/ReceiptTrail[5]/ReceiptStatusTrail[0]/SectionNode[1]/LayoutAnchorNode[0]/SectionNode[0]/RowNode[0]/SectionNode[1]", + "childIndex" : 1, + "depth" : 9, + "layer" : 9, + "computedX" : 63.941, + "computedY" : 318.02, + "placementX" : 63.941, + "placementY" : 318.02, + "placementWidth" : 275.387, + "placementHeight" : 8.14, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 275.387, + "contentHeight" : 8.14, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ReceiptModernRoot[0]/ReceiptTrail[5]/ReceiptStatusTrail[0]/SectionNode[1]/LayoutAnchorNode[1]", + "entityName" : null, + "entityKind" : "LayoutAnchorNode", + "parentPath" : "ReceiptModernRoot[0]/ReceiptTrail[5]/ReceiptStatusTrail[0]/SectionNode[1]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 34.0, + "computedY" : 286.63, + "placementX" : 34.0, + "placementY" : 286.63, + "placementWidth" : 527.276, + "placementHeight" : 25.39, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 527.276, + "contentHeight" : 25.39, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ReceiptModernRoot[0]/ReceiptTrail[5]/ReceiptStatusTrail[0]/SectionNode[1]/LayoutAnchorNode[1]/SectionNode[0]", + "entityName" : null, + "entityKind" : "SectionNode", + "parentPath" : "ReceiptModernRoot[0]/ReceiptTrail[5]/ReceiptStatusTrail[0]/SectionNode[1]/LayoutAnchorNode[1]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 34.0, + "computedY" : 286.63, + "placementX" : 34.0, + "placementY" : 286.63, + "placementWidth" : 527.276, + "placementHeight" : 25.39, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 527.276, + "contentHeight" : 25.39, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 6.0, + "left" : 12.0 + } + }, { + "path" : "ReceiptModernRoot[0]/ReceiptTrail[5]/ReceiptStatusTrail[0]/SectionNode[1]/LayoutAnchorNode[1]/SectionNode[0]/RowNode[0]", + "entityName" : null, + "entityKind" : "RowNode", + "parentPath" : "ReceiptModernRoot[0]/ReceiptTrail[5]/ReceiptStatusTrail[0]/SectionNode[1]/LayoutAnchorNode[1]/SectionNode[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 46.0, + "computedY" : 292.63, + "placementX" : 46.0, + "placementY" : 292.63, + "placementWidth" : 515.276, + "placementHeight" : 19.39, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 515.276, + "contentHeight" : 19.39, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ReceiptModernRoot[0]/ReceiptTrail[5]/ReceiptStatusTrail[0]/SectionNode[1]/LayoutAnchorNode[1]/SectionNode[0]/RowNode[0]/SectionNode[0]", + "entityName" : null, + "entityKind" : "SectionNode", + "parentPath" : "ReceiptModernRoot[0]/ReceiptTrail[5]/ReceiptStatusTrail[0]/SectionNode[1]/LayoutAnchorNode[1]/SectionNode[0]/RowNode[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 46.0, + "computedY" : 305.02, + "placementX" : 46.0, + "placementY" : 305.02, + "placementWidth" : 10.941, + "placementHeight" : 7.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 10.941, + "contentHeight" : 7.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ReceiptModernRoot[0]/ReceiptTrail[5]/ReceiptStatusTrail[0]/SectionNode[1]/LayoutAnchorNode[1]/SectionNode[0]/RowNode[0]/SectionNode[0]/Align[0]", + "entityName" : null, + "entityKind" : "Align", + "parentPath" : "ReceiptModernRoot[0]/ReceiptTrail[5]/ReceiptStatusTrail[0]/SectionNode[1]/LayoutAnchorNode[1]/SectionNode[0]/RowNode[0]/SectionNode[0]", + "childIndex" : 0, + "depth" : 9, + "layer" : 9, + "computedX" : 46.0, + "computedY" : 305.02, + "placementX" : 46.0, + "placementY" : 305.02, + "placementWidth" : 10.941, + "placementHeight" : 7.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 10.941, + "contentHeight" : 7.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ReceiptModernRoot[0]/ReceiptTrail[5]/ReceiptStatusTrail[0]/SectionNode[1]/LayoutAnchorNode[1]/SectionNode[0]/RowNode[0]/SectionNode[0]/Align[0]/LayoutAnchorNode[0]", + "entityName" : null, + "entityKind" : "LayoutAnchorNode", + "parentPath" : "ReceiptModernRoot[0]/ReceiptTrail[5]/ReceiptStatusTrail[0]/SectionNode[1]/LayoutAnchorNode[1]/SectionNode[0]/RowNode[0]/SectionNode[0]/Align[0]", + "childIndex" : 0, + "depth" : 10, + "layer" : 10, + "computedX" : 46.0, + "computedY" : 305.02, + "placementX" : 46.0, + "placementY" : 305.02, + "placementWidth" : 7.0, + "placementHeight" : 7.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 7.0, + "contentHeight" : 7.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ReceiptModernRoot[0]/ReceiptTrail[5]/ReceiptStatusTrail[0]/SectionNode[1]/LayoutAnchorNode[1]/SectionNode[0]/RowNode[0]/SectionNode[0]/Align[0]/LayoutAnchorNode[0]/marker[0]", + "entityName" : "marker", + "entityKind" : "CanvasLayerNode", + "parentPath" : "ReceiptModernRoot[0]/ReceiptTrail[5]/ReceiptStatusTrail[0]/SectionNode[1]/LayoutAnchorNode[1]/SectionNode[0]/RowNode[0]/SectionNode[0]/Align[0]/LayoutAnchorNode[0]", + "childIndex" : 0, + "depth" : 11, + "layer" : 11, + "computedX" : 46.0, + "computedY" : 305.02, + "placementX" : 46.0, + "placementY" : 305.02, + "placementWidth" : 7.0, + "placementHeight" : 7.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 7.0, + "contentHeight" : 7.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ReceiptModernRoot[0]/ReceiptTrail[5]/ReceiptStatusTrail[0]/SectionNode[1]/LayoutAnchorNode[1]/SectionNode[0]/RowNode[0]/SectionNode[0]/Align[0]/LayoutAnchorNode[0]/marker[0]/SectionNode[0]", + "entityName" : null, + "entityKind" : "SectionNode", + "parentPath" : "ReceiptModernRoot[0]/ReceiptTrail[5]/ReceiptStatusTrail[0]/SectionNode[1]/LayoutAnchorNode[1]/SectionNode[0]/RowNode[0]/SectionNode[0]/Align[0]/LayoutAnchorNode[0]/marker[0]", + "childIndex" : 0, + "depth" : 12, + "layer" : 12, + "computedX" : 46.0, + "computedY" : 305.02, + "placementX" : 46.0, + "placementY" : 305.02, + "placementWidth" : 7.0, + "placementHeight" : 7.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 7.0, + "contentHeight" : 7.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ReceiptModernRoot[0]/ReceiptTrail[5]/ReceiptStatusTrail[0]/SectionNode[1]/LayoutAnchorNode[1]/SectionNode[0]/RowNode[0]/SectionNode[0]/Align[0]/LayoutAnchorNode[0]/marker[0]/SectionNode[0]/EllipseNode[0]", + "entityName" : null, + "entityKind" : "EllipseNode", + "parentPath" : "ReceiptModernRoot[0]/ReceiptTrail[5]/ReceiptStatusTrail[0]/SectionNode[1]/LayoutAnchorNode[1]/SectionNode[0]/RowNode[0]/SectionNode[0]/Align[0]/LayoutAnchorNode[0]/marker[0]/SectionNode[0]", + "childIndex" : 0, + "depth" : 13, + "layer" : 13, + "computedX" : 46.0, + "computedY" : 305.02, + "placementX" : 46.0, + "placementY" : 305.02, + "placementWidth" : 7.0, + "placementHeight" : 7.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 7.0, + "contentHeight" : 7.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ReceiptModernRoot[0]/ReceiptTrail[5]/ReceiptStatusTrail[0]/SectionNode[1]/LayoutAnchorNode[1]/SectionNode[0]/RowNode[0]/SectionNode[1]", + "entityName" : null, + "entityKind" : "SectionNode", + "parentPath" : "ReceiptModernRoot[0]/ReceiptTrail[5]/ReceiptStatusTrail[0]/SectionNode[1]/LayoutAnchorNode[1]/SectionNode[0]/RowNode[0]", + "childIndex" : 1, + "depth" : 8, + "layer" : 8, + "computedX" : 63.941, + "computedY" : 292.63, + "placementX" : 63.941, + "placementY" : 292.63, + "placementWidth" : 224.497, + "placementHeight" : 19.39, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 224.497, + "contentHeight" : 19.39, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ReceiptModernRoot[0]/ReceiptTrail[5]/ReceiptStatusTrail[0]/SectionNode[1]/LayoutAnchorNode[1]/SectionNode[0]/RowNode[0]/SectionNode[1]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "ReceiptModernRoot[0]/ReceiptTrail[5]/ReceiptStatusTrail[0]/SectionNode[1]/LayoutAnchorNode[1]/SectionNode[0]/RowNode[0]/SectionNode[1]", + "childIndex" : 0, + "depth" : 9, + "layer" : 9, + "computedX" : 63.941, + "computedY" : 302.77, + "placementX" : 63.941, + "placementY" : 302.77, + "placementWidth" : 50.02, + "placementHeight" : 9.25, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 50.02, + "contentHeight" : 9.25, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ReceiptModernRoot[0]/ReceiptTrail[5]/ReceiptStatusTrail[0]/SectionNode[1]/LayoutAnchorNode[1]/SectionNode[0]/RowNode[0]/SectionNode[1]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "ReceiptModernRoot[0]/ReceiptTrail[5]/ReceiptStatusTrail[0]/SectionNode[1]/LayoutAnchorNode[1]/SectionNode[0]/RowNode[0]/SectionNode[1]", + "childIndex" : 1, + "depth" : 9, + "layer" : 9, + "computedX" : 63.941, + "computedY" : 292.63, + "placementX" : 63.941, + "placementY" : 292.63, + "placementWidth" : 224.497, + "placementHeight" : 8.14, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 224.497, + "contentHeight" : 8.14, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ReceiptModernRoot[0]/ReceiptTrail[5]/ReceiptStatusTrail[0]/SectionNode[1]/LayoutAnchorNode[2]", + "entityName" : null, + "entityKind" : "LayoutAnchorNode", + "parentPath" : "ReceiptModernRoot[0]/ReceiptTrail[5]/ReceiptStatusTrail[0]/SectionNode[1]", + "childIndex" : 2, + "depth" : 5, + "layer" : 5, + "computedX" : 34.0, + "computedY" : 267.24, + "placementX" : 34.0, + "placementY" : 267.24, + "placementWidth" : 527.276, + "placementHeight" : 19.39, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 527.276, + "contentHeight" : 19.39, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ReceiptModernRoot[0]/ReceiptTrail[5]/ReceiptStatusTrail[0]/SectionNode[1]/LayoutAnchorNode[2]/SectionNode[0]", + "entityName" : null, + "entityKind" : "SectionNode", + "parentPath" : "ReceiptModernRoot[0]/ReceiptTrail[5]/ReceiptStatusTrail[0]/SectionNode[1]/LayoutAnchorNode[2]", + "childIndex" : 0, + "depth" : 6, + "layer" : 6, + "computedX" : 34.0, + "computedY" : 267.24, + "placementX" : 34.0, + "placementY" : 267.24, + "placementWidth" : 527.276, + "placementHeight" : 19.39, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 527.276, + "contentHeight" : 19.39, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 12.0 + } + }, { + "path" : "ReceiptModernRoot[0]/ReceiptTrail[5]/ReceiptStatusTrail[0]/SectionNode[1]/LayoutAnchorNode[2]/SectionNode[0]/RowNode[0]", + "entityName" : null, + "entityKind" : "RowNode", + "parentPath" : "ReceiptModernRoot[0]/ReceiptTrail[5]/ReceiptStatusTrail[0]/SectionNode[1]/LayoutAnchorNode[2]/SectionNode[0]", + "childIndex" : 0, + "depth" : 7, + "layer" : 7, + "computedX" : 46.0, + "computedY" : 267.24, + "placementX" : 46.0, + "placementY" : 267.24, + "placementWidth" : 515.276, + "placementHeight" : 19.39, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 515.276, + "contentHeight" : 19.39, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ReceiptModernRoot[0]/ReceiptTrail[5]/ReceiptStatusTrail[0]/SectionNode[1]/LayoutAnchorNode[2]/SectionNode[0]/RowNode[0]/SectionNode[0]", + "entityName" : null, + "entityKind" : "SectionNode", + "parentPath" : "ReceiptModernRoot[0]/ReceiptTrail[5]/ReceiptStatusTrail[0]/SectionNode[1]/LayoutAnchorNode[2]/SectionNode[0]/RowNode[0]", + "childIndex" : 0, + "depth" : 8, + "layer" : 8, + "computedX" : 46.0, + "computedY" : 279.63, + "placementX" : 46.0, + "placementY" : 279.63, + "placementWidth" : 10.941, + "placementHeight" : 7.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 10.941, + "contentHeight" : 7.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ReceiptModernRoot[0]/ReceiptTrail[5]/ReceiptStatusTrail[0]/SectionNode[1]/LayoutAnchorNode[2]/SectionNode[0]/RowNode[0]/SectionNode[0]/Align[0]", + "entityName" : null, + "entityKind" : "Align", + "parentPath" : "ReceiptModernRoot[0]/ReceiptTrail[5]/ReceiptStatusTrail[0]/SectionNode[1]/LayoutAnchorNode[2]/SectionNode[0]/RowNode[0]/SectionNode[0]", + "childIndex" : 0, + "depth" : 9, + "layer" : 9, + "computedX" : 46.0, + "computedY" : 279.63, + "placementX" : 46.0, + "placementY" : 279.63, + "placementWidth" : 10.941, + "placementHeight" : 7.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 10.941, + "contentHeight" : 7.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ReceiptModernRoot[0]/ReceiptTrail[5]/ReceiptStatusTrail[0]/SectionNode[1]/LayoutAnchorNode[2]/SectionNode[0]/RowNode[0]/SectionNode[0]/Align[0]/LayoutAnchorNode[0]", + "entityName" : null, + "entityKind" : "LayoutAnchorNode", + "parentPath" : "ReceiptModernRoot[0]/ReceiptTrail[5]/ReceiptStatusTrail[0]/SectionNode[1]/LayoutAnchorNode[2]/SectionNode[0]/RowNode[0]/SectionNode[0]/Align[0]", + "childIndex" : 0, + "depth" : 10, + "layer" : 10, + "computedX" : 46.0, + "computedY" : 279.63, + "placementX" : 46.0, + "placementY" : 279.63, + "placementWidth" : 7.0, + "placementHeight" : 7.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 7.0, + "contentHeight" : 7.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ReceiptModernRoot[0]/ReceiptTrail[5]/ReceiptStatusTrail[0]/SectionNode[1]/LayoutAnchorNode[2]/SectionNode[0]/RowNode[0]/SectionNode[0]/Align[0]/LayoutAnchorNode[0]/marker[0]", + "entityName" : "marker", + "entityKind" : "CanvasLayerNode", + "parentPath" : "ReceiptModernRoot[0]/ReceiptTrail[5]/ReceiptStatusTrail[0]/SectionNode[1]/LayoutAnchorNode[2]/SectionNode[0]/RowNode[0]/SectionNode[0]/Align[0]/LayoutAnchorNode[0]", + "childIndex" : 0, + "depth" : 11, + "layer" : 11, + "computedX" : 46.0, + "computedY" : 279.63, + "placementX" : 46.0, + "placementY" : 279.63, + "placementWidth" : 7.0, + "placementHeight" : 7.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 7.0, + "contentHeight" : 7.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ReceiptModernRoot[0]/ReceiptTrail[5]/ReceiptStatusTrail[0]/SectionNode[1]/LayoutAnchorNode[2]/SectionNode[0]/RowNode[0]/SectionNode[0]/Align[0]/LayoutAnchorNode[0]/marker[0]/SectionNode[0]", + "entityName" : null, + "entityKind" : "SectionNode", + "parentPath" : "ReceiptModernRoot[0]/ReceiptTrail[5]/ReceiptStatusTrail[0]/SectionNode[1]/LayoutAnchorNode[2]/SectionNode[0]/RowNode[0]/SectionNode[0]/Align[0]/LayoutAnchorNode[0]/marker[0]", + "childIndex" : 0, + "depth" : 12, + "layer" : 12, + "computedX" : 46.0, + "computedY" : 279.63, + "placementX" : 46.0, + "placementY" : 279.63, + "placementWidth" : 7.0, + "placementHeight" : 7.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 7.0, + "contentHeight" : 7.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ReceiptModernRoot[0]/ReceiptTrail[5]/ReceiptStatusTrail[0]/SectionNode[1]/LayoutAnchorNode[2]/SectionNode[0]/RowNode[0]/SectionNode[0]/Align[0]/LayoutAnchorNode[0]/marker[0]/SectionNode[0]/EllipseNode[0]", + "entityName" : null, + "entityKind" : "EllipseNode", + "parentPath" : "ReceiptModernRoot[0]/ReceiptTrail[5]/ReceiptStatusTrail[0]/SectionNode[1]/LayoutAnchorNode[2]/SectionNode[0]/RowNode[0]/SectionNode[0]/Align[0]/LayoutAnchorNode[0]/marker[0]/SectionNode[0]", + "childIndex" : 0, + "depth" : 13, + "layer" : 13, + "computedX" : 46.0, + "computedY" : 279.63, + "placementX" : 46.0, + "placementY" : 279.63, + "placementWidth" : 7.0, + "placementHeight" : 7.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 7.0, + "contentHeight" : 7.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ReceiptModernRoot[0]/ReceiptTrail[5]/ReceiptStatusTrail[0]/SectionNode[1]/LayoutAnchorNode[2]/SectionNode[0]/RowNode[0]/SectionNode[1]", + "entityName" : null, + "entityKind" : "SectionNode", + "parentPath" : "ReceiptModernRoot[0]/ReceiptTrail[5]/ReceiptStatusTrail[0]/SectionNode[1]/LayoutAnchorNode[2]/SectionNode[0]/RowNode[0]", + "childIndex" : 1, + "depth" : 8, + "layer" : 8, + "computedX" : 63.941, + "computedY" : 267.24, + "placementX" : 63.941, + "placementY" : 267.24, + "placementWidth" : 306.214, + "placementHeight" : 19.39, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 306.214, + "contentHeight" : 19.39, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ReceiptModernRoot[0]/ReceiptTrail[5]/ReceiptStatusTrail[0]/SectionNode[1]/LayoutAnchorNode[2]/SectionNode[0]/RowNode[0]/SectionNode[1]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "ReceiptModernRoot[0]/ReceiptTrail[5]/ReceiptStatusTrail[0]/SectionNode[1]/LayoutAnchorNode[2]/SectionNode[0]/RowNode[0]/SectionNode[1]", + "childIndex" : 0, + "depth" : 9, + "layer" : 9, + "computedX" : 63.941, + "computedY" : 277.38, + "placementX" : 63.941, + "placementY" : 277.38, + "placementWidth" : 33.34, + "placementHeight" : 9.25, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 33.34, + "contentHeight" : 9.25, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ReceiptModernRoot[0]/ReceiptTrail[5]/ReceiptStatusTrail[0]/SectionNode[1]/LayoutAnchorNode[2]/SectionNode[0]/RowNode[0]/SectionNode[1]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "ReceiptModernRoot[0]/ReceiptTrail[5]/ReceiptStatusTrail[0]/SectionNode[1]/LayoutAnchorNode[2]/SectionNode[0]/RowNode[0]/SectionNode[1]", + "childIndex" : 1, + "depth" : 9, + "layer" : 9, + "computedX" : 63.941, + "computedY" : 267.24, + "placementX" : 63.941, + "placementY" : 267.24, + "placementWidth" : 306.214, + "placementHeight" : 8.14, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 306.214, + "contentHeight" : 8.14, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ReceiptModernRoot[0]/ReceiptNotes[6]", + "entityName" : "ReceiptNotes", + "entityKind" : "SectionNode", + "parentPath" : "ReceiptModernRoot[0]", + "childIndex" : 6, + "depth" : 2, + "layer" : 2, + "computedX" : 34.0, + "computedY" : 223.96, + "placementX" : 34.0, + "placementY" : 223.96, + "placementWidth" : 259.843, + "placementHeight" : 27.28, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 259.843, + "contentHeight" : 27.28, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ReceiptModernRoot[0]/ReceiptNotes[6]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "ReceiptModernRoot[0]/ReceiptNotes[6]", + "childIndex" : 0, + "depth" : 3, + "layer" : 3, + "computedX" : 34.0, + "computedY" : 243.84, + "placementX" : 34.0, + "placementY" : 243.84, + "placementWidth" : 34.76, + "placementHeight" : 7.4, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 34.76, + "contentHeight" : 7.4, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 6.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ReceiptModernRoot[0]/ReceiptNotes[6]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "ReceiptModernRoot[0]/ReceiptNotes[6]", + "childIndex" : 1, + "depth" : 3, + "layer" : 3, + "computedX" : 34.0, + "computedY" : 223.96, + "placementX" : 34.0, + "placementY" : 223.96, + "placementWidth" : 259.843, + "placementHeight" : 8.88, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 259.843, + "contentHeight" : 8.88, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ReceiptModernRoot[0]/ReceiptFooterPin[7]", + "entityName" : "ReceiptFooterPin", + "entityKind" : "SpacerNode", + "parentPath" : "ReceiptModernRoot[0]", + "childIndex" : 7, + "depth" : 2, + "layer" : 2, + "computedX" : 34.0, + "computedY" : 115.5, + "placementX" : 34.0, + "placementY" : 115.5, + "placementWidth" : 0.0, + "placementHeight" : 92.46, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 0.0, + "contentHeight" : 92.46, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ReceiptModernRoot[0]/ReceiptFooter[8]", + "entityName" : "ReceiptFooter", + "entityKind" : "SectionNode", + "parentPath" : "ReceiptModernRoot[0]", + "childIndex" : 8, + "depth" : 2, + "layer" : 2, + "computedX" : 34.0, + "computedY" : 34.5, + "placementX" : 34.0, + "placementY" : 34.5, + "placementWidth" : 527.276, + "placementHeight" : 65.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 527.276, + "contentHeight" : 65.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ReceiptModernRoot[0]/ReceiptFooter[8]/LineNode[0]", + "entityName" : null, + "entityKind" : "LineNode", + "parentPath" : "ReceiptModernRoot[0]/ReceiptFooter[8]", + "childIndex" : 0, + "depth" : 3, + "layer" : 3, + "computedX" : 34.0, + "computedY" : 92.5, + "placementX" : 34.0, + "placementY" : 92.5, + "placementWidth" : 527.276, + "placementHeight" : 1.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 527.276, + "contentHeight" : 1.0, + "margin" : { + "top" : 6.0, + "right" : 0.0, + "bottom" : 6.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ReceiptModernRoot[0]/ReceiptFooter[8]/ReceiptFooterRow[1]", + "entityName" : "ReceiptFooterRow", + "entityKind" : "RowNode", + "parentPath" : "ReceiptModernRoot[0]/ReceiptFooter[8]", + "childIndex" : 1, + "depth" : 3, + "layer" : 3, + "computedX" : 34.0, + "computedY" : 34.5, + "placementX" : 34.0, + "placementY" : 34.5, + "placementWidth" : 527.276, + "placementHeight" : 52.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 527.276, + "contentHeight" : 52.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ReceiptModernRoot[0]/ReceiptFooter[8]/ReceiptFooterRow[1]/ReceiptVerificationCode[0]", + "entityName" : "ReceiptVerificationCode", + "entityKind" : "SectionNode", + "parentPath" : "ReceiptModernRoot[0]/ReceiptFooter[8]/ReceiptFooterRow[1]", + "childIndex" : 0, + "depth" : 4, + "layer" : 4, + "computedX" : 34.0, + "computedY" : 34.5, + "placementX" : 34.0, + "placementY" : 34.5, + "placementWidth" : 52.0, + "placementHeight" : 52.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 52.0, + "contentHeight" : 52.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ReceiptModernRoot[0]/ReceiptFooter[8]/ReceiptFooterRow[1]/ReceiptVerificationCode[0]/BarcodeNode[0]", + "entityName" : null, + "entityKind" : "BarcodeNode", + "parentPath" : "ReceiptModernRoot[0]/ReceiptFooter[8]/ReceiptFooterRow[1]/ReceiptVerificationCode[0]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 34.0, + "computedY" : 34.5, + "placementX" : 34.0, + "placementY" : 34.5, + "placementWidth" : 52.0, + "placementHeight" : 52.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 52.0, + "contentHeight" : 52.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ReceiptModernRoot[0]/ReceiptFooter[8]/ReceiptFooterRow[1]/ReceiptFooterText[1]", + "entityName" : "ReceiptFooterText", + "entityKind" : "SectionNode", + "parentPath" : "ReceiptModernRoot[0]/ReceiptFooter[8]/ReceiptFooterRow[1]", + "childIndex" : 1, + "depth" : 4, + "layer" : 4, + "computedX" : 118.797, + "computedY" : 36.06, + "placementX" : 118.797, + "placementY" : 36.06, + "placementWidth" : 441.528, + "placementHeight" : 50.44, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 441.528, + "contentHeight" : 50.44, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ReceiptModernRoot[0]/ReceiptFooter[8]/ReceiptFooterRow[1]/ReceiptFooterText[1]/ParagraphNode[0]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "ReceiptModernRoot[0]/ReceiptFooter[8]/ReceiptFooterRow[1]/ReceiptFooterText[1]", + "childIndex" : 0, + "depth" : 5, + "layer" : 5, + "computedX" : 118.797, + "computedY" : 78.36, + "placementX" : 118.797, + "placementY" : 78.36, + "placementWidth" : 231.343, + "placementHeight" : 8.14, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 231.343, + "contentHeight" : 8.14, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ReceiptModernRoot[0]/ReceiptFooter[8]/ReceiptFooterRow[1]/ReceiptFooterText[1]/ParagraphNode[1]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "ReceiptModernRoot[0]/ReceiptFooter[8]/ReceiptFooterRow[1]/ReceiptFooterText[1]", + "childIndex" : 1, + "depth" : 5, + "layer" : 5, + "computedX" : 118.797, + "computedY" : 57.21, + "placementX" : 118.797, + "placementY" : 57.21, + "placementWidth" : 105.936, + "placementHeight" : 16.15, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 105.936, + "contentHeight" : 16.15, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ReceiptModernRoot[0]/ReceiptFooter[8]/ReceiptFooterRow[1]/ReceiptFooterText[1]/ParagraphNode[2]", + "entityName" : null, + "entityKind" : "ParagraphNode", + "parentPath" : "ReceiptModernRoot[0]/ReceiptFooter[8]/ReceiptFooterRow[1]/ReceiptFooterText[1]", + "childIndex" : 2, + "depth" : 5, + "layer" : 5, + "computedX" : 118.797, + "computedY" : 36.06, + "placementX" : 118.797, + "placementY" : 36.06, + "placementWidth" : 441.528, + "placementHeight" : 16.15, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 441.528, + "contentHeight" : 16.15, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + } ] +} diff --git a/qa/src/test/resources/layout-snapshots/canonical-templates/rota/cobalt_rota_layout.json b/qa/src/test/resources/layout-snapshots/canonical-templates/rota/cobalt_rota_layout.json new file mode 100644 index 000000000..86d1fb88a --- /dev/null +++ b/qa/src/test/resources/layout-snapshots/canonical-templates/rota/cobalt_rota_layout.json @@ -0,0 +1,77 @@ +{ + "formatVersion" : "2.0", + "canvas" : { + "pageWidth" : 841.89, + "pageHeight" : 595.276, + "innerWidth" : 817.054, + "innerHeight" : 545.749, + "margin" : { + "top" : 10.596, + "right" : 12.418, + "bottom" : 38.931, + "left" : 12.418 + } + }, + "totalPages" : 1, + "nodes" : [ { + "path" : "CobaltRota[0]", + "entityName" : "CobaltRota", + "entityKind" : "ContainerNode", + "parentPath" : null, + "childIndex" : 0, + "depth" : 1, + "layer" : 1, + "computedX" : 12.418, + "computedY" : 81.405, + "placementX" : 12.418, + "placementY" : 81.405, + "placementWidth" : 817.054, + "placementHeight" : 503.275, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 817.054, + "contentHeight" : 503.275, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "CobaltRota[0]/Schedule[0]", + "entityName" : "Schedule", + "entityKind" : "TableNode", + "parentPath" : "CobaltRota[0]", + "childIndex" : 0, + "depth" : 2, + "layer" : 2, + "computedX" : 12.418, + "computedY" : 81.405, + "placementX" : 12.418, + "placementY" : 81.405, + "placementWidth" : 817.054, + "placementHeight" : 503.275, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 817.054, + "contentHeight" : 503.275, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + } ] +} diff --git a/qa/src/test/resources/layout-snapshots/document/timeline_leading_column.json b/qa/src/test/resources/layout-snapshots/document/timeline_leading_column.json index 10014b031..00a162fea 100644 --- a/qa/src/test/resources/layout-snapshots/document/timeline_leading_column.json +++ b/qa/src/test/resources/layout-snapshots/document/timeline_leading_column.json @@ -231,6 +231,36 @@ "childIndex" : 1, "depth" : 6, "layer" : 6, + "computedX" : 84.0, + "computedY" : 240.0, + "placementX" : 84.0, + "placementY" : 240.0, + "placementWidth" : 0.0, + "placementHeight" : 0.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 0.0, + "contentHeight" : 0.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ContainerNode[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/RowNode[0]/SectionNode[2]", + "entityName" : null, + "entityKind" : "SectionNode", + "parentPath" : "ContainerNode[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/RowNode[0]", + "childIndex" : 2, + "depth" : 6, + "layer" : 6, "computedX" : 92.0, "computedY" : 232.0, "placementX" : 92.0, @@ -254,10 +284,10 @@ "left" : 0.0 } }, { - "path" : "ContainerNode[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/RowNode[0]/SectionNode[1]/Align[0]", + "path" : "ContainerNode[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/RowNode[0]/SectionNode[2]/Align[0]", "entityName" : null, "entityKind" : "Align", - "parentPath" : "ContainerNode[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/RowNode[0]/SectionNode[1]", + "parentPath" : "ContainerNode[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/RowNode[0]/SectionNode[2]", "childIndex" : 0, "depth" : 7, "layer" : 7, @@ -284,10 +314,10 @@ "left" : 0.0 } }, { - "path" : "ContainerNode[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/RowNode[0]/SectionNode[1]/Align[0]/LayoutAnchorNode[0]", + "path" : "ContainerNode[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/RowNode[0]/SectionNode[2]/Align[0]/LayoutAnchorNode[0]", "entityName" : null, "entityKind" : "LayoutAnchorNode", - "parentPath" : "ContainerNode[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/RowNode[0]/SectionNode[1]/Align[0]", + "parentPath" : "ContainerNode[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/RowNode[0]/SectionNode[2]/Align[0]", "childIndex" : 0, "depth" : 8, "layer" : 8, @@ -314,10 +344,10 @@ "left" : 0.0 } }, { - "path" : "ContainerNode[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/RowNode[0]/SectionNode[1]/Align[0]/LayoutAnchorNode[0]/marker[0]", + "path" : "ContainerNode[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/RowNode[0]/SectionNode[2]/Align[0]/LayoutAnchorNode[0]/marker[0]", "entityName" : "marker", "entityKind" : "CanvasLayerNode", - "parentPath" : "ContainerNode[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/RowNode[0]/SectionNode[1]/Align[0]/LayoutAnchorNode[0]", + "parentPath" : "ContainerNode[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/RowNode[0]/SectionNode[2]/Align[0]/LayoutAnchorNode[0]", "childIndex" : 0, "depth" : 9, "layer" : 9, @@ -344,10 +374,10 @@ "left" : 0.0 } }, { - "path" : "ContainerNode[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/RowNode[0]/SectionNode[1]/Align[0]/LayoutAnchorNode[0]/marker[0]/SectionNode[0]", + "path" : "ContainerNode[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/RowNode[0]/SectionNode[2]/Align[0]/LayoutAnchorNode[0]/marker[0]/SectionNode[0]", "entityName" : null, "entityKind" : "SectionNode", - "parentPath" : "ContainerNode[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/RowNode[0]/SectionNode[1]/Align[0]/LayoutAnchorNode[0]/marker[0]", + "parentPath" : "ContainerNode[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/RowNode[0]/SectionNode[2]/Align[0]/LayoutAnchorNode[0]/marker[0]", "childIndex" : 0, "depth" : 10, "layer" : 10, @@ -374,10 +404,10 @@ "left" : 0.0 } }, { - "path" : "ContainerNode[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/RowNode[0]/SectionNode[1]/Align[0]/LayoutAnchorNode[0]/marker[0]/SectionNode[0]/EllipseNode[0]", + "path" : "ContainerNode[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/RowNode[0]/SectionNode[2]/Align[0]/LayoutAnchorNode[0]/marker[0]/SectionNode[0]/EllipseNode[0]", "entityName" : null, "entityKind" : "EllipseNode", - "parentPath" : "ContainerNode[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/RowNode[0]/SectionNode[1]/Align[0]/LayoutAnchorNode[0]/marker[0]/SectionNode[0]", + "parentPath" : "ContainerNode[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/RowNode[0]/SectionNode[2]/Align[0]/LayoutAnchorNode[0]/marker[0]/SectionNode[0]", "childIndex" : 0, "depth" : 11, "layer" : 11, @@ -404,11 +434,41 @@ "left" : 0.0 } }, { - "path" : "ContainerNode[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/RowNode[0]/SectionNode[2]", + "path" : "ContainerNode[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/RowNode[0]/SectionNode[3]", "entityName" : null, "entityKind" : "SectionNode", "parentPath" : "ContainerNode[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/RowNode[0]", - "childIndex" : 2, + "childIndex" : 3, + "depth" : 6, + "layer" : 6, + "computedX" : 113.818, + "computedY" : 240.0, + "placementX" : 113.818, + "placementY" : 240.0, + "placementWidth" : 0.0, + "placementHeight" : 0.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 0.0, + "contentHeight" : 0.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ContainerNode[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/RowNode[0]/SectionNode[4]", + "entityName" : null, + "entityKind" : "SectionNode", + "parentPath" : "ContainerNode[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/RowNode[0]", + "childIndex" : 4, "depth" : 6, "layer" : 6, "computedX" : 121.818, @@ -434,10 +494,10 @@ "left" : 0.0 } }, { - "path" : "ContainerNode[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/RowNode[0]/SectionNode[2]/ParagraphNode[0]", + "path" : "ContainerNode[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/RowNode[0]/SectionNode[4]/ParagraphNode[0]", "entityName" : null, "entityKind" : "ParagraphNode", - "parentPath" : "ContainerNode[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/RowNode[0]/SectionNode[2]", + "parentPath" : "ContainerNode[0]/SectionNode[0]/LayoutAnchorNode[0]/SectionNode[0]/RowNode[0]/SectionNode[4]", "childIndex" : 0, "depth" : 7, "layer" : 7, @@ -651,6 +711,36 @@ "childIndex" : 1, "depth" : 6, "layer" : 6, + "computedX" : 84.0, + "computedY" : 200.262, + "placementX" : 84.0, + "placementY" : 200.262, + "placementWidth" : 0.0, + "placementHeight" : 0.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 0.0, + "contentHeight" : 0.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ContainerNode[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/RowNode[0]/SectionNode[2]", + "entityName" : null, + "entityKind" : "SectionNode", + "parentPath" : "ContainerNode[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/RowNode[0]", + "childIndex" : 2, + "depth" : 6, + "layer" : 6, "computedX" : 92.0, "computedY" : 192.262, "placementX" : 92.0, @@ -674,10 +764,10 @@ "left" : 0.0 } }, { - "path" : "ContainerNode[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/RowNode[0]/SectionNode[1]/Align[0]", + "path" : "ContainerNode[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/RowNode[0]/SectionNode[2]/Align[0]", "entityName" : null, "entityKind" : "Align", - "parentPath" : "ContainerNode[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/RowNode[0]/SectionNode[1]", + "parentPath" : "ContainerNode[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/RowNode[0]/SectionNode[2]", "childIndex" : 0, "depth" : 7, "layer" : 7, @@ -704,10 +794,10 @@ "left" : 0.0 } }, { - "path" : "ContainerNode[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/RowNode[0]/SectionNode[1]/Align[0]/LayoutAnchorNode[0]", + "path" : "ContainerNode[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/RowNode[0]/SectionNode[2]/Align[0]/LayoutAnchorNode[0]", "entityName" : null, "entityKind" : "LayoutAnchorNode", - "parentPath" : "ContainerNode[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/RowNode[0]/SectionNode[1]/Align[0]", + "parentPath" : "ContainerNode[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/RowNode[0]/SectionNode[2]/Align[0]", "childIndex" : 0, "depth" : 8, "layer" : 8, @@ -734,10 +824,10 @@ "left" : 0.0 } }, { - "path" : "ContainerNode[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/RowNode[0]/SectionNode[1]/Align[0]/LayoutAnchorNode[0]/marker[0]", + "path" : "ContainerNode[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/RowNode[0]/SectionNode[2]/Align[0]/LayoutAnchorNode[0]/marker[0]", "entityName" : "marker", "entityKind" : "CanvasLayerNode", - "parentPath" : "ContainerNode[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/RowNode[0]/SectionNode[1]/Align[0]/LayoutAnchorNode[0]", + "parentPath" : "ContainerNode[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/RowNode[0]/SectionNode[2]/Align[0]/LayoutAnchorNode[0]", "childIndex" : 0, "depth" : 9, "layer" : 9, @@ -764,10 +854,10 @@ "left" : 0.0 } }, { - "path" : "ContainerNode[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/RowNode[0]/SectionNode[1]/Align[0]/LayoutAnchorNode[0]/marker[0]/SectionNode[0]", + "path" : "ContainerNode[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/RowNode[0]/SectionNode[2]/Align[0]/LayoutAnchorNode[0]/marker[0]/SectionNode[0]", "entityName" : null, "entityKind" : "SectionNode", - "parentPath" : "ContainerNode[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/RowNode[0]/SectionNode[1]/Align[0]/LayoutAnchorNode[0]/marker[0]", + "parentPath" : "ContainerNode[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/RowNode[0]/SectionNode[2]/Align[0]/LayoutAnchorNode[0]/marker[0]", "childIndex" : 0, "depth" : 10, "layer" : 10, @@ -794,10 +884,10 @@ "left" : 0.0 } }, { - "path" : "ContainerNode[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/RowNode[0]/SectionNode[1]/Align[0]/LayoutAnchorNode[0]/marker[0]/SectionNode[0]/EllipseNode[0]", + "path" : "ContainerNode[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/RowNode[0]/SectionNode[2]/Align[0]/LayoutAnchorNode[0]/marker[0]/SectionNode[0]/EllipseNode[0]", "entityName" : null, "entityKind" : "EllipseNode", - "parentPath" : "ContainerNode[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/RowNode[0]/SectionNode[1]/Align[0]/LayoutAnchorNode[0]/marker[0]/SectionNode[0]", + "parentPath" : "ContainerNode[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/RowNode[0]/SectionNode[2]/Align[0]/LayoutAnchorNode[0]/marker[0]/SectionNode[0]", "childIndex" : 0, "depth" : 11, "layer" : 11, @@ -824,11 +914,41 @@ "left" : 0.0 } }, { - "path" : "ContainerNode[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/RowNode[0]/SectionNode[2]", + "path" : "ContainerNode[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/RowNode[0]/SectionNode[3]", "entityName" : null, "entityKind" : "SectionNode", "parentPath" : "ContainerNode[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/RowNode[0]", - "childIndex" : 2, + "childIndex" : 3, + "depth" : 6, + "layer" : 6, + "computedX" : 113.818, + "computedY" : 200.262, + "placementX" : 113.818, + "placementY" : 200.262, + "placementWidth" : 0.0, + "placementHeight" : 0.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 0.0, + "contentHeight" : 0.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ContainerNode[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/RowNode[0]/SectionNode[4]", + "entityName" : null, + "entityKind" : "SectionNode", + "parentPath" : "ContainerNode[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/RowNode[0]", + "childIndex" : 4, "depth" : 6, "layer" : 6, "computedX" : 121.818, @@ -854,10 +974,10 @@ "left" : 0.0 } }, { - "path" : "ContainerNode[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/RowNode[0]/SectionNode[2]/ParagraphNode[0]", + "path" : "ContainerNode[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/RowNode[0]/SectionNode[4]/ParagraphNode[0]", "entityName" : null, "entityKind" : "ParagraphNode", - "parentPath" : "ContainerNode[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/RowNode[0]/SectionNode[2]", + "parentPath" : "ContainerNode[0]/SectionNode[0]/LayoutAnchorNode[1]/SectionNode[0]/RowNode[0]/SectionNode[4]", "childIndex" : 0, "depth" : 7, "layer" : 7, @@ -1071,6 +1191,36 @@ "childIndex" : 1, "depth" : 6, "layer" : 6, + "computedX" : 84.0, + "computedY" : 134.625, + "placementX" : 84.0, + "placementY" : 134.625, + "placementWidth" : 0.0, + "placementHeight" : 0.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 0.0, + "contentHeight" : 0.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ContainerNode[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/RowNode[0]/SectionNode[2]", + "entityName" : null, + "entityKind" : "SectionNode", + "parentPath" : "ContainerNode[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/RowNode[0]", + "childIndex" : 2, + "depth" : 6, + "layer" : 6, "computedX" : 92.0, "computedY" : 126.625, "placementX" : 92.0, @@ -1094,10 +1244,10 @@ "left" : 0.0 } }, { - "path" : "ContainerNode[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/RowNode[0]/SectionNode[1]/Align[0]", + "path" : "ContainerNode[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/RowNode[0]/SectionNode[2]/Align[0]", "entityName" : null, "entityKind" : "Align", - "parentPath" : "ContainerNode[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/RowNode[0]/SectionNode[1]", + "parentPath" : "ContainerNode[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/RowNode[0]/SectionNode[2]", "childIndex" : 0, "depth" : 7, "layer" : 7, @@ -1124,10 +1274,10 @@ "left" : 0.0 } }, { - "path" : "ContainerNode[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/RowNode[0]/SectionNode[1]/Align[0]/LayoutAnchorNode[0]", + "path" : "ContainerNode[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/RowNode[0]/SectionNode[2]/Align[0]/LayoutAnchorNode[0]", "entityName" : null, "entityKind" : "LayoutAnchorNode", - "parentPath" : "ContainerNode[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/RowNode[0]/SectionNode[1]/Align[0]", + "parentPath" : "ContainerNode[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/RowNode[0]/SectionNode[2]/Align[0]", "childIndex" : 0, "depth" : 8, "layer" : 8, @@ -1154,10 +1304,10 @@ "left" : 0.0 } }, { - "path" : "ContainerNode[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/RowNode[0]/SectionNode[1]/Align[0]/LayoutAnchorNode[0]/marker[0]", + "path" : "ContainerNode[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/RowNode[0]/SectionNode[2]/Align[0]/LayoutAnchorNode[0]/marker[0]", "entityName" : "marker", "entityKind" : "CanvasLayerNode", - "parentPath" : "ContainerNode[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/RowNode[0]/SectionNode[1]/Align[0]/LayoutAnchorNode[0]", + "parentPath" : "ContainerNode[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/RowNode[0]/SectionNode[2]/Align[0]/LayoutAnchorNode[0]", "childIndex" : 0, "depth" : 9, "layer" : 9, @@ -1184,10 +1334,10 @@ "left" : 0.0 } }, { - "path" : "ContainerNode[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/RowNode[0]/SectionNode[1]/Align[0]/LayoutAnchorNode[0]/marker[0]/SectionNode[0]", + "path" : "ContainerNode[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/RowNode[0]/SectionNode[2]/Align[0]/LayoutAnchorNode[0]/marker[0]/SectionNode[0]", "entityName" : null, "entityKind" : "SectionNode", - "parentPath" : "ContainerNode[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/RowNode[0]/SectionNode[1]/Align[0]/LayoutAnchorNode[0]/marker[0]", + "parentPath" : "ContainerNode[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/RowNode[0]/SectionNode[2]/Align[0]/LayoutAnchorNode[0]/marker[0]", "childIndex" : 0, "depth" : 10, "layer" : 10, @@ -1214,10 +1364,10 @@ "left" : 0.0 } }, { - "path" : "ContainerNode[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/RowNode[0]/SectionNode[1]/Align[0]/LayoutAnchorNode[0]/marker[0]/SectionNode[0]/TimelineMarkerSquare[0]", + "path" : "ContainerNode[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/RowNode[0]/SectionNode[2]/Align[0]/LayoutAnchorNode[0]/marker[0]/SectionNode[0]/TimelineMarkerSquare[0]", "entityName" : "TimelineMarkerSquare", "entityKind" : "ShapeNode", - "parentPath" : "ContainerNode[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/RowNode[0]/SectionNode[1]/Align[0]/LayoutAnchorNode[0]/marker[0]/SectionNode[0]", + "parentPath" : "ContainerNode[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/RowNode[0]/SectionNode[2]/Align[0]/LayoutAnchorNode[0]/marker[0]/SectionNode[0]", "childIndex" : 0, "depth" : 11, "layer" : 11, @@ -1244,11 +1394,41 @@ "left" : 0.0 } }, { - "path" : "ContainerNode[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/RowNode[0]/SectionNode[2]", + "path" : "ContainerNode[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/RowNode[0]/SectionNode[3]", "entityName" : null, "entityKind" : "SectionNode", "parentPath" : "ContainerNode[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/RowNode[0]", - "childIndex" : 2, + "childIndex" : 3, + "depth" : 6, + "layer" : 6, + "computedX" : 113.818, + "computedY" : 134.625, + "placementX" : 113.818, + "placementY" : 134.625, + "placementWidth" : 0.0, + "placementHeight" : 0.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 0.0, + "contentHeight" : 0.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ContainerNode[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/RowNode[0]/SectionNode[4]", + "entityName" : null, + "entityKind" : "SectionNode", + "parentPath" : "ContainerNode[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/RowNode[0]", + "childIndex" : 4, "depth" : 6, "layer" : 6, "computedX" : 121.818, @@ -1274,10 +1454,10 @@ "left" : 0.0 } }, { - "path" : "ContainerNode[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/RowNode[0]/SectionNode[2]/ParagraphNode[0]", + "path" : "ContainerNode[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/RowNode[0]/SectionNode[4]/ParagraphNode[0]", "entityName" : null, "entityKind" : "ParagraphNode", - "parentPath" : "ContainerNode[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/RowNode[0]/SectionNode[2]", + "parentPath" : "ContainerNode[0]/SectionNode[0]/LayoutAnchorNode[2]/SectionNode[0]/RowNode[0]/SectionNode[4]", "childIndex" : 0, "depth" : 7, "layer" : 7, @@ -1431,6 +1611,36 @@ "childIndex" : 1, "depth" : 6, "layer" : 6, + "computedX" : 84.0, + "computedY" : 107.675, + "placementX" : 84.0, + "placementY" : 107.675, + "placementWidth" : 0.0, + "placementHeight" : 0.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 0.0, + "contentHeight" : 0.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ContainerNode[0]/SectionNode[0]/LayoutAnchorNode[3]/SectionNode[0]/RowNode[0]/SectionNode[2]", + "entityName" : null, + "entityKind" : "SectionNode", + "parentPath" : "ContainerNode[0]/SectionNode[0]/LayoutAnchorNode[3]/SectionNode[0]/RowNode[0]", + "childIndex" : 2, + "depth" : 6, + "layer" : 6, "computedX" : 92.0, "computedY" : 99.675, "placementX" : 92.0, @@ -1454,10 +1664,10 @@ "left" : 0.0 } }, { - "path" : "ContainerNode[0]/SectionNode[0]/LayoutAnchorNode[3]/SectionNode[0]/RowNode[0]/SectionNode[1]/Align[0]", + "path" : "ContainerNode[0]/SectionNode[0]/LayoutAnchorNode[3]/SectionNode[0]/RowNode[0]/SectionNode[2]/Align[0]", "entityName" : null, "entityKind" : "Align", - "parentPath" : "ContainerNode[0]/SectionNode[0]/LayoutAnchorNode[3]/SectionNode[0]/RowNode[0]/SectionNode[1]", + "parentPath" : "ContainerNode[0]/SectionNode[0]/LayoutAnchorNode[3]/SectionNode[0]/RowNode[0]/SectionNode[2]", "childIndex" : 0, "depth" : 7, "layer" : 7, @@ -1484,10 +1694,10 @@ "left" : 0.0 } }, { - "path" : "ContainerNode[0]/SectionNode[0]/LayoutAnchorNode[3]/SectionNode[0]/RowNode[0]/SectionNode[1]/Align[0]/LayoutAnchorNode[0]", + "path" : "ContainerNode[0]/SectionNode[0]/LayoutAnchorNode[3]/SectionNode[0]/RowNode[0]/SectionNode[2]/Align[0]/LayoutAnchorNode[0]", "entityName" : null, "entityKind" : "LayoutAnchorNode", - "parentPath" : "ContainerNode[0]/SectionNode[0]/LayoutAnchorNode[3]/SectionNode[0]/RowNode[0]/SectionNode[1]/Align[0]", + "parentPath" : "ContainerNode[0]/SectionNode[0]/LayoutAnchorNode[3]/SectionNode[0]/RowNode[0]/SectionNode[2]/Align[0]", "childIndex" : 0, "depth" : 8, "layer" : 8, @@ -1514,10 +1724,10 @@ "left" : 0.0 } }, { - "path" : "ContainerNode[0]/SectionNode[0]/LayoutAnchorNode[3]/SectionNode[0]/RowNode[0]/SectionNode[1]/Align[0]/LayoutAnchorNode[0]/marker[0]", + "path" : "ContainerNode[0]/SectionNode[0]/LayoutAnchorNode[3]/SectionNode[0]/RowNode[0]/SectionNode[2]/Align[0]/LayoutAnchorNode[0]/marker[0]", "entityName" : "marker", "entityKind" : "CanvasLayerNode", - "parentPath" : "ContainerNode[0]/SectionNode[0]/LayoutAnchorNode[3]/SectionNode[0]/RowNode[0]/SectionNode[1]/Align[0]/LayoutAnchorNode[0]", + "parentPath" : "ContainerNode[0]/SectionNode[0]/LayoutAnchorNode[3]/SectionNode[0]/RowNode[0]/SectionNode[2]/Align[0]/LayoutAnchorNode[0]", "childIndex" : 0, "depth" : 9, "layer" : 9, @@ -1544,10 +1754,10 @@ "left" : 0.0 } }, { - "path" : "ContainerNode[0]/SectionNode[0]/LayoutAnchorNode[3]/SectionNode[0]/RowNode[0]/SectionNode[1]/Align[0]/LayoutAnchorNode[0]/marker[0]/SectionNode[0]", + "path" : "ContainerNode[0]/SectionNode[0]/LayoutAnchorNode[3]/SectionNode[0]/RowNode[0]/SectionNode[2]/Align[0]/LayoutAnchorNode[0]/marker[0]/SectionNode[0]", "entityName" : null, "entityKind" : "SectionNode", - "parentPath" : "ContainerNode[0]/SectionNode[0]/LayoutAnchorNode[3]/SectionNode[0]/RowNode[0]/SectionNode[1]/Align[0]/LayoutAnchorNode[0]/marker[0]", + "parentPath" : "ContainerNode[0]/SectionNode[0]/LayoutAnchorNode[3]/SectionNode[0]/RowNode[0]/SectionNode[2]/Align[0]/LayoutAnchorNode[0]/marker[0]", "childIndex" : 0, "depth" : 10, "layer" : 10, @@ -1574,10 +1784,10 @@ "left" : 0.0 } }, { - "path" : "ContainerNode[0]/SectionNode[0]/LayoutAnchorNode[3]/SectionNode[0]/RowNode[0]/SectionNode[1]/Align[0]/LayoutAnchorNode[0]/marker[0]/SectionNode[0]/EllipseNode[0]", + "path" : "ContainerNode[0]/SectionNode[0]/LayoutAnchorNode[3]/SectionNode[0]/RowNode[0]/SectionNode[2]/Align[0]/LayoutAnchorNode[0]/marker[0]/SectionNode[0]/EllipseNode[0]", "entityName" : null, "entityKind" : "EllipseNode", - "parentPath" : "ContainerNode[0]/SectionNode[0]/LayoutAnchorNode[3]/SectionNode[0]/RowNode[0]/SectionNode[1]/Align[0]/LayoutAnchorNode[0]/marker[0]/SectionNode[0]", + "parentPath" : "ContainerNode[0]/SectionNode[0]/LayoutAnchorNode[3]/SectionNode[0]/RowNode[0]/SectionNode[2]/Align[0]/LayoutAnchorNode[0]/marker[0]/SectionNode[0]", "childIndex" : 0, "depth" : 11, "layer" : 11, @@ -1604,11 +1814,41 @@ "left" : 0.0 } }, { - "path" : "ContainerNode[0]/SectionNode[0]/LayoutAnchorNode[3]/SectionNode[0]/RowNode[0]/SectionNode[2]", + "path" : "ContainerNode[0]/SectionNode[0]/LayoutAnchorNode[3]/SectionNode[0]/RowNode[0]/SectionNode[3]", "entityName" : null, "entityKind" : "SectionNode", "parentPath" : "ContainerNode[0]/SectionNode[0]/LayoutAnchorNode[3]/SectionNode[0]/RowNode[0]", - "childIndex" : 2, + "childIndex" : 3, + "depth" : 6, + "layer" : 6, + "computedX" : 113.818, + "computedY" : 107.675, + "placementX" : 113.818, + "placementY" : 107.675, + "placementWidth" : 0.0, + "placementHeight" : 0.0, + "startPage" : 0, + "endPage" : 0, + "contentWidth" : 0.0, + "contentHeight" : 0.0, + "margin" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + }, + "padding" : { + "top" : 0.0, + "right" : 0.0, + "bottom" : 0.0, + "left" : 0.0 + } + }, { + "path" : "ContainerNode[0]/SectionNode[0]/LayoutAnchorNode[3]/SectionNode[0]/RowNode[0]/SectionNode[4]", + "entityName" : null, + "entityKind" : "SectionNode", + "parentPath" : "ContainerNode[0]/SectionNode[0]/LayoutAnchorNode[3]/SectionNode[0]/RowNode[0]", + "childIndex" : 4, "depth" : 6, "layer" : 6, "computedX" : 121.818, @@ -1634,10 +1874,10 @@ "left" : 0.0 } }, { - "path" : "ContainerNode[0]/SectionNode[0]/LayoutAnchorNode[3]/SectionNode[0]/RowNode[0]/SectionNode[2]/ParagraphNode[0]", + "path" : "ContainerNode[0]/SectionNode[0]/LayoutAnchorNode[3]/SectionNode[0]/RowNode[0]/SectionNode[4]/ParagraphNode[0]", "entityName" : null, "entityKind" : "ParagraphNode", - "parentPath" : "ContainerNode[0]/SectionNode[0]/LayoutAnchorNode[3]/SectionNode[0]/RowNode[0]/SectionNode[2]", + "parentPath" : "ContainerNode[0]/SectionNode[0]/LayoutAnchorNode[3]/SectionNode[0]/RowNode[0]/SectionNode[4]", "childIndex" : 0, "depth" : 7, "layer" : 7, diff --git a/qa/src/test/resources/layout-snapshots/document/timeline_paginated.json b/qa/src/test/resources/layout-snapshots/document/timeline_paginated.json index a5d3b88f4..16d51f16e 100644 --- a/qa/src/test/resources/layout-snapshots/document/timeline_paginated.json +++ b/qa/src/test/resources/layout-snapshots/document/timeline_paginated.json @@ -472,12 +472,12 @@ "depth" : 3, "layer" : 3, "computedX" : 20.0, - "computedY" : -10.35, + "computedY" : 117.175, "placementX" : 20.0, - "placementY" : -10.35, + "placementY" : 117.175, "placementWidth" : 280.0, "placementHeight" : 32.825, - "startPage" : 0, + "startPage" : 1, "endPage" : 1, "contentWidth" : 280.0, "contentHeight" : 32.825, @@ -502,12 +502,12 @@ "depth" : 4, "layer" : 4, "computedX" : 20.0, - "computedY" : -10.35, + "computedY" : 117.175, "placementX" : 20.0, - "placementY" : -10.35, + "placementY" : 117.175, "placementWidth" : 280.0, "placementHeight" : 32.825, - "startPage" : 0, + "startPage" : 1, "endPage" : 1, "contentWidth" : 280.0, "contentHeight" : 32.825, diff --git a/qa/src/test/resources/layout-snapshots/fonts/available_fonts_preview.json b/qa/src/test/resources/layout-snapshots/fonts/available_fonts_preview.json index 0d3555d48..e6f84bbc9 100644 --- a/qa/src/test/resources/layout-snapshots/fonts/available_fonts_preview.json +++ b/qa/src/test/resources/layout-snapshots/fonts/available_fonts_preview.json @@ -1372,12 +1372,12 @@ "depth" : 2, "layer" : 2, "computedX" : 28.0, - "computedY" : -43.554, + "computedY" : 736.466, "placementX" : 28.0, - "placementY" : -43.554, + "placementY" : 736.466, "placementWidth" : 350.306, "placementHeight" : 77.424, - "startPage" : 0, + "startPage" : 1, "endPage" : 1, "contentWidth" : 350.306, "contentHeight" : 77.424, diff --git a/qa/src/test/resources/sample-data/consulting-invoice-logo.png b/qa/src/test/resources/sample-data/consulting-invoice-logo.png new file mode 100644 index 000000000..e1a37e806 Binary files /dev/null and b/qa/src/test/resources/sample-data/consulting-invoice-logo.png differ diff --git a/qa/src/test/resources/visual-baselines/cv-v2-layered/blue_banner-page-0.png b/qa/src/test/resources/visual-baselines/cv-v2-layered/blue_banner-page-0.png index c2232517a..750d2e7c3 100644 Binary files a/qa/src/test/resources/visual-baselines/cv-v2-layered/blue_banner-page-0.png and b/qa/src/test/resources/visual-baselines/cv-v2-layered/blue_banner-page-0.png differ diff --git a/qa/src/test/resources/visual-baselines/cv-v2-layered/boxed_sections-page-0.png b/qa/src/test/resources/visual-baselines/cv-v2-layered/boxed_sections-page-0.png index be81d1776..4e5c970e1 100644 Binary files a/qa/src/test/resources/visual-baselines/cv-v2-layered/boxed_sections-page-0.png and b/qa/src/test/resources/visual-baselines/cv-v2-layered/boxed_sections-page-0.png differ diff --git a/qa/src/test/resources/visual-baselines/cv-v2-layered/centered_headline-page-0.png b/qa/src/test/resources/visual-baselines/cv-v2-layered/centered_headline-page-0.png index dea90bd58..26a99d354 100644 Binary files a/qa/src/test/resources/visual-baselines/cv-v2-layered/centered_headline-page-0.png and b/qa/src/test/resources/visual-baselines/cv-v2-layered/centered_headline-page-0.png differ diff --git a/qa/src/test/resources/visual-baselines/cv-v2-layered/charcoal_gold-page-0.png b/qa/src/test/resources/visual-baselines/cv-v2-layered/charcoal_gold-page-0.png new file mode 100644 index 000000000..4416f356e Binary files /dev/null and b/qa/src/test/resources/visual-baselines/cv-v2-layered/charcoal_gold-page-0.png differ diff --git a/qa/src/test/resources/visual-baselines/cv-v2-layered/engineering_resume-page-0.png b/qa/src/test/resources/visual-baselines/cv-v2-layered/engineering_resume-page-0.png index 8965d8855..d7e64901f 100644 Binary files a/qa/src/test/resources/visual-baselines/cv-v2-layered/engineering_resume-page-0.png and b/qa/src/test/resources/visual-baselines/cv-v2-layered/engineering_resume-page-0.png differ diff --git a/qa/src/test/resources/visual-baselines/cv-v2-layered/executive-page-0.png b/qa/src/test/resources/visual-baselines/cv-v2-layered/executive-page-0.png index 98f3ad4d8..6b4fe69eb 100644 Binary files a/qa/src/test/resources/visual-baselines/cv-v2-layered/executive-page-0.png and b/qa/src/test/resources/visual-baselines/cv-v2-layered/executive-page-0.png differ diff --git a/qa/src/test/resources/visual-baselines/cv-v2-layered/midnight_navy-page-0.png b/qa/src/test/resources/visual-baselines/cv-v2-layered/midnight_navy-page-0.png new file mode 100644 index 000000000..ee47fbb74 Binary files /dev/null and b/qa/src/test/resources/visual-baselines/cv-v2-layered/midnight_navy-page-0.png differ diff --git a/qa/src/test/resources/visual-baselines/cv-v2-layered/minimal_underlined-page-0.png b/qa/src/test/resources/visual-baselines/cv-v2-layered/minimal_underlined-page-0.png index adfa1806f..13e1dede6 100644 Binary files a/qa/src/test/resources/visual-baselines/cv-v2-layered/minimal_underlined-page-0.png and b/qa/src/test/resources/visual-baselines/cv-v2-layered/minimal_underlined-page-0.png differ diff --git a/qa/src/test/resources/visual-baselines/cv-v2-layered/modern_professional-page-0.png b/qa/src/test/resources/visual-baselines/cv-v2-layered/modern_professional-page-0.png index c210db266..561c3277e 100644 Binary files a/qa/src/test/resources/visual-baselines/cv-v2-layered/modern_professional-page-0.png and b/qa/src/test/resources/visual-baselines/cv-v2-layered/modern_professional-page-0.png differ diff --git a/qa/src/test/resources/visual-baselines/cv-v2-layered/navy_sidebar-page-0.png b/qa/src/test/resources/visual-baselines/cv-v2-layered/navy_sidebar-page-0.png new file mode 100644 index 000000000..367e6f85a Binary files /dev/null and b/qa/src/test/resources/visual-baselines/cv-v2-layered/navy_sidebar-page-0.png differ diff --git a/qa/src/test/resources/visual-baselines/cv-v2-layered/orange_ops-page-0.png b/qa/src/test/resources/visual-baselines/cv-v2-layered/orange_ops-page-0.png new file mode 100644 index 000000000..96a2e2f37 Binary files /dev/null and b/qa/src/test/resources/visual-baselines/cv-v2-layered/orange_ops-page-0.png differ diff --git a/qa/src/test/resources/visual-baselines/cv-v2-layered/panel-page-0.png b/qa/src/test/resources/visual-baselines/cv-v2-layered/panel-page-0.png index 7aabda0a4..2f3a0a588 100644 Binary files a/qa/src/test/resources/visual-baselines/cv-v2-layered/panel-page-0.png and b/qa/src/test/resources/visual-baselines/cv-v2-layered/panel-page-0.png differ diff --git a/qa/src/test/resources/visual-baselines/cv-v2-layered/professional_sidebar-page-0.png b/qa/src/test/resources/visual-baselines/cv-v2-layered/professional_sidebar-page-0.png new file mode 100644 index 000000000..57fd32c53 Binary files /dev/null and b/qa/src/test/resources/visual-baselines/cv-v2-layered/professional_sidebar-page-0.png differ diff --git a/qa/src/test/resources/visual-baselines/cv-v2-layered/serif_headline-page-0.png b/qa/src/test/resources/visual-baselines/cv-v2-layered/serif_headline-page-0.png new file mode 100644 index 000000000..8ec07cfc1 Binary files /dev/null and b/qa/src/test/resources/visual-baselines/cv-v2-layered/serif_headline-page-0.png differ diff --git a/qa/src/test/resources/visual-baselines/cv-v2-layered/slate_orange-page-0.png b/qa/src/test/resources/visual-baselines/cv-v2-layered/slate_orange-page-0.png new file mode 100644 index 000000000..f80279a87 Binary files /dev/null and b/qa/src/test/resources/visual-baselines/cv-v2-layered/slate_orange-page-0.png differ diff --git a/qa/src/test/resources/visual-baselines/cv-v2-layered/teal_pulse-page-0.png b/qa/src/test/resources/visual-baselines/cv-v2-layered/teal_pulse-page-0.png new file mode 100644 index 000000000..b47c081c3 Binary files /dev/null and b/qa/src/test/resources/visual-baselines/cv-v2-layered/teal_pulse-page-0.png differ diff --git a/qa/src/test/resources/visual-baselines/cv-v2-layered/terracotta_rail-page-0.png b/qa/src/test/resources/visual-baselines/cv-v2-layered/terracotta_rail-page-0.png new file mode 100644 index 000000000..95c8e16bf Binary files /dev/null and b/qa/src/test/resources/visual-baselines/cv-v2-layered/terracotta_rail-page-0.png differ diff --git a/qa/src/test/resources/visual-baselines/cv-v2-layered/violet_grid-page-0.png b/qa/src/test/resources/visual-baselines/cv-v2-layered/violet_grid-page-0.png new file mode 100644 index 000000000..0c6bae506 Binary files /dev/null and b/qa/src/test/resources/visual-baselines/cv-v2-layered/violet_grid-page-0.png differ diff --git a/qa/src/test/resources/visual-baselines/invoice-v2-layered/classic_invoice-page-0.png b/qa/src/test/resources/visual-baselines/invoice-v2-layered/classic_invoice-page-0.png new file mode 100644 index 000000000..a9681f812 Binary files /dev/null and b/qa/src/test/resources/visual-baselines/invoice-v2-layered/classic_invoice-page-0.png differ diff --git a/qa/src/test/resources/visual-baselines/invoice-v2-layered/consulting_invoice-page-0.png b/qa/src/test/resources/visual-baselines/invoice-v2-layered/consulting_invoice-page-0.png new file mode 100644 index 000000000..502aa3a0d Binary files /dev/null and b/qa/src/test/resources/visual-baselines/invoice-v2-layered/consulting_invoice-page-0.png differ diff --git a/qa/src/test/resources/visual-baselines/invoice-v2-layered/luma_studio_invoice-page-0.png b/qa/src/test/resources/visual-baselines/invoice-v2-layered/luma_studio_invoice-page-0.png new file mode 100644 index 000000000..05eeaea9b Binary files /dev/null and b/qa/src/test/resources/visual-baselines/invoice-v2-layered/luma_studio_invoice-page-0.png differ diff --git a/qa/src/test/resources/visual-baselines/invoice-v2-layered/merchant_invoice-page-0.png b/qa/src/test/resources/visual-baselines/invoice-v2-layered/merchant_invoice-page-0.png new file mode 100644 index 000000000..2490fdd86 Binary files /dev/null and b/qa/src/test/resources/visual-baselines/invoice-v2-layered/merchant_invoice-page-0.png differ diff --git a/qa/src/test/resources/visual-baselines/invoice-v2-layered/metered_invoice-page-0.png b/qa/src/test/resources/visual-baselines/invoice-v2-layered/metered_invoice-page-0.png new file mode 100644 index 000000000..c0b6a4c64 Binary files /dev/null and b/qa/src/test/resources/visual-baselines/invoice-v2-layered/metered_invoice-page-0.png differ diff --git a/qa/src/test/resources/visual-baselines/invoice-v2-layered/obsidian_invoice-page-0.png b/qa/src/test/resources/visual-baselines/invoice-v2-layered/obsidian_invoice-page-0.png new file mode 100644 index 000000000..eac10f6ba Binary files /dev/null and b/qa/src/test/resources/visual-baselines/invoice-v2-layered/obsidian_invoice-page-0.png differ diff --git a/qa/src/test/resources/visual-baselines/invoice-v2-layered/payments_invoice-page-0.png b/qa/src/test/resources/visual-baselines/invoice-v2-layered/payments_invoice-page-0.png new file mode 100644 index 000000000..525ad6654 Binary files /dev/null and b/qa/src/test/resources/visual-baselines/invoice-v2-layered/payments_invoice-page-0.png differ diff --git a/qa/src/test/resources/visual-baselines/invoice-v2-layered/platform_invoice-page-0.png b/qa/src/test/resources/visual-baselines/invoice-v2-layered/platform_invoice-page-0.png new file mode 100644 index 000000000..1170210e7 Binary files /dev/null and b/qa/src/test/resources/visual-baselines/invoice-v2-layered/platform_invoice-page-0.png differ diff --git a/qa/src/test/resources/visual-baselines/invoice-v2-layered/subscription_invoice-page-0.png b/qa/src/test/resources/visual-baselines/invoice-v2-layered/subscription_invoice-page-0.png new file mode 100644 index 000000000..0dbd2cba5 Binary files /dev/null and b/qa/src/test/resources/visual-baselines/invoice-v2-layered/subscription_invoice-page-0.png differ diff --git a/qa/src/test/resources/visual-baselines/invoice-v2-layered/workspace_invoice-page-0.png b/qa/src/test/resources/visual-baselines/invoice-v2-layered/workspace_invoice-page-0.png new file mode 100644 index 000000000..a334c63d8 Binary files /dev/null and b/qa/src/test/resources/visual-baselines/invoice-v2-layered/workspace_invoice-page-0.png differ diff --git a/qa/src/test/resources/visual-baselines/proposal-v2-layered/editorial_proposal-page-0.png b/qa/src/test/resources/visual-baselines/proposal-v2-layered/editorial_proposal-page-0.png new file mode 100644 index 000000000..1fec1f87c Binary files /dev/null and b/qa/src/test/resources/visual-baselines/proposal-v2-layered/editorial_proposal-page-0.png differ diff --git a/qa/src/test/resources/visual-baselines/proposal-v2-layered/editorial_proposal-page-1.png b/qa/src/test/resources/visual-baselines/proposal-v2-layered/editorial_proposal-page-1.png new file mode 100644 index 000000000..b34c336b7 Binary files /dev/null and b/qa/src/test/resources/visual-baselines/proposal-v2-layered/editorial_proposal-page-1.png differ diff --git a/qa/src/test/resources/visual-baselines/proposal-v2-layered/indigo_proposal-page-0.png b/qa/src/test/resources/visual-baselines/proposal-v2-layered/indigo_proposal-page-0.png new file mode 100644 index 000000000..612e14934 Binary files /dev/null and b/qa/src/test/resources/visual-baselines/proposal-v2-layered/indigo_proposal-page-0.png differ diff --git a/qa/src/test/resources/visual-baselines/proposal-v2-layered/northline_proposal-page-0.png b/qa/src/test/resources/visual-baselines/proposal-v2-layered/northline_proposal-page-0.png new file mode 100644 index 000000000..707752f16 Binary files /dev/null and b/qa/src/test/resources/visual-baselines/proposal-v2-layered/northline_proposal-page-0.png differ diff --git a/qa/src/test/resources/visual-baselines/proposal-v2-layered/northline_proposal-page-1.png b/qa/src/test/resources/visual-baselines/proposal-v2-layered/northline_proposal-page-1.png new file mode 100644 index 000000000..f4646b8a1 Binary files /dev/null and b/qa/src/test/resources/visual-baselines/proposal-v2-layered/northline_proposal-page-1.png differ diff --git a/qa/src/test/resources/visual-baselines/receipt-layered/modern_receipt-page-0.png b/qa/src/test/resources/visual-baselines/receipt-layered/modern_receipt-page-0.png new file mode 100644 index 000000000..4e4f15258 Binary files /dev/null and b/qa/src/test/resources/visual-baselines/receipt-layered/modern_receipt-page-0.png differ diff --git a/qa/src/test/resources/visual-baselines/rota/cobalt_rota-page-0.png b/qa/src/test/resources/visual-baselines/rota/cobalt_rota-page-0.png new file mode 100644 index 000000000..fd0acf581 Binary files /dev/null and b/qa/src/test/resources/visual-baselines/rota/cobalt_rota-page-0.png differ diff --git a/render-docx/src/main/java/com/demcha/compose/document/backend/semantic/docx/DocxSemanticBackend.java b/render-docx/src/main/java/com/demcha/compose/document/backend/semantic/docx/DocxSemanticBackend.java index 713ac5c93..0c4a49101 100644 --- a/render-docx/src/main/java/com/demcha/compose/document/backend/semantic/docx/DocxSemanticBackend.java +++ b/render-docx/src/main/java/com/demcha/compose/document/backend/semantic/docx/DocxSemanticBackend.java @@ -338,7 +338,11 @@ private void warnUnsupported(DocumentNode node) { * no signal at all, weaker than the block-level drop path. */ private void warnDroppedInlineRuns(ParagraphNode node) { - for (InlineRun run : node.inlineRuns()) { + warnDroppedInlineRuns(node.inlineRuns()); + } + + private void warnDroppedInlineRuns(List runs) { + for (InlineRun run : runs) { if (run instanceof InlineTextRun || run instanceof InlineHighlightRun) { continue; } @@ -369,8 +373,15 @@ private void writeList(XWPFDocument document, if (normalized.isBlank()) { continue; } - writeListLine(document, list.textStyle(), - list.marker().prefix() + normalized, 0); + if (list.marker().isRich()) { + // A drawn marker's pieces are runs, so the row is written the way + // any row with runs in it is; its item is still just a label. + writeRichListLine(document, list.textStyle(), list.marker(), + com.demcha.compose.document.node.ListItem.of(normalized), 0); + } else { + writeListLine(document, list.textStyle(), + list.marker().prefix() + normalized, 0); + } } for (com.demcha.compose.document.node.ListItem item : list.nestedItems()) { writeNestedItem(document, list, item, 0); @@ -389,7 +400,11 @@ private void writeNestedItem(XWPFDocument document, item.marker() != null ? item.marker() : com.demcha.compose.document.node.ListMarker.defaultForDepth(depth); - writeListLine(document, list.textStyle(), marker.prefix() + item.label(), depth); + if (item.isRich() || marker.isRich()) { + writeRichListLine(document, list.textStyle(), marker, item, depth); + } else { + writeListLine(document, list.textStyle(), marker.prefix() + item.label(), depth); + } for (com.demcha.compose.document.node.ListItem child : item.children()) { writeNestedItem(document, list, child, depth + 1); } @@ -403,6 +418,68 @@ private void writeListLine(XWPFDocument document, DocumentTextStyle style, run.setText(" ".repeat(depth) + text); } + /** + * Writes an item whose marker or whose content is made of inline runs as one + * Word run per inline run, the way {@link #writeParagraphRuns} writes a rich + * paragraph. + * + *

This is the part of the opt-in marker/content list the semantic export + * can reproduce, and it reproduces it exactly. The geometry — the measured + * marker column, the gap, the shared content origin — is unavailable here for + * the reason {@code hangingIndent} documents. Which piece of an item is bold + * is not geometry: Word carries a style per run inside a paragraph, so + * writing the item's plain reading in one face would be dropping something + * Word can hold.

+ * + *

The same is true of a marker. A marker written as text keeps the colour + * and face it was given, because those are run properties Word has. A marker + * that draws a disc or an icon has no Word analogue at all, so it drops with + * the export's usual per-kind warning and its item is written unmarked — + * rather than substituting a glyph the author did not ask for.

+ */ + private void writeRichListLine(XWPFDocument document, DocumentTextStyle style, + com.demcha.compose.document.node.ListMarker marker, + com.demcha.compose.document.node.ListItem item, + int depth) { + warnDroppedInlineRuns(marker.runs()); + warnDroppedInlineRuns(item.runs()); + XWPFParagraph para = document.createParagraph(); + XWPFRun leading = para.createRun(); + applyStyle(leading, style); + leading.setText(" ".repeat(depth) + (marker.isRich() ? "" : marker.prefix())); + if (marker.isRich()) { + writeInlineTextRuns(para, style, marker.runs()); + // The gap after a marker is markerGap, which is geometry and so not + // available here; a space is what separates a marker from its item on + // the text path, and it separates them here for the same reason. + if (!com.demcha.compose.document.node.InlineRun.plainText(marker.runs()).isBlank()) { + XWPFRun gap = para.createRun(); + applyStyle(gap, style); + gap.setText(" "); + } + } + if (item.isRich()) { + writeInlineTextRuns(para, style, item.runs()); + } else { + XWPFRun label = para.createRun(); + applyStyle(label, style); + label.setText(item.label()); + } + } + + /** + * Appends one Word run per text-carrying inline run, each in its own style + * and falling back to {@code style} when it has none. + */ + private void writeInlineTextRuns(XWPFParagraph para, DocumentTextStyle style, + List runs) { + for (InlineTextRun run : com.demcha.compose.document.node.InlineRun.textRuns(runs)) { + XWPFRun docRun = para.createRun(); + applyStyle(docRun, run.textStyle() == null ? style : run.textStyle()); + docRun.setText(run.text() == null ? "" : run.text()); + } + } + /** * Semantic chart fallback: the semantic export has no layout pass, so the * chart's compiled vector geometry is unavailable here. The chart's diff --git a/render-docx/src/test/java/com/demcha/compose/document/backend/semantic/docx/DocxSemanticBackendTest.java b/render-docx/src/test/java/com/demcha/compose/document/backend/semantic/docx/DocxSemanticBackendTest.java index 588cfed06..c27b6bac8 100644 --- a/render-docx/src/test/java/com/demcha/compose/document/backend/semantic/docx/DocxSemanticBackendTest.java +++ b/render-docx/src/test/java/com/demcha/compose/document/backend/semantic/docx/DocxSemanticBackendTest.java @@ -13,6 +13,7 @@ import com.demcha.compose.document.table.DocumentTableColumn; import org.apache.poi.xwpf.usermodel.XWPFDocument; import org.apache.poi.xwpf.usermodel.XWPFParagraph; +import org.apache.poi.xwpf.usermodel.XWPFRun; import org.apache.poi.xwpf.usermodel.XWPFTable; import org.junit.jupiter.api.Test; @@ -142,6 +143,111 @@ void nestedListItemsKeepTheirCustomMarkers() throws Exception { } } + @Test + void aRichListItemKeepsOneWordRunPerAuthoredRun() throws Exception { + try (XWPFDocument document = new XWPFDocument( + new ByteArrayInputStream(richListDocx(true)))) { + List paragraphs = document.getParagraphs().stream() + .filter(p -> !p.getText().isBlank()) + .toList(); + assertThat(paragraphs).hasSize(2); + + // The marker still reads the same down the whole list, and the + // item's text is all there. The geometry hangingIndent(true) asks + // for has no Word analogue; which piece of an item is bold does. + assertThat(paragraphs.get(0).getText()).isEqualTo("- Plain item"); + XWPFParagraph rich = paragraphs.get(1); + assertThat(rich.getText()).isEqualTo("- Status: pending"); + assertThat(rich.getRuns().stream().map(XWPFRun::text).toList()) + .as("the marker leads in the list's own style, then one run each") + .containsExactly("- ", "Status: ", "pending"); + assertThat(rich.getRuns().stream().map(XWPFRun::isBold).toList()) + .as("and only the bold run is bold") + .containsExactly(false, true, false); + } + } + + @Test + void aRichListItemExportsTheSameWhetherOrNotTheListOptedIntoMarkerGeometry() throws Exception { + // hangingIndent is fixed-layout geometry, and the semantic export lays + // nothing out — so unlike the PDF path, which needs the opt-in before it + // can carry runs at all, this one is indifferent to it. Word owns the + // layout here and can hold a run's style either way. + assertThat(runStructure(richListDocx(false))) + .isEqualTo(runStructure(richListDocx(true))); + } + + @Test + void aDrawnListMarkerDropsAndATextMarkerKeepsItsOwnStyle() throws Exception { + byte[] docxBytes; + try (DocumentSession session = GraphCompose.document() + .pageSize(595, 842) + .margin(DocumentInsets.of(36)) + .create()) { + session.dsl().pageFlow().name("Flow") + .addList(list -> list + .hangingIndent(true) + .marker(m -> m.dot(4.0, DocumentColor.rgb(0, 0x88, 0x88))) + .items("Drawn marker item")) + .addList(list -> list + .hangingIndent(true) + .marker(m -> m.color("•", DocumentColor.rgb(0, 0x88, 0x88))) + .items("Coloured marker item")) + .build(); + docxBytes = session.export(new DocxSemanticBackend()); + } + + try (XWPFDocument document = new XWPFDocument(new ByteArrayInputStream(docxBytes))) { + List paragraphs = document.getParagraphs().stream() + .filter(p -> !p.getText().isBlank()) + .toList(); + assertThat(paragraphs).hasSize(2); + + // A disc has no Word analogue, so it drops — and drops rather than + // being replaced by a bullet nobody asked for. The item survives. + assertThat(paragraphs.get(0).getText()).isEqualTo("Drawn marker item"); + + // A marker written as text keeps the colour it was given, because a + // run colour is something Word holds. + XWPFParagraph coloured = paragraphs.get(1); + assertThat(coloured.getText()).isEqualTo("• Coloured marker item"); + assertThat(coloured.getRuns().stream().map(XWPFRun::text).toList()) + .containsExactly("", "•", " ", "Coloured marker item"); + assertThat(coloured.getRuns().get(1).getColor()) + .as("the marker's own colour") + .isEqualTo("008888"); + assertThat(coloured.getRuns().get(3).getColor()) + .as("and the item's text does not take it") + .isNotEqualTo("008888"); + } + } + + private static byte[] richListDocx(boolean hangingIndent) throws Exception { + try (DocumentSession session = GraphCompose.document() + .pageSize(595, 842) + .margin(DocumentInsets.of(36)) + .create()) { + session.dsl().pageFlow().name("Flow") + .addList(list -> list + .dash() + .hangingIndent(hangingIndent) + .addItem("Plain item") + .addItem(t -> t.bold("Status: ").plain("pending"))) + .build(); + return session.export(new DocxSemanticBackend()); + } + } + + private static List runStructure(byte[] docxBytes) throws Exception { + try (XWPFDocument document = new XWPFDocument(new ByteArrayInputStream(docxBytes))) { + return document.getParagraphs().stream() + .filter(p -> !p.getText().isBlank()) + .flatMap(p -> p.getRuns().stream()) + .map(run -> run.text() + "|bold=" + run.isBold()) + .toList(); + } + } + @Test void listInsideASectionIsExported() throws Exception { byte[] docxBytes; diff --git a/templates/src/main/java/com/demcha/compose/document/templates/core/identity/ContactUri.java b/templates/src/main/java/com/demcha/compose/document/templates/core/identity/ContactUri.java new file mode 100644 index 000000000..94ad76583 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/core/identity/ContactUri.java @@ -0,0 +1,161 @@ +package com.demcha.compose.document.templates.core.identity; + +import com.demcha.compose.document.node.DocumentLinkOptions; + +import java.util.regex.Pattern; + +/** + * Turns a printed contact detail into something a reader can act on. + * + *

A document prints a number, an address and a site the way a person reads + * them; a reader's device needs the form it can dial, mail or open. Every + * template family needs that conversion and they all need the same one, so it + * lives beside the rest of the neutral identity layer rather than three times + * over in three families' presets.

+ * + *

A missing link is never a failed render

+ * + *

Every method here answers {@code null} rather than throwing: not + * everything a document prints where a contact would go is one, and losing the + * affordance is not worth failing to compose the page over. A caller passes the + * result straight to a component that takes a link or nothing.

+ * + * @author Artem Demchyshyn + * @since 2.4.0 + */ +public final class ContactUri { + + /** + * A trunk prefix in a printed number is for a domestic dialler and is not + * part of the international one — {@code +44 (0)20 7946 0832} dials + * {@code +442079460832}. Left in, the digits run together into a number that + * reaches nobody. A parenthesised area code is not a trunk prefix, + * which is why only an all-zero group goes. + */ + private static final Pattern TRUNK_PREFIX = Pattern.compile("\\(0+\\)"); + + private ContactUri() { + } + + /** + * A printed telephone number as a {@code tel:} target. + * + * @param phone the number as the sheet prints it + * @return the target, or {@code null} when the text carries no digits to + * dial + */ + public static String tel(String phone) { + if (phone == null || phone.isBlank()) { + return null; + } + String dialled = TRUNK_PREFIX.matcher(phone).replaceAll(""); + String digits = dialled.replaceAll("[^0-9]", ""); + return digits.isEmpty() + ? null + : "tel:" + (phone.trim().startsWith("+") ? "+" : "") + digits; + } + + /** + * The same, ready to hand to a component that takes a link or nothing. + * + * @param phone the number as the sheet prints it + * @return the link options, or {@code null} when there is nothing to dial + */ + public static DocumentLinkOptions telLink(String phone) { + return options(tel(phone)); + } + + /** + * An address as something a reader can write to. + * + * @param email the address as the sheet prints it + * @return the link options, or {@code null} when there is no address, or + * none that could be made from what was printed + */ + public static DocumentLinkOptions mailLink(String email) { + return email == null || email.isBlank() + ? null + : options("mailto:" + email.trim()); + } + + /** + * A written site as a browsable target. A sheet prints {@code example.com}; + * a viewer needs a scheme in front of it. + * + *

A target is not yet a link. Giving a scheme to something that is not an + * address — {@code ask at the bar} becomes {@code https://ask at the bar} — + * produces a string {@link DocumentLinkOptions} refuses, so this is for a + * component that takes a plain target. Where a link is being made, reach for + * {@link #webLink(String)}: that is where the refusal becomes no link rather + * than a page that does not compose.

+ * + * @param website the site as the sheet prints it + * @return the target, or {@code null} when there is no site + */ + public static String web(String website) { + if (website == null || website.isBlank()) { + return null; + } + String trimmed = website.trim(); + return trimmed.startsWith("http://") || trimmed.startsWith("https://") + ? trimmed + : "https://" + trimmed; + } + + /** + * The same, ready to hand to a component that takes a link or nothing. + * + * @param website the site as the sheet prints it + * @return the link options, or {@code null} when there is no site, or none + * that could be made from what was printed + */ + public static DocumentLinkOptions webLink(String website) { + return options(web(website)); + } + + /** + * The link a printed contact string wants, guessed from its own shape. + * + *

A document's foot often lists its channels as plain strings — a site, + * an address, a number — without saying which is which, because a reader can + * see it. This reads the same thing back: an address has an at-sign, a + * number starts with a plus or is mostly digits, and anything else is a + * site.

+ * + *

Reach for this only where the document really does not say. Where a + * field is known to hold a number or an address, name it: a heuristic that + * is right almost always is worse than a fact.

+ * + * @param contact the channel as the sheet prints it + * @return the link options, or {@code null} when nothing can be made of it + */ + public static DocumentLinkOptions channelLink(String contact) { + if (contact == null || contact.isBlank()) { + return null; + } + String trimmed = contact.trim(); + if (trimmed.contains("@")) { + return mailLink(trimmed); + } + String digits = trimmed.replaceAll("[^0-9]", ""); + boolean dialable = trimmed.startsWith("+") + || digits.length() >= 9 && digits.length() >= trimmed.length() / 2; + return dialable ? telLink(trimmed) : webLink(trimmed); + } + + /** + * The link a target makes, or nothing when it makes none. + * + *

{@link DocumentLinkOptions} refuses a string that is not a URI, and a + * contact line is not the place to find that out: a notice, a name, a line + * of prose can all reach a channel field, and none of them should stop the + * page composing.

+ */ + private static DocumentLinkOptions options(String target) { + try { + return new DocumentLinkOptions(target); + } catch (IllegalArgumentException notAUri) { + return null; + } + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/core/identity/SvgGlyph.java b/templates/src/main/java/com/demcha/compose/document/templates/core/identity/SvgGlyph.java index d33001a98..0efcd3ce9 100644 --- a/templates/src/main/java/com/demcha/compose/document/templates/core/identity/SvgGlyph.java +++ b/templates/src/main/java/com/demcha/compose/document/templates/core/identity/SvgGlyph.java @@ -9,9 +9,12 @@ import java.io.InputStream; import java.io.UncheckedIOException; import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.concurrent.ConcurrentHashMap; /** @@ -72,34 +75,73 @@ public static SvgGlyph fromResource(String resourcePath) { return CACHE.computeIfAbsent(resourcePath, SvgGlyph::load); } + /** + * Loads the flattened glyph for an SVG file on disk. + * + *

The classpath variant covers glyphs a template ships with. This one + * covers the glyph a template is given — a receipt's issuer mark, + * a report's client logo — which arrives as a file next to the running + * application rather than repackaged into its jar.

+ * + *

Not cached, unlike {@link #fromResource(String)}. A + * classpath entry cannot change while the JVM runs and the key space is the + * jar's; a file can be replaced, and the key space is whatever paths a + * caller passes — a service rendering for a hundred issuers would hold a + * hundred glyphs for its lifetime and keep serving the logo each one had + * when it started. Callers that load the same mark per document should hold + * the returned glyph themselves; it is immutable and safe to share.

+ * + * @param file path to the SVG file + * @return the recolorable glyph + * @throws IllegalStateException if the file does not exist + * @throws UncheckedIOException if the file cannot be read + * @since 2.4.0 + */ + public static SvgGlyph fromFile(Path file) { + Objects.requireNonNull(file, "file"); + if (!Files.isRegularFile(file)) { + throw new IllegalStateException("Missing glyph file: " + file); + } + try { + return flatten(SvgIcon.parse(Files.readString(file, StandardCharsets.UTF_8)), + file.toString()); + } catch (IOException e) { + throw new UncheckedIOException("Failed to read glyph file: " + file, e); + } + } + private static SvgGlyph load(String resourcePath) { try (InputStream input = SvgGlyph.class.getResourceAsStream(resourcePath)) { if (input == null) { throw new IllegalStateException("Missing CV glyph resource: " + resourcePath); } - SvgIcon icon = SvgIcon.parse(new String(input.readAllBytes(), StandardCharsets.UTF_8)); - List filled = new ArrayList<>(); - for (SvgIcon.Layer layer : icon.layers()) { - if (isInkFill(layer.fill()) || layer.fillPaint() != null) { - filled.addAll(layer.geometry().segments()); - } - } - if (filled.isEmpty()) { - // Stroke-only / unfilled icon: keep every path so the glyph - // still renders as a filled silhouette rather than vanishing. - for (SvgIcon.Layer layer : icon.layers()) { - filled.addAll(layer.geometry().segments()); - } - } - if (filled.isEmpty()) { - throw new IllegalStateException("CV glyph has no drawable geometry: " + resourcePath); - } - return new SvgGlyph(List.copyOf(filled), icon.aspectRatio()); + return flatten(SvgIcon.parse(new String(input.readAllBytes(), StandardCharsets.UTF_8)), + resourcePath); } catch (IOException e) { throw new UncheckedIOException("Failed to read CV glyph: " + resourcePath, e); } } + private static SvgGlyph flatten(SvgIcon icon, String source) { + List filled = new ArrayList<>(); + for (SvgIcon.Layer layer : icon.layers()) { + if (isInkFill(layer.fill()) || layer.fillPaint() != null) { + filled.addAll(layer.geometry().segments()); + } + } + if (filled.isEmpty()) { + // Stroke-only / unfilled icon: keep every path so the glyph + // still renders as a filled silhouette rather than vanishing. + for (SvgIcon.Layer layer : icon.layers()) { + filled.addAll(layer.geometry().segments()); + } + } + if (filled.isEmpty()) { + throw new IllegalStateException("Glyph has no drawable geometry: " + source); + } + return new SvgGlyph(List.copyOf(filled), icon.aspectRatio()); + } + /** * Reports whether a fill is "ink" (part of the glyph) rather than a * near-white background. Many svgrepo icons wrap the figure in a full-box diff --git a/templates/src/main/java/com/demcha/compose/document/templates/core/identity/package-info.java b/templates/src/main/java/com/demcha/compose/document/templates/core/identity/package-info.java index b46b1fc05..646ec3a86 100644 --- a/templates/src/main/java/com/demcha/compose/document/templates/core/identity/package-info.java +++ b/templates/src/main/java/com/demcha/compose/document/templates/core/identity/package-info.java @@ -20,6 +20,9 @@ * — the header widgets that render a {@code PartyIdentity}. *
  • {@link com.demcha.compose.document.templates.core.identity.SvgGlyph} * — a small inline-SVG glyph helper used by the header widgets.
  • + *
  • {@link com.demcha.compose.document.templates.core.identity.ContactUri} + * — turns a printed number, address or site into the target a reader's + * device can act on.
  • * */ package com.demcha.compose.document.templates.core.identity; diff --git a/templates/src/main/java/com/demcha/compose/document/templates/core/theme/BrandTheme.java b/templates/src/main/java/com/demcha/compose/document/templates/core/theme/BrandTheme.java index 64743a6e1..9439665c3 100644 --- a/templates/src/main/java/com/demcha/compose/document/templates/core/theme/BrandTheme.java +++ b/templates/src/main/java/com/demcha/compose/document/templates/core/theme/BrandTheme.java @@ -357,6 +357,25 @@ public static BrandTheme proposalModern() { Spacing.invoiceModern(), Decoration.classic()); } + + /** + * The "Modern Receipt" look — a fintech statement: Helvetica on a white + * page, near-black ink, one grey for every label, hairline rules between + * field rows, and a barely-tinted panel behind a very large amount. + * + *

    Deliberately carries no brand colour of its own. An issuer's accent + * is supplied per document through {@code ModernReceipt.Options}, so one + * theme serves every bank rather than one theme per bank.

    + * + * @return a {@code BrandTheme} for the "Modern Receipt" look + */ + public static BrandTheme receiptModern() { + return new BrandTheme( + Palette.receiptModern(), + Typography.receiptModern(), + Spacing.receiptModern(), + Decoration.classic()); + } // -- pre-built text-style helpers ------------------------------------ // Renderers ask the theme for an already-composed DocumentTextStyle // instead of re-assembling font + size + decoration + colour every diff --git a/templates/src/main/java/com/demcha/compose/document/templates/core/theme/Palette.java b/templates/src/main/java/com/demcha/compose/document/templates/core/theme/Palette.java index e2ab093fc..793a4ea5d 100644 --- a/templates/src/main/java/com/demcha/compose/document/templates/core/theme/Palette.java +++ b/templates/src/main/java/com/demcha/compose/document/templates/core/theme/Palette.java @@ -319,4 +319,26 @@ public static Palette invoiceModern() { DocumentColor.rgb(244, 238, 228), // banner — soft-tan hero panel / zebra (modern surfaceMuted) DocumentColor.rgb(252, 248, 240)); // mainFill — cream surface + page background (modern surface) } + + /** + * Modern Receipt palette: near-black ink on a white page, one grey for + * metadata, a hairline rule that reads as a separator rather than as a + * border, and a barely-there panel fill behind the hero amount. + * + *

    The restraint is the point. A statement is read for its numbers, so + * everything that is not a number is pushed down the contrast ladder — + * and it leaves the accent colour, which each issuer supplies through + * {@code ModernReceipt.Options}, the only saturated thing on the + * page.

    + * + * @return a {@code Palette} for the Modern Receipt flavour + */ + public static Palette receiptModern() { + return new Palette( + DocumentColor.rgb(13, 15, 20), // ink — amount, values, titles + DocumentColor.rgb(122, 127, 138), // muted — labels, captions, small print + DocumentColor.rgb(228, 231, 236), // rule — hairlines between field rows + DocumentColor.rgb(246, 247, 249), // banner — hero panel and party cards + DocumentColor.WHITE); // mainFill — the page + } } diff --git a/templates/src/main/java/com/demcha/compose/document/templates/core/theme/Spacing.java b/templates/src/main/java/com/demcha/compose/document/templates/core/theme/Spacing.java index 6ce6bc70b..faba25442 100644 --- a/templates/src/main/java/com/demcha/compose/document/templates/core/theme/Spacing.java +++ b/templates/src/main/java/com/demcha/compose/document/templates/core/theme/Spacing.java @@ -528,4 +528,35 @@ public static Spacing invoiceModern() { 0.45, // entryDateWeight 3.0); // entrySeparation } + + /** + * Modern Receipt spacing: roomy blocks, tight rows. + * + *

    Two tokens carry a receipt-specific reading. {@code accentRuleWidth} + * is the hairline under each field row rather than a wide accent strip — + * on this layout the rule separates rows, it does not decorate a panel. + * {@code entryTitleWeight} and {@code entryDateWeight} split each field + * row into its label and value columns, and they are equal because a + * receipt's values (account numbers, transaction ids) run as long as its + * labels.

    + * + * @return a {@code Spacing} scale for the Modern Receipt preset + */ + public static Spacing receiptModern() { + return new Spacing( + 16, // pageFlowSpacing (gap between blocks) + 5, // sectionBodySpacing + DocumentInsets.zero(), // sectionBodyPadding + DocumentInsets.zero(), // headlinePadding + DocumentInsets.zero(), // contactPadding + 12.0, // bannerCornerRadius (hero panel + party cards) + 16.0, // bannerInnerPadding (hero panel padding) + DocumentInsets.zero(), // bannerMargin + 0.6, // accentRuleWidth (field-row hairline) + 2.0, // paragraphMarginTop + 12.0, // entryHeaderRowSpacing (label/value column gap) + 1.0, // entryTitleWeight (label column) + 1.0, // entryDateWeight (value column) + 6.0); // entrySeparation + } } diff --git a/templates/src/main/java/com/demcha/compose/document/templates/core/theme/Typography.java b/templates/src/main/java/com/demcha/compose/document/templates/core/theme/Typography.java index 7e0b5eda9..0b176f121 100644 --- a/templates/src/main/java/com/demcha/compose/document/templates/core/theme/Typography.java +++ b/templates/src/main/java/com/demcha/compose/document/templates/core/theme/Typography.java @@ -404,4 +404,33 @@ public static Typography proposalModern() { 11.0, // body (summary, sections, parties, cells — modern body) 1.3); // line spacing } + + /** + * Helvetica scale for the Modern Receipt preset — a fintech statement + * ladder, which inverts the usual one: on a receipt the largest thing on + * the page is the amount, not the title. + * + *

    So {@code sizeHeadline} carries the hero amount at 30pt and the + * document title takes the {@code entryTitle} slot at 14pt. Field values + * sit in {@code entryDate} — a receipt's right-hand column is the same + * kind of column a CV puts dates in — and the spaced-caps group headings + * and eyebrow labels take the small {@code banner} slot.

    + * + * @return a {@code Typography} scale for the Modern Receipt preset + */ + public static Typography receiptModern() { + return new Typography( + // Both slots name the family, not a face: every receipt style sets + // its own decoration, and naming HELVETICA_BOLD here would be the + // face arriving twice. + FontName.HELVETICA, FontName.HELVETICA, + 30.0, // headline (hero amount — the largest thing on the page) + 8.0, // contact (footer small print + support lines) + 8.0, // banner (spaced-caps group headings and eyebrow labels) + 14.0, // entry title (document title, party names) + 10.0, // entry date (field values in the right-hand column) + 8.8, // entry subtitle (captions under the amount and the title) + 9.6, // body (notes, address lines) + 1.35); // line spacing + } } diff --git a/templates/src/main/java/com/demcha/compose/document/templates/cv/components/ParagraphPrimitive.java b/templates/src/main/java/com/demcha/compose/document/templates/cv/components/ParagraphPrimitive.java index ccc07ff11..f440061fd 100644 --- a/templates/src/main/java/com/demcha/compose/document/templates/cv/components/ParagraphPrimitive.java +++ b/templates/src/main/java/com/demcha/compose/document/templates/cv/components/ParagraphPrimitive.java @@ -3,9 +3,9 @@ import com.demcha.compose.document.templates.core.text.MarkdownInline; import com.demcha.compose.document.dsl.SectionBuilder; +import com.demcha.compose.document.node.ListMarker; import com.demcha.compose.document.node.TextAlign; import com.demcha.compose.document.style.DocumentInsets; -import com.demcha.compose.document.style.DocumentTextIndent; import com.demcha.compose.document.style.DocumentTextStyle; import com.demcha.compose.document.templates.core.theme.BrandTheme; @@ -28,6 +28,17 @@ */ final class ParagraphPrimitive { + /** + * The gap between a bullet and its text, as a share of the type size. + * + *

    A quarter of the size, which is what the trailing space inside + * {@code Decoration.bulletGlyph} was approximating — at the sizes these sheets + * set, one space of their body faces measures within a fraction of a point of + * it. A share rather than a value, so the gap follows the type instead of + * needing one number per theme.

    + */ + private static final double BULLET_GAP_EM = 0.25; + private ParagraphPrimitive() { } @@ -40,26 +51,92 @@ static void writeBody(SectionBuilder host, String text, DocumentTextStyle style, BrandTheme theme) { write(host, text, style, DocumentInsets.top((float) theme.spacing().paragraphMarginTop()), - theme.typography().bodyLineSpacing(), - null); + theme.typography().bodyLineSpacing()); } /** - * Body-style paragraph plus a bullet glyph + hanging indent. The - * glyph is supplied by the caller (typically - * {@code theme.decoration().bulletGlyph()} or - * {@code theme.decoration().stackedIndent()} for the stacked - * second line). + * One bulleted row, as a list of one item. + * + *

    A real list, so the marker sits in its own measured column and every + * visual line of the row — the ones it wraps onto, the ones that continue on + * a later page — starts at one content origin. It was a paragraph with the + * glyph as {@code bulletOffset} under {@code DocumentTextIndent.ALL_LINES}, + * which indents continuation lines with a run of spaces measured to clear the + * glyph; a whole number of spaces rarely equals a bullet, and that + * approximation is what the marker column replaces.

    + * + *

    The content is inline runs, so the bold label of a {@code label: body} + * row is part of one item rather than a row of two columns pretending to be + * one.

    */ static void writeBulleted(SectionBuilder host, String text, DocumentTextStyle style, String bulletGlyph, DocumentInsets margin, BrandTheme theme) { - write(host, text, style, - margin, - theme.typography().bodyLineSpacing(), - bulletGlyph); + host.addList(list -> list + .textStyle(style) + .align(TextAlign.LEFT) + .lineSpacing(theme.typography().bodyLineSpacing()) + .marker(bulletGlyph) + .hangingIndent(true) + .markerGap(style.size() * BULLET_GAP_EM) + .normalizeMarkers(false) + .margin(margin) + .addItem(rich -> MarkdownInline.append(rich, text, style))); + } + + /** + * A bulleted name with its body hanging under it — the two lines of a + * stacked row, as one list. + * + *

    The body is a markerless child of the name's item, which is what + * makes it begin where the name's text begins: a child's marker column opens + * at its parent's content origin, and a child with no marker takes no marker + * width and no gap, so its content starts exactly there. That is the + * alignment the row is drawn for and could not state.

    + * + *

    It was two paragraphs, the second prefixed with + * {@code Decoration.stackedIndent}, whose own contract is that it "must + * visually occupy the same width as bulletGlyph". Two spaces do not: measured + * on the canonical document the body began between 0.720pt and 3.078pt to the + * left of the name above it, the amount depending on the theme's type size. + * The indent is geometry now, so the two agree by construction.

    + */ + static void writeBulletedPair(SectionBuilder host, + String name, String body, + DocumentTextStyle nameStyle, + DocumentTextStyle bodyStyle, + String bulletGlyph, + DocumentInsets margin, + BrandTheme theme) { + host.addList(list -> { + // The list's style is the name's, because the marker is drawn in it and + // the bullet in front of a bold name was bold before this. Both items + // carry their own runs, so the body is unaffected by the choice. + list.textStyle(nameStyle) + .align(TextAlign.LEFT) + .lineSpacing(theme.typography().bodyLineSpacing()) + .marker(bulletGlyph) + .markerFor(1, ListMarker.none()) + .hangingIndent(true) + .markerGap(nameStyle.size() * BULLET_GAP_EM) + .normalizeMarkers(false) + // The name and the body were two children of the host section, + // so the host's own spacing sat between them. One list is one + // child, so that gap is stated here or the row closes up — and + // this change is meant to move the body sideways, not to + // re-space the sheet. + .itemSpacing(theme.spacing().sectionBodySpacing()) + .margin(margin); + if (body == null || body.isBlank()) { + list.addItem(rich -> MarkdownInline.append(rich, name, nameStyle)); + return; + } + list.addItem(rich -> MarkdownInline.append(rich, name, nameStyle), + child -> child.addItem( + rich -> MarkdownInline.append(rich, body, bodyStyle))); + }); } /** @@ -69,7 +146,7 @@ static void writeBulleted(SectionBuilder host, String text, */ static void writeSubtitle(SectionBuilder host, String text, DocumentTextStyle style) { - write(host, text, style, DocumentInsets.zero(), null, null); + write(host, text, style, DocumentInsets.zero(), null); } /** @@ -77,16 +154,18 @@ static void writeSubtitle(SectionBuilder host, String text, * that need a one-off combination not covered by the convenience * methods above. * + *

    It used to take a bullet glyph too, applied as {@code bulletOffset} + * under {@code DocumentTextIndent.ALL_LINES}. Bulleted rows are lists now, + * so nothing passed one and the branch was reachable only with null.

    + * * @param margin paragraph margin * @param lineSpacing optional lineSpacing override; null = use * engine default - * @param bulletGlyph optional bullet prefix; null = no bullet */ static void write(SectionBuilder host, String text, DocumentTextStyle style, DocumentInsets margin, - Double lineSpacing, - String bulletGlyph) { + Double lineSpacing) { host.addParagraph(p -> { p.textStyle(style) .align(TextAlign.LEFT) @@ -95,10 +174,6 @@ static void write(SectionBuilder host, String text, if (lineSpacing != null) { p.lineSpacing(lineSpacing); } - if (bulletGlyph != null) { - p.bulletOffset(bulletGlyph) - .indentStrategy(DocumentTextIndent.ALL_LINES); - } }); } } diff --git a/templates/src/main/java/com/demcha/compose/document/templates/cv/components/RowRenderer.java b/templates/src/main/java/com/demcha/compose/document/templates/cv/components/RowRenderer.java index c52daff12..785d6f271 100644 --- a/templates/src/main/java/com/demcha/compose/document/templates/cv/components/RowRenderer.java +++ b/templates/src/main/java/com/demcha/compose/document/templates/cv/components/RowRenderer.java @@ -61,27 +61,22 @@ private static void inline(SectionBuilder section, CvRow row, } /** - * Renders the row as two stacked paragraphs: bullet + bold label - * on line 1, body indented under the label on line 2. Both - * paragraphs draw through {@link ParagraphPrimitive#writeBulleted} - * so only the bullet glyph / margin differs. + * Renders the row as a bulleted bold name with its body hanging under the + * name rather than under the bullet. + * + *

    The two are one list: the name is its item and the body a markerless + * child of that item, so the body begins at the name's own content origin. + * {@code Decoration.stackedIndent} is no longer read for this — a run of + * spaces cannot equal a measured marker column, which is exactly why the + * body used to start a little to the left of the name it hangs under.

    */ private static void stacked(SectionBuilder section, CvRow row, BrandTheme theme) { - DocumentTextStyle base = theme.bodyStyle(); - DocumentTextStyle nameStyle = theme.bodyBoldStyle(); - DocumentInsets topMargin = DocumentInsets.top( - (float) theme.spacing().paragraphMarginTop()); - - // Line 1 — bullet + bold name. - ParagraphPrimitive.writeBulleted(section, row.label(), nameStyle, - theme.decoration().bulletGlyph(), topMargin, theme); - - if (row.body().isBlank()) { - return; - } - // Line 2 — body indented under name (not under bullet). - ParagraphPrimitive.writeBulleted(section, row.body(), base, - theme.decoration().stackedIndent(), DocumentInsets.zero(), theme); + ParagraphPrimitive.writeBulletedPair(section, + row.label(), row.body(), + theme.bodyBoldStyle(), theme.bodyStyle(), + theme.decoration().bulletGlyph(), + DocumentInsets.top((float) theme.spacing().paragraphMarginTop()), + theme); } /** diff --git a/templates/src/main/java/com/demcha/compose/document/templates/cv/data/CvEntry.java b/templates/src/main/java/com/demcha/compose/document/templates/cv/data/CvEntry.java index 7e915cc72..dc9f2aab3 100644 --- a/templates/src/main/java/com/demcha/compose/document/templates/cv/data/CvEntry.java +++ b/templates/src/main/java/com/demcha/compose/document/templates/cv/data/CvEntry.java @@ -1,15 +1,20 @@ package com.demcha.compose.document.templates.cv.data; +import java.util.List; import java.util.Objects; /** * Timeline-style entry used inside an {@link EntriesSection}. Covers - * both Education and Professional Experience — they share the same - * four fields so authors don't have to learn two record types. + * Education, Professional Experience, Projects and anything else a preset + * lays out as a dated block — they share the same fields so authors don't + * have to learn a record type per section. * *

    Blank fields are honoured: a blank {@code date} omits the date * column, a blank {@code subtitle} drops the italic line, a blank - * {@code body} drops the description paragraph.

    + * {@code body} drops the description paragraph, a blank {@code place} + * drops the location, a blank {@code icon} leaves the entry unmarked, and a + * blank {@code link} leaves its title plain. A preset draws only what it has + * somewhere to put.

    * * @param title bold heading on the left (job title, degree) * @param subtitle italic subtitle on the line below (employer, @@ -19,19 +24,187 @@ * blank removes the date column * @param body full-width prose paragraph beneath the subtitle; * may contain inline markdown + * @param place where this happened — a city, a campus, "Remote". The + * designs that show it set it beside the employer or the + * years rather than inside them, which is why it is its own + * field; blank when absent + * @param icon token naming the mark a preset draws for this entry. The + * vocabulary is preset-scoped — each preset packages its own + * set — so a token means something only to the preset that + * declares it, and the presets that draw no marks ignore it; + * blank when absent + * @param link where this entry points — a repository, a case study, a + * company. The presets that honour it make the title a link + * in the rendered PDF, which costs the layout nothing: a + * link is an annotation, not ink. Blank when absent */ -public record CvEntry(String title, String subtitle, String date, String body) { +public record CvEntry(String title, String subtitle, String date, String body, + String place, String icon, String link) { /** - * Validates that every field is non-null and that {@code title} is non-blank. + * Validates that the four original fields are non-null and that + * {@code title} is non-blank, treating a null {@code place}, + * {@code icon} or {@code link} as absent. */ public CvEntry { Objects.requireNonNull(title, "title"); Objects.requireNonNull(subtitle, "subtitle"); Objects.requireNonNull(date, "date"); Objects.requireNonNull(body, "body"); + place = place == null ? "" : place; + icon = icon == null ? "" : icon; + link = link == null ? "" : link; if (title.isBlank()) { throw new IllegalArgumentException("title must not be blank"); } } + + /** + * Backward-compatible constructor for callers that predate the link. + * + * @param title bold heading on the left + * @param subtitle subtitle on the line below; blank collapses it + * @param date date column next to the title; blank removes it + * @param body prose paragraph beneath the subtitle + * @param place where this happened; blank when absent + * @param icon the mark a preset draws for this entry; blank when absent + */ + public CvEntry(String title, String subtitle, String date, String body, + String place, String icon) { + this(title, subtitle, date, body, place, icon, ""); + } + + /** + * Backward-compatible constructor for callers that predate the location + * and the mark. The entry simply carries neither. + * + * @param title bold heading on the left + * @param subtitle subtitle on the line below; blank collapses it + * @param date date column next to the title; blank removes it + * @param body prose paragraph beneath the subtitle + */ + public CvEntry(String title, String subtitle, String date, String body) { + this(title, subtitle, date, body, "", "", ""); + } + + /** + * Creates a fluent builder, which is the readable way to reach the + * optional fields without counting positions. + * + * @param title the entry's heading (required, non-blank) + * @return new fluent builder + * @since 2.4.0 + */ + public static Builder builder(String title) { + return new Builder(title); + } + + /** + * Mutable builder for {@link CvEntry}. Every field but the title starts + * blank, so an entry names only what it has. + * + * @since 2.4.0 + */ + public static final class Builder { + private final String title; + private String subtitle = ""; + private String date = ""; + private String body = ""; + private String place = ""; + private String icon = ""; + private String link = ""; + + private Builder(String title) { + this.title = title; + } + + /** + * Sets the subtitle — the employer, the institution, the stack. + * + * @param value the subtitle; null becomes blank + * @return this builder for chaining + */ + public Builder subtitle(String value) { + this.subtitle = value == null ? "" : value; + return this; + } + + /** + * Sets the date column. + * + * @param value the date text; null becomes blank + * @return this builder for chaining + */ + public Builder date(String value) { + this.date = value == null ? "" : value; + return this; + } + + /** + * Sets the prose body. + * + * @param value the body; null becomes blank + * @return this builder for chaining + */ + public Builder body(String value) { + this.body = value == null ? "" : value; + return this; + } + + /** + * Sets the prose body from one line per element — the shape the + * presets that draw a bulleted entry read. + * + * @param lines the lines, in order; null becomes blank + * @return this builder for chaining + */ + public Builder body(List lines) { + this.body = lines == null ? "" : String.join("\n", lines); + return this; + } + + /** + * Sets where this happened. + * + * @param value the location; null becomes blank + * @return this builder for chaining + */ + public Builder place(String value) { + this.place = value == null ? "" : value; + return this; + } + + /** + * Sets the mark this entry asks for, in the vocabulary of the preset + * that will draw it. + * + * @param value the icon token; null becomes blank + * @return this builder for chaining + */ + public Builder icon(String value) { + this.icon = value == null ? "" : value; + return this; + } + + /** + * Sets where this entry points. The presets that honour it make the + * title a link. + * + * @param value the target URL; null becomes blank + * @return this builder for chaining + */ + public Builder link(String value) { + this.link = value == null ? "" : value; + return this; + } + + /** + * Builds the immutable {@link CvEntry}. + * + * @return the assembled entry + */ + public CvEntry build() { + return new CvEntry(title, subtitle, date, body, place, icon, link); + } + } } diff --git a/templates/src/main/java/com/demcha/compose/document/templates/cv/data/CvIdentity.java b/templates/src/main/java/com/demcha/compose/document/templates/cv/data/CvIdentity.java index 4ae2135bd..e8c05b525 100644 --- a/templates/src/main/java/com/demcha/compose/document/templates/cv/data/CvIdentity.java +++ b/templates/src/main/java/com/demcha/compose/document/templates/cv/data/CvIdentity.java @@ -1,5 +1,6 @@ package com.demcha.compose.document.templates.cv.data; +import com.demcha.compose.document.image.DocumentImageData; import com.demcha.compose.document.templates.core.identity.Contact; import com.demcha.compose.document.templates.core.identity.Link; import com.demcha.compose.document.templates.core.identity.PartyIdentity; @@ -7,6 +8,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Objects; +import java.util.Optional; /** * Top-of-document identity block — name, optional professional title, @@ -24,21 +26,41 @@ * that have a subtitle line; blank when absent * @param contact phone / email / address — all three required * @param links ordered list of optional outbound links; never null + * @param portrait optional photograph of the candidate, drawn by the presets + * that have somewhere to put one; empty otherwise */ public record CvIdentity(CvName name, String jobTitle, - Contact contact, List links) + Contact contact, List links, + Optional portrait) implements PartyIdentity { /** * Validates that {@code name} and {@code contact} are non-null, - * trims a null {@code jobTitle} to empty, and defensively copies - * {@code links} (null becomes an empty list). + * trims a null {@code jobTitle} to empty, defensively copies + * {@code links} (null becomes an empty list), and treats a null + * {@code portrait} as absent. */ public CvIdentity { Objects.requireNonNull(name, "name"); jobTitle = jobTitle == null ? "" : jobTitle.trim(); Objects.requireNonNull(contact, "contact"); links = links == null ? List.of() : List.copyOf(links); + portrait = portrait == null ? Optional.empty() : portrait; + } + + /** + * Backward-compatible constructor for callers that predate the + * optional portrait. The identity simply carries no photograph. + * + * @param name structured name with first / last required and an + * optional middle component + * @param jobTitle optional professional title; blank when absent + * @param contact phone / email / address — all three required + * @param links ordered list of optional outbound links; never null + */ + public CvIdentity(CvName name, String jobTitle, + Contact contact, List links) { + this(name, jobTitle, contact, links, Optional.empty()); } /** @@ -51,7 +73,7 @@ public record CvIdentity(CvName name, String jobTitle, * @param links ordered list of optional outbound links; never null */ public CvIdentity(CvName name, Contact contact, List links) { - this(name, "", contact, links); + this(name, "", contact, links, Optional.empty()); } /** @@ -91,6 +113,7 @@ public static final class Builder { private String jobTitle = ""; private Contact contact; private final List links = new ArrayList<>(); + private DocumentImageData portrait; private Builder() { } @@ -190,13 +213,29 @@ public Builder link(String label, String url) { return this; } + /** + * Sets the candidate's photograph. Only the presets with a place + * for one draw it; the rest ignore it, so a document carrying a + * portrait still renders through every preset in the family. + * + * @param value the image data; null leaves the identity without a + * photograph + * @return this builder for chaining + * @since 2.4.0 + */ + public Builder portrait(DocumentImageData value) { + this.portrait = value; + return this; + } + /** * Builds the immutable {@link CvIdentity}. * * @return the assembled identity block */ public CvIdentity build() { - return new CvIdentity(name, jobTitle, contact, links); + return new CvIdentity(name, jobTitle, contact, links, + Optional.ofNullable(portrait)); } } } diff --git a/templates/src/main/java/com/demcha/compose/document/templates/cv/data/CvSkill.java b/templates/src/main/java/com/demcha/compose/document/templates/cv/data/CvSkill.java index 6ab5e3b28..1f1590061 100644 --- a/templates/src/main/java/com/demcha/compose/document/templates/cv/data/CvSkill.java +++ b/templates/src/main/java/com/demcha/compose/document/templates/cv/data/CvSkill.java @@ -15,11 +15,18 @@ * empty level, so name-only renderers are completely unaffected by the * level channel.

    * + *

    The note is the level written out — "Native", "B2 – Upper + * Intermediate", "3 years" — for the designs that show a rating and name it + * in the same line. It is a separate channel from {@link #level} because a + * number cannot carry a wording and a wording cannot be measured into a + * meter; a preset draws whichever of the two it has room for, or both.

    + * * @param name non-blank skill label * @param level optional proficiency in {@code [0, 1]}; empty when * unspecified + * @param note the level as the document words it; blank when unspecified */ -public record CvSkill(String name, OptionalDouble level) { +public record CvSkill(String name, OptionalDouble level, String note) { /** * Validates that both fields are non-null, trims {@code name} and @@ -29,6 +36,7 @@ public record CvSkill(String name, OptionalDouble level) { public CvSkill { Objects.requireNonNull(name, "name"); Objects.requireNonNull(level, "level"); + note = note == null ? "" : note.trim(); name = name.trim(); if (name.isBlank()) { throw new IllegalArgumentException("name must not be blank"); @@ -42,6 +50,17 @@ public record CvSkill(String name, OptionalDouble level) { } } + /** + * Backward-compatible constructor for callers that predate the written-out + * level. + * + * @param name non-blank skill label + * @param level optional proficiency in {@code [0, 1]} + */ + public CvSkill(String name, OptionalDouble level) { + this(name, level, ""); + } + /** * Skill with no proficiency level. * @@ -49,7 +68,7 @@ public record CvSkill(String name, OptionalDouble level) { * @return a {@code CvSkill} carrying an empty level */ public static CvSkill of(String name) { - return new CvSkill(name, OptionalDouble.empty()); + return new CvSkill(name, OptionalDouble.empty(), ""); } /** @@ -61,6 +80,19 @@ public static CvSkill of(String name) { */ public static CvSkill of(String name, double level) { return new CvSkill(name, - OptionalDouble.of(Math.max(0.0, Math.min(1.0, level)))); + OptionalDouble.of(Math.max(0.0, Math.min(1.0, level))), ""); + } + + /** + * Skill with a proficiency level and the wording that names it. + * + * @param name non-blank skill label + * @param level proficiency, clamped into {@code [0, 1]} + * @param note the level as the document words it + * @return a {@code CvSkill} carrying both channels + */ + public static CvSkill of(String name, double level, String note) { + return new CvSkill(name, + OptionalDouble.of(Math.max(0.0, Math.min(1.0, level))), note); } } diff --git a/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/CharcoalGold.java b/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/CharcoalGold.java new file mode 100644 index 000000000..54938f986 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/CharcoalGold.java @@ -0,0 +1,235 @@ +package com.demcha.compose.document.templates.cv.presets; + +import com.demcha.compose.document.api.DocumentSession; +import com.demcha.compose.document.api.PageBackgroundFill; +import com.demcha.compose.document.templates.api.DocumentTemplate; +import com.demcha.compose.document.templates.cv.components.SectionLookup; +import com.demcha.compose.document.templates.cv.data.CvDocument; +import com.demcha.compose.document.templates.cv.data.CvSection; +import com.demcha.compose.document.templates.cv.data.EntriesSection; +import com.demcha.compose.document.templates.cv.data.ParagraphSection; +import com.demcha.compose.document.templates.cv.data.RowsSection; +import com.demcha.compose.document.templates.cv.data.SkillsSection; + +import java.util.List; +import java.util.Objects; + +import static com.demcha.compose.document.templates.cv.presets.CharcoalGoldStyles.MAIN_WEIGHT; +import static com.demcha.compose.document.templates.cv.presets.CharcoalGoldStyles.PAPER; +import static com.demcha.compose.document.templates.cv.presets.CharcoalGoldStyles.SIDEBAR; +import static com.demcha.compose.document.templates.cv.presets.CharcoalGoldStyles.SIDEBAR_WEIGHT; + +/** + * Charcoal Gold — a one-page CV in two columns: a charcoal sidebar carrying + * a ringed photograph, the contact channels, rated skills, languages and + * degrees, beside a paper column carrying a two-tone name, the summary, the + * roles held on a dated rail, a pair of credential columns and a closing + * strip of tools. + * + *

    The preset paints both columns as page backgrounds, so each reaches the + * foot of the sheet whatever it holds. It leaves the page size to the + * caller, unlike its ported siblings: the design is drawn on A4 and the + * geometry follows the page's own width, so a different page rescales rather + * than breaks.

    + * + *

    One page, and what happens past it

    + * + *

    The two columns are a single row, and a row is atomic — it cannot be + * split — so a CV longer than the sheet does not flow onto a second page: + * composing it raises {@code AtomicNodeTooLargeException}, naming the node + * and the height it needed. It draws no cap of its own, because a CV that + * quietly loses a job is worse than one that refuses to compose. + * {@link TimelineMinimal} is the preset in this package that splits its own + * columns across pages.

    + * + *

    How a document reaches its berth

    + * + *

    The columns are fixed, so the preset assigns each section a berth by + * title rather than reading {@link + * com.demcha.compose.document.templates.cv.data.Slot}. It has seven:

    + * + *
      + *
    • skills, a {@link SkillsSection}; languages, a {@link RowsSection}; + * and education, an {@link EntriesSection} — in the sidebar;
    • + *
    • the summary, a {@link ParagraphSection} of one paragraph per line, + * and experience, an {@link EntriesSection} — in the main column;
    • + *
    • certifications and achievements, {@link EntriesSection}s, as the + * credential pair;
    • + *
    • the tools, a {@link ParagraphSection} of one tool per line, as the + * closing strip.
    • + *
    + * + *

    A section whose title matches no berth is not drawn, a berth with no + * section takes its heading and its divider with it, and a berth is filled + * by the first section that names it. Languages are rows rather than + * levelled skills because this design writes the level out — "Native", + * "B2 – Upper Intermediate" — which a number could not carry back; their + * {@code RowStyle} is not read.

    + * + *

    The name, the photograph and the marks

    + * + *

    The masthead changes colour halfway through the name: the given name in + * ink, the family name larger and in gold. It reads them from + * {@code CvName}, which is why this preset wants a structured name — nothing + * else could tell it where to change.

    + * + *

    The photograph comes from {@code CvIdentity.portrait()} and is drawn to + * fill its disc, so a picture that is not square is cut rather than + * squeezed. An identity without one simply starts at the contact block.

    + * + *

    Each credential takes the mark its entry names in + * {@code CvEntry.icon()}, from this preset's own vocabulary — + * {@code certificate}, {@code trophy}, {@code growth}, {@code star} — and an + * unknown token is reported as a data error naming the set. An entry with no + * token is drawn without a mark. Every title the preset draws — a role, a + * credential — becomes a link when its entry carries + * {@code CvEntry.link()}.

    + * + *

    A skill's rating is drawn as five dots, of which its level fills the + * nearest whole number; a skill the document leaves unlevelled draws no + * rating rather than five empty dots. The phone, the email and each link are + * reachable from the PDF, with the {@code tel:} and {@code mailto:} targets + * built from the values.

    + * + *

    Fonts

    + * + *

    The sheet is set in Lato. The templates artifact does not carry it — + * register it on the session, or the engine substitutes and the measured + * geometry here no longer matches the type sitting in it.

    + * + *

    Usage:

    + *
    {@code
    + * DocumentTemplate template = CharcoalGold.create();
    + * template.compose(session, cv);
    + * }
    + */ +public final class CharcoalGold { + + /** Stable identifier of this preset. */ + public static final String ID = "charcoal-gold"; + + /** Human-readable name of this preset. */ + public static final String DISPLAY_NAME = "Charcoal Gold"; + + /** + * The margin this preset expects: none. Both columns run to the paper + * edge and each carries its own padding. + */ + public static final double RECOMMENDED_MARGIN = 0.0; + + private static final List SUMMARY_KEYS = + List.of("summary", "profile", "about"); + private static final List EXPERIENCE_KEYS = + List.of("experience", "employment", "work history", "career"); + private static final List EDUCATION_KEYS = + List.of("education", "qualifications"); + private static final List TOOL_KEYS = + List.of("tools", "stack", "software"); + private static final List SKILL_KEYS = + List.of("skills", "expertise", "competencies"); + private static final List LANGUAGE_KEYS = + List.of("languages", "language"); + private static final List CERTIFICATION_KEYS = + List.of("certifications", "certification", "licences", "licenses"); + private static final List ACHIEVEMENT_KEYS = + List.of("achievements", "achievement", "awards"); + + private CharcoalGold() { + } + + /** + * Creates the template. + * + * @return a template composing a {@link CvDocument} + */ + public static DocumentTemplate create() { + return new Template(); + } + + private record Template() implements DocumentTemplate { + + @Override + public String id() { + return ID; + } + + @Override + public String displayName() { + return DISPLAY_NAME; + } + + @Override + public void compose(DocumentSession document, CvDocument doc) { + Objects.requireNonNull(document, "document"); + Objects.requireNonNull(doc, "doc"); + List sections = doc.sections(); + + // Both fills are page backgrounds: a section is only as tall as + // what it holds, and each column has to reach the foot of the + // sheet whatever the other one carries. + document.pageBackgrounds(List.of( + PageBackgroundFill.leftColumn(SIDEBAR_WEIGHT, SIDEBAR), + PageBackgroundFill.rightColumn(MAIN_WEIGHT, PAPER))); + + SkillsSection skills = berth(sections, SkillsSection.class, SKILL_KEYS); + RowsSection languages = berth(sections, RowsSection.class, LANGUAGE_KEYS); + EntriesSection education = berth(sections, EntriesSection.class, EDUCATION_KEYS); + // Tools first: "Technical Tools" names no other berth, but the + // summary keys are broad and a document is free to title its + // closing strip loosely. + ParagraphSection tools = berth(sections, ParagraphSection.class, TOOL_KEYS); + ParagraphSection summary = berth(sections, ParagraphSection.class, SUMMARY_KEYS); + EntriesSection experience = berth(sections, EntriesSection.class, EXPERIENCE_KEYS); + EntriesSection certifications = + berth(sections, EntriesSection.class, CERTIFICATION_KEYS); + EntriesSection achievements = + berth(sections, EntriesSection.class, ACHIEVEMENT_KEYS); + + boolean hasCredentials = SectionLookup.hasContent(certifications) + || SectionLookup.hasContent(achievements); + boolean hasTools = SectionLookup.hasContent(tools); + + document.pageFlow(page -> page + .name("CharcoalGoldCv") + .spacing(0) + .addRow("Body", row -> { + row.name("Body"); + row.spacing(0); + row.weights(SIDEBAR_WEIGHT, MAIN_WEIGHT); + row.addSection("Sidebar", side -> + CharcoalGoldAside.compose(side, doc.identity(), + skills, languages, education)); + row.addSection("MainColumn", main -> { + CharcoalGoldMain.compose(main, doc.identity(), summary, experience); + if (hasCredentials) { + CharcoalGoldCredentials.renderCredentials(main, + certifications, achievements); + } + if (hasTools) { + CharcoalGoldCredentials.renderTools(main, tools); + } + }); + })); + } + + /** + * The first section of the wanted shape whose title names this berth, + * or {@code null} when the document fills no such berth. + */ + private static T berth(List sections, + Class type, + List keys) { + for (CvSection section : sections) { + if (!type.isInstance(section)) { + continue; + } + for (String key : keys) { + if (SectionLookup.titleContains(section.title(), key)) { + return type.cast(section); + } + } + } + return null; + } + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/CharcoalGoldAside.java b/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/CharcoalGoldAside.java new file mode 100644 index 000000000..6932d2ce3 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/CharcoalGoldAside.java @@ -0,0 +1,376 @@ +package com.demcha.compose.document.templates.cv.presets; + +import com.demcha.compose.document.dsl.ImageBuilder; +import com.demcha.compose.document.dsl.ParagraphBuilder; +import com.demcha.compose.document.dsl.SectionBuilder; +import com.demcha.compose.document.dsl.ShapeContainerBuilder; +import com.demcha.compose.document.image.DocumentImageData; +import com.demcha.compose.document.image.DocumentImageFitMode; +import com.demcha.compose.document.node.DocumentLinkOptions; +import com.demcha.compose.document.node.DocumentNode; +import com.demcha.compose.document.node.HorizontalAlign; +import com.demcha.compose.document.node.InlineImageAlignment; +import com.demcha.compose.document.node.RowVerticalAlign; +import com.demcha.compose.document.node.TextAlign; +import com.demcha.compose.document.style.ClipPolicy; +import com.demcha.compose.document.style.DocumentInsets; +import com.demcha.compose.document.style.DocumentRowColumn; +import com.demcha.compose.document.style.DocumentStroke; +import com.demcha.compose.document.templates.core.identity.ContactUri; +import com.demcha.compose.document.templates.core.identity.Link; +import com.demcha.compose.document.templates.cv.components.SectionLookup; +import com.demcha.compose.document.templates.cv.data.CvEntry; +import com.demcha.compose.document.templates.cv.data.CvIdentity; +import com.demcha.compose.document.templates.cv.data.CvRow; +import com.demcha.compose.document.templates.cv.data.CvSkill; +import com.demcha.compose.document.templates.cv.data.EntriesSection; +import com.demcha.compose.document.templates.cv.data.RowsSection; +import com.demcha.compose.document.templates.cv.data.SkillGroup; +import com.demcha.compose.document.templates.cv.data.SkillsSection; + +import java.util.ArrayList; +import java.util.List; + +import static com.demcha.compose.document.templates.cv.presets.CharcoalGoldStyles.ACCENT; +import static com.demcha.compose.document.templates.cv.presets.CharcoalGoldStyles.BLOCK_TO_DIVIDER; +import static com.demcha.compose.document.templates.cv.presets.CharcoalGoldStyles.BODY_SIZE; +import static com.demcha.compose.document.templates.cv.presets.CharcoalGoldStyles.CONTACT_PITCH; +import static com.demcha.compose.document.templates.cv.presets.CharcoalGoldStyles.DETAIL_SIZE; +import static com.demcha.compose.document.templates.cv.presets.CharcoalGoldStyles.DIVIDER_TO_HEADING; +import static com.demcha.compose.document.templates.cv.presets.CharcoalGoldStyles.EDUCATION_ENTRY_GAP; +import static com.demcha.compose.document.templates.cv.presets.CharcoalGoldStyles.EDUCATION_INDENT; +import static com.demcha.compose.document.templates.cv.presets.CharcoalGoldStyles.EDUCATION_LINE_PITCH; +import static com.demcha.compose.document.templates.cv.presets.CharcoalGoldStyles.HEADING_TO_BODY; +import static com.demcha.compose.document.templates.cv.presets.CharcoalGoldStyles.LANGUAGE_NAME_WEIGHT; +import static com.demcha.compose.document.templates.cv.presets.CharcoalGoldStyles.LANGUAGE_PITCH; +import static com.demcha.compose.document.templates.cv.presets.CharcoalGoldStyles.PHOTO_DIAMETER; +import static com.demcha.compose.document.templates.cv.presets.CharcoalGoldStyles.PHOTO_RING_WIDTH; +import static com.demcha.compose.document.templates.cv.presets.CharcoalGoldStyles.PHOTO_TO_CONTACT; +import static com.demcha.compose.document.templates.cv.presets.CharcoalGoldStyles.RATING_DOT_DIAMETER; +import static com.demcha.compose.document.templates.cv.presets.CharcoalGoldStyles.RATING_EMPTY; +import static com.demcha.compose.document.templates.cv.presets.CharcoalGoldStyles.RULE; +import static com.demcha.compose.document.templates.cv.presets.CharcoalGoldStyles.RULE_THICKNESS; +import static com.demcha.compose.document.templates.cv.presets.CharcoalGoldStyles.SIDEBAR; +import static com.demcha.compose.document.templates.cv.presets.CharcoalGoldStyles.SIDEBAR_INK; +import static com.demcha.compose.document.templates.cv.presets.CharcoalGoldStyles.SIDEBAR_PAD; +import static com.demcha.compose.document.templates.cv.presets.CharcoalGoldStyles.SIDEBAR_PAD_TOP; +import static com.demcha.compose.document.templates.cv.presets.CharcoalGoldStyles.SIDEBAR_RULE; +import static com.demcha.compose.document.templates.cv.presets.CharcoalGoldStyles.SKILL_BULLET_COLUMN; +import static com.demcha.compose.document.templates.cv.presets.CharcoalGoldStyles.SKILL_BULLET_DIAMETER; +import static com.demcha.compose.document.templates.cv.presets.CharcoalGoldStyles.SKILL_NAME_WEIGHT; +import static com.demcha.compose.document.templates.cv.presets.CharcoalGoldStyles.SKILL_PITCH; +import static com.demcha.compose.document.templates.cv.presets.CharcoalGoldStyles.gap; +import static com.demcha.compose.document.templates.cv.presets.CharcoalGoldStyles.textStyle; +import static com.demcha.compose.document.templates.cv.presets.CharcoalGoldWidgets.inlineGap; +import static com.demcha.compose.document.templates.cv.presets.CharcoalGoldWidgets.layeredRow; +import static com.demcha.compose.document.templates.cv.presets.CharcoalGoldWidgets.sidebarHeading; + +/** + * The charcoal left column: the ringed photograph, the contact channels + * behind their marks, the rated skills, the languages and the degrees, with + * a hairline between each block. + */ +final class CharcoalGoldAside { + + /** + * The contact heading. It is not read from the document because the + * channels are not a section — they come off the identity, which carries + * no title of its own. + */ + static final String CONTACT_HEADING = "CONTACT"; + + /** How many dots a skill rating is drawn out of. */ + static final int RATING_SCALE = 5; + + private CharcoalGoldAside() { + } + + static void compose(SectionBuilder side, + CvIdentity identity, + SkillsSection skills, + RowsSection languages, + EntriesSection education) { + side.name("Sidebar"); + side.spacing(0); + // No bottom padding: the sidebar's fill is a page background that + // already reaches the paper edge, so padding here would only shorten + // the column. + side.padding((float) SIDEBAR_PAD_TOP, (float) SIDEBAR_PAD, 0f, (float) SIDEBAR_PAD); + + identity.portrait().ifPresent(photo -> renderPhoto(side, photo)); + renderContact(side, identity, identity.portrait().isPresent()); + if (SectionLookup.hasContent(skills)) { + divider(side, "AfterContact"); + renderSkills(side, skills); + } + if (SectionLookup.hasContent(languages)) { + divider(side, "AfterSkills"); + renderLanguages(side, languages); + } + if (SectionLookup.hasContent(education)) { + divider(side, "AfterLanguages"); + renderEducation(side, education); + } + } + + private static void divider(SectionBuilder side, String name) { + side.addLine(line -> line + .name("Divider_" + name) + .fill() + .thickness(RULE_THICKNESS) + .color(SIDEBAR_RULE) + .margin(new DocumentInsets(BLOCK_TO_DIVIDER, 0, 0, 0))); + } + + // -- photograph ------------------------------------------------------ + + /** + * The photograph, clipped to a disc inside a pale ring. It is drawn with + * {@code COVER}, so a picture that is not square fills the circle and is + * cut rather than squeezed. + */ + private static void renderPhoto(SectionBuilder side, DocumentImageData photo) { + DocumentNode portrait = new ImageBuilder() + .name("PortraitImage") + .source(photo) + .size(PHOTO_DIAMETER, PHOTO_DIAMETER) + .fitMode(DocumentImageFitMode.COVER) + .build(); + DocumentNode circle = new ShapeContainerBuilder() + .name("ProfilePhoto") + .circle(PHOTO_DIAMETER) + .clipPolicy(ClipPolicy.CLIP_PATH) + .fillColor(SIDEBAR) + .stroke(DocumentStroke.of(RULE, PHOTO_RING_WIDTH)) + .center(portrait) + .build(); + side.addAligned(HorizontalAlign.CENTER, circle); + } + + // -- contact --------------------------------------------------------- + + /** + * The contact channels, in the order the design sets them: phone, email, + * address, then whatever links the identity carries. The phone and the + * email carry {@code tel:} and {@code mailto:} targets built from the + * values, and each link its own url. + */ + private static void renderContact(SectionBuilder side, CvIdentity identity, + boolean afterPhoto) { + side.addSection("Contact", block -> { + block.spacing(0); + block.margin(afterPhoto ? (float) PHOTO_TO_CONTACT : 0f, 0f, 0f, 0f); + sidebarHeading(block, CONTACT_HEADING); + List channels = channels(identity); + for (int i = 0; i < channels.size(); i++) { + Channel channel = channels.get(i); + boolean first = i == 0; + int index = i; + block.addParagraph(p -> { + p.name("Contact_" + index); + p.inlineSvgIcon(CharcoalGoldIcons.icon(channel.token()), + CharcoalGoldIcons.CONTACT_SIZE, InlineImageAlignment.CENTER); + inlineGap(p, textStyle(DETAIL_SIZE, SIDEBAR_INK, false)); + if (channel.href() == null) { + p.inlineText(channel.value(), textStyle(DETAIL_SIZE, SIDEBAR_INK, false)); + } else { + p.inlineText(channel.value(), textStyle(DETAIL_SIZE, SIDEBAR_INK, false), + new DocumentLinkOptions(channel.href())); + } + p.margin(first ? (float) HEADING_TO_BODY : 0f, 0f, + (float) gap(CONTACT_PITCH, DETAIL_SIZE), 0f); + }); + } + }); + } + + private static List channels(CvIdentity identity) { + List channels = new ArrayList<>(); + String phone = identity.contact().phone(); + channels.add(new Channel(CharcoalGoldIcons.PHONE, phone, ContactUri.tel(phone))); + String email = identity.contact().email(); + channels.add(new Channel(CharcoalGoldIcons.EMAIL, email, "mailto:" + email)); + channels.add(new Channel(CharcoalGoldIcons.LOCATION, + identity.contact().address(), null)); + for (Link link : identity.links()) { + channels.add(new Channel(linkToken(link), link.label(), link.url())); + } + return channels; + } + + /** + * The mark for a link: the network's own when it names one, the globe + * otherwise. + */ + private static String linkToken(Link link) { + String haystack = SectionLookup.normalize(link.label() + " " + link.url()); + return haystack.contains("linkedin") + ? CharcoalGoldIcons.LINKEDIN + : CharcoalGoldIcons.WEBSITE; + } + + private record Channel(String token, String value, String href) { + } + + // -- skills ---------------------------------------------------------- + + /** + * Skill rows: a gold bullet, the name, and a rating drawn as five dots + * of which the level fills the nearest whole number. + * + *

    A skill the document leaves unlevelled draws no rating at all, + * rather than five empty dots that read as a score of zero.

    + */ + private static void renderSkills(SectionBuilder side, SkillsSection skills) { + side.addSection("Skills", block -> { + block.spacing(0); + block.margin((float) DIVIDER_TO_HEADING, 0f, 0f, 0f); + sidebarHeading(block, skills.title()); + List items = flatten(skills); + for (int i = 0; i < items.size(); i++) { + CvSkill skill = items.get(i); + boolean first = i == 0; + int index = i; + layeredRow(block, "Skill_" + index, + first ? HEADING_TO_BODY : 0.0, + gap(SKILL_PITCH, DETAIL_SIZE), + row -> { + row.verticalAlign(RowVerticalAlign.CENTER); + row.columns( + DocumentRowColumn.fixed(SKILL_BULLET_COLUMN), + DocumentRowColumn.weight(SKILL_NAME_WEIGHT), + DocumentRowColumn.weight(1.0 - SKILL_NAME_WEIGHT)); + row.addParagraph(p -> p + .name("SkillBullet_" + index) + .align(TextAlign.CENTER) + // Styled even though it holds no text: a + // paragraph takes its line box from its + // type whether or not a glyph uses it, and + // the default size would set the row's + // pitch instead of this one. + .textStyle(textStyle(DETAIL_SIZE, SIDEBAR_INK, false)) + .dot(SKILL_BULLET_DIAMETER, ACCENT)); + row.addParagraph(p -> p + .name("SkillName_" + index) + .text(skill.name()) + .textStyle(textStyle(DETAIL_SIZE, SIDEBAR_INK, false))); + row.addParagraph(p -> renderRating(p, index, skill)); + }); + } + }); + } + + private static void renderRating(ParagraphBuilder p, int index, CvSkill skill) { + p.name("SkillRating_" + index); + p.align(TextAlign.RIGHT); + if (skill.level().isEmpty()) { + return; + } + long filled = Math.round(Math.max(0.0, Math.min(1.0, skill.level().getAsDouble())) + * RATING_SCALE); + for (int d = 0; d < RATING_SCALE; d++) { + p.dot(RATING_DOT_DIAMETER, d < filled ? ACCENT : RATING_EMPTY); + if (d < RATING_SCALE - 1) { + p.inlineText(" ", textStyle(DETAIL_SIZE, SIDEBAR_INK, false)); + } + } + } + + /** + * The section's skills as one list. This design sets no group headings, + * so a document that groups its skills gets them in the order the groups + * were declared. + */ + private static List flatten(SkillsSection section) { + List out = new ArrayList<>(); + for (SkillGroup group : section.groups()) { + out.addAll(group.entries()); + } + return out; + } + + // -- languages ------------------------------------------------------- + + /** + * Language rows: the name and the level in two columns, so the levels + * line up whatever the names measure. + * + *

    They are {@link CvRow}s rather than levelled skills because this + * design writes the level out — "Native", "B2 – Upper Intermediate" — + * which a number could not carry back. The {@code RowStyle} is not + * read.

    + */ + private static void renderLanguages(SectionBuilder side, RowsSection languages) { + side.addSection("Languages", block -> { + block.spacing(0); + block.margin((float) DIVIDER_TO_HEADING, 0f, 0f, 0f); + sidebarHeading(block, languages.title()); + List items = languages.rows(); + for (int i = 0; i < items.size(); i++) { + CvRow language = items.get(i); + boolean first = i == 0; + int index = i; + layeredRow(block, "Language_" + index, + first ? HEADING_TO_BODY : 0.0, + gap(LANGUAGE_PITCH, BODY_SIZE), + row -> { + row.weights(LANGUAGE_NAME_WEIGHT, 1.0 - LANGUAGE_NAME_WEIGHT); + row.addParagraph(p -> p + .name("LanguageName_" + index) + .text(language.label()) + .textStyle(textStyle(BODY_SIZE, SIDEBAR_INK, false))); + row.addParagraph(p -> p + .name("LanguageLevel_" + index) + .text(language.body()) + .textStyle(textStyle(BODY_SIZE, SIDEBAR_INK, false))); + }); + } + }); + } + + // -- education ------------------------------------------------------- + + /** + * Each degree as three lines: the qualification behind a gold dot, then + * the institution and the years indented under it. + */ + private static void renderEducation(SectionBuilder side, EntriesSection education) { + side.addSection("Education", block -> { + block.spacing(0); + block.margin((float) DIVIDER_TO_HEADING, 0f, 0f, 0f); + sidebarHeading(block, education.title()); + List items = education.entries(); + for (int i = 0; i < items.size(); i++) { + CvEntry entry = items.get(i); + boolean first = i == 0; + int index = i; + block.addSection("EducationEntry_" + index, e -> { + e.spacing(0); + e.margin(first ? (float) HEADING_TO_BODY : (float) EDUCATION_ENTRY_GAP, + 0f, 0f, 0f); + e.addParagraph(p -> { + p.name("Degree_" + index); + p.dot(RATING_DOT_DIAMETER, ACCENT); + inlineGap(p, textStyle(DETAIL_SIZE, SIDEBAR_INK, false)); + p.inlineText(entry.title(), textStyle(DETAIL_SIZE, SIDEBAR_INK, true)); + p.margin(0f, 0f, (float) gap(EDUCATION_LINE_PITCH, DETAIL_SIZE), 0f); + }); + e.addSection("EducationDetail_" + index, d -> { + d.spacing(0); + d.padding(0f, 0f, 0f, (float) EDUCATION_INDENT); + d.addParagraph(p -> p + .name("Institution_" + index) + .text(entry.subtitle()) + .textStyle(textStyle(DETAIL_SIZE, SIDEBAR_INK, false)) + .margin(0f, 0f, + (float) gap(EDUCATION_LINE_PITCH, DETAIL_SIZE), 0f)); + d.addParagraph(p -> p + .name("Period_" + index) + .text(entry.date()) + .textStyle(textStyle(DETAIL_SIZE, SIDEBAR_INK, false))); + }); + }); + } + }); + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/CharcoalGoldCredentials.java b/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/CharcoalGoldCredentials.java new file mode 100644 index 000000000..5331019bf --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/CharcoalGoldCredentials.java @@ -0,0 +1,170 @@ +package com.demcha.compose.document.templates.cv.presets; + +import com.demcha.compose.document.dsl.SectionBuilder; +import com.demcha.compose.document.node.TextAlign; +import com.demcha.compose.document.style.DocumentInsets; +import com.demcha.compose.document.templates.cv.components.SectionLookup; +import com.demcha.compose.document.templates.cv.data.CvEntry; +import com.demcha.compose.document.templates.cv.data.EntriesSection; +import com.demcha.compose.document.templates.cv.data.ParagraphSection; + +import java.util.List; + +import static com.demcha.compose.document.templates.cv.presets.CharcoalGoldStyles.CREDENTIALS_TO_TOOLS; +import static com.demcha.compose.document.templates.cv.presets.CharcoalGoldStyles.CREDENTIAL_ENTRY_GAP; +import static com.demcha.compose.document.templates.cv.presets.CharcoalGoldStyles.CREDENTIAL_GUTTER_WEIGHT; +import static com.demcha.compose.document.templates.cv.presets.CharcoalGoldStyles.CREDENTIAL_HALF_GUTTER; +import static com.demcha.compose.document.templates.cv.presets.CharcoalGoldStyles.CREDENTIAL_HEADING_TO_BODY; +import static com.demcha.compose.document.templates.cv.presets.CharcoalGoldStyles.CREDENTIAL_ICON_WEIGHT; +import static com.demcha.compose.document.templates.cv.presets.CharcoalGoldStyles.CREDENTIAL_LEFT_WEIGHT; +import static com.demcha.compose.document.templates.cv.presets.CharcoalGoldStyles.CREDENTIAL_LINE_GAP; +import static com.demcha.compose.document.templates.cv.presets.CharcoalGoldStyles.CREDENTIAL_RIGHT_WEIGHT; +import static com.demcha.compose.document.templates.cv.presets.CharcoalGoldStyles.DETAIL_SIZE; +import static com.demcha.compose.document.templates.cv.presets.CharcoalGoldStyles.ENTRIES_TO_RULE; +import static com.demcha.compose.document.templates.cv.presets.CharcoalGoldStyles.HEADING_TO_BODY; +import static com.demcha.compose.document.templates.cv.presets.CharcoalGoldStyles.INK; +import static com.demcha.compose.document.templates.cv.presets.CharcoalGoldStyles.RULE; +import static com.demcha.compose.document.templates.cv.presets.CharcoalGoldStyles.RULE_THICKNESS; +import static com.demcha.compose.document.templates.cv.presets.CharcoalGoldStyles.RULE_TO_CREDENTIALS; +import static com.demcha.compose.document.templates.cv.presets.CharcoalGoldStyles.SMALL_SIZE; +import static com.demcha.compose.document.templates.cv.presets.CharcoalGoldStyles.TOOL_SEPARATOR; +import static com.demcha.compose.document.templates.cv.presets.CharcoalGoldStyles.textStyle; +import static com.demcha.compose.document.templates.cv.presets.CharcoalGoldWidgets.layeredRow; +import static com.demcha.compose.document.templates.cv.presets.CharcoalGoldWidgets.mainHeading; +import static com.demcha.compose.document.templates.cv.presets.CharcoalGoldWidgets.plainHeading; + +/** + * The two closing blocks: the credentials in a pair of columns divided by a + * hairline, and the tools strip beneath them. + */ +final class CharcoalGoldCredentials { + + private CharcoalGoldCredentials() { + } + + /** + * The credential pair. + * + *

    Two cells, not three: the gutter is split down the middle and half + * given to each column, so the divider can be the right-hand column's own + * left border and take its height from that column's content. A third + * cell holding a line would need a height, and a height here could only + * be computed against today's text.

    + */ + static void renderCredentials(SectionBuilder main, + EntriesSection certifications, + EntriesSection achievements) { + boolean left = SectionLookup.hasContent(certifications); + boolean right = SectionLookup.hasContent(achievements); + main.addSection("Credentials", block -> { + block.spacing(0); + block.margin((float) ENTRIES_TO_RULE, 0f, 0f, 0f); + block.addLine(line -> line + .name("CredentialsRule") + .fill() + .thickness(RULE_THICKNESS) + .color(RULE)); + layeredRow(block, "CredentialColumns", RULE_TO_CREDENTIALS, 0.0, row -> { + row.spacing(0); + row.weights( + CREDENTIAL_LEFT_WEIGHT + CREDENTIAL_GUTTER_WEIGHT / 2.0, + CREDENTIAL_RIGHT_WEIGHT + CREDENTIAL_GUTTER_WEIGHT / 2.0); + row.addSection("Certifications", column -> { + column.padding(0f, (float) CREDENTIAL_HALF_GUTTER, 0f, 0f); + if (left) { + renderColumn(column, "Certification", certifications); + } + }); + row.addSection("Achievements", column -> { + // The divider is this column's left border, so it stops + // where the column's own content does. + column.accentLeft(RULE, RULE_THICKNESS); + column.padding(0f, 0f, 0f, (float) CREDENTIAL_HALF_GUTTER); + if (right) { + renderColumn(column, "Achievement", achievements); + } + }); + }); + }); + } + + /** One credential column: a plain heading, then a marked entry per row. */ + private static void renderColumn(SectionBuilder column, String prefix, + EntriesSection credentials) { + column.spacing(0); + column.keepTogether(); + plainHeading(column, credentials.title()); + List items = credentials.entries(); + for (int i = 0; i < items.size(); i++) { + CvEntry item = items.get(i); + boolean first = i == 0; + boolean last = i == items.size() - 1; + int index = i; + layeredRow(column, prefix + "_" + index, + first ? CREDENTIAL_HEADING_TO_BODY : CREDENTIAL_ENTRY_GAP, 0.0, + row -> { + row.spacing(0); + row.weights(CREDENTIAL_ICON_WEIGHT, 1.0 - CREDENTIAL_ICON_WEIGHT); + row.addSection(prefix + "Icon_" + index, cell -> { + cell.spacing(0); + if (!item.icon().isBlank()) { + cell.addSvgIcon(CharcoalGoldIcons.icon(item.icon()), + CharcoalGoldIcons.CREDENTIAL_SIZE); + } + }); + row.addSection(prefix + "Text_" + index, text -> { + text.spacing(0); + text.addParagraph(p -> { + p.name(prefix + "Name_" + index) + .textStyle(textStyle(DETAIL_SIZE, INK, true)); + CharcoalGoldText.title(p, item, + textStyle(DETAIL_SIZE, INK, true)); + p.margin(0f, 0f, (float) CREDENTIAL_LINE_GAP, 0f); + }); + text.addParagraph(p -> p + .name(prefix + "Issuer_" + index) + .text(item.subtitle()) + .textStyle(textStyle(SMALL_SIZE, INK, false)) + .margin(0f, 0f, (float) CREDENTIAL_LINE_GAP, 0f)); + text.addParagraph(p -> p + .name(prefix + "Year_" + index) + .text(item.date()) + .textStyle(textStyle(SMALL_SIZE, INK, false))); + }); + }); + if (!last) { + column.addLine(line -> line + .name(prefix + "Rule_" + index) + .fill() + .thickness(RULE_THICKNESS) + .color(RULE) + .margin(new DocumentInsets(CREDENTIAL_ENTRY_GAP, 0, 0, 0))); + } + } + } + + /** + * The closing strip: one line of tools, centred, with a pale pipe + * between neighbours. + */ + static void renderTools(SectionBuilder main, ParagraphSection tools) { + List items = CharcoalGoldText.lines(tools.body()); + main.addSection("TechnicalTools", block -> { + block.spacing(0); + block.margin((float) CREDENTIALS_TO_TOOLS, 0f, 0f, 0f); + block.keepWithNext(); + mainHeading(block, tools.title()); + block.addParagraph(p -> { + p.name("ToolsStrip"); + p.align(TextAlign.CENTER); + p.margin((float) HEADING_TO_BODY, 0f, 0f, 0f); + for (int i = 0; i < items.size(); i++) { + if (i > 0) { + p.inlineText(TOOL_SEPARATOR, textStyle(DETAIL_SIZE, RULE, false)); + } + p.inlineText(items.get(i), textStyle(DETAIL_SIZE, INK, false)); + } + }); + }); + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/CharcoalGoldIcons.java b/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/CharcoalGoldIcons.java new file mode 100644 index 000000000..40627e8cf --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/CharcoalGoldIcons.java @@ -0,0 +1,89 @@ +package com.demcha.compose.document.templates.cv.presets; + +import com.demcha.compose.document.svg.SvgIcon; + +import java.io.IOException; +import java.io.InputStream; +import java.io.UncheckedIOException; +import java.nio.charset.StandardCharsets; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Classpath loader for the Charcoal Gold CV marks — the five contact glyphs + * in the sidebar and the four credential marks in the closing columns. + * + *

    They ship inside the templates artifact under + * {@code templates/cv/charcoal-gold/icons/} as SVG, so they scale with the + * box they are drawn into rather than being resampled. Parsed icons are + * cached per token, so a sheet that repeats a mark reads each file once per + * JVM.

    + * + *

    The credential marks are the vocabulary a document names through + * {@code CvEntry.icon()}. It is scoped to this preset — another preset + * packages its own set — so {@link #ENTRY_TOKENS} is the list a document can + * choose from here.

    + */ +final class CharcoalGoldIcons { + + static final String PHONE = "phone"; + static final String EMAIL = "email"; + static final String LOCATION = "location"; + static final String WEBSITE = "website"; + static final String LINKEDIN = "linkedin"; + + private static final String ICON_ROOT = "/templates/cv/charcoal-gold/icons/"; + + /** Contact glyphs are set at the size of the text beside them. */ + static final double CONTACT_SIZE = 9.0; + + /** A credential mark stands on its own, so it is drawn larger. */ + static final double CREDENTIAL_SIZE = 15.0; + + /** The marks a document may name on a credential entry. */ + static final Set ENTRY_TOKENS = + Set.of("certificate", "trophy", "growth", "star"); + + private static final Set TOKENS = + Set.of(PHONE, EMAIL, LOCATION, WEBSITE, LINKEDIN, + "certificate", "trophy", "growth", "star"); + + private static final Map CACHE = new ConcurrentHashMap<>(); + + private CharcoalGoldIcons() { + } + + /** + * Reads one mark. + * + * @param token one of the packaged tokens + * @return the parsed icon + * @throws IllegalArgumentException when the token names no packaged mark, + * listing the ones a document may choose — an icon token is data, + * so a wrong one is a data error + */ + static SvgIcon icon(String token) { + if (!TOKENS.contains(token)) { + throw new IllegalArgumentException( + "Unknown charcoal gold CV icon token '" + token + + "'. This preset packages " + ENTRY_TOKENS.stream().sorted().toList() + + " for a credential mark."); + } + return CACHE.computeIfAbsent(token, CharcoalGoldIcons::read); + } + + private static SvgIcon read(String token) { + String resourcePath = ICON_ROOT + token + ".svg"; + try (InputStream input = CharcoalGoldIcons.class.getResourceAsStream(resourcePath)) { + if (input == null) { + throw new IllegalStateException( + "Missing charcoal gold CV icon: " + resourcePath); + } + return SvgIcon.parse(new String(input.readAllBytes(), StandardCharsets.UTF_8)); + } catch (IOException e) { + throw new UncheckedIOException( + "Failed to read charcoal gold CV icon: " + resourcePath, e); + } + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/CharcoalGoldMain.java b/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/CharcoalGoldMain.java new file mode 100644 index 000000000..d94a8882f --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/CharcoalGoldMain.java @@ -0,0 +1,244 @@ +package com.demcha.compose.document.templates.cv.presets; + +import com.demcha.compose.document.dsl.SectionBuilder; +import com.demcha.compose.document.node.DocumentNode; +import com.demcha.compose.document.node.LayerAlign; +import com.demcha.compose.document.node.ListMarker; +import com.demcha.compose.document.style.DocumentInsets; +import com.demcha.compose.document.style.DocumentRowColumn; +import com.demcha.compose.document.templates.cv.components.SectionLookup; +import com.demcha.compose.document.templates.cv.data.CvEntry; +import com.demcha.compose.document.templates.cv.data.CvIdentity; +import com.demcha.compose.document.templates.cv.data.EntriesSection; +import com.demcha.compose.document.templates.cv.data.ParagraphSection; + +import java.util.List; + +import static com.demcha.compose.document.templates.cv.presets.CharcoalGoldStyles.ACCENT; +import static com.demcha.compose.document.templates.cv.presets.CharcoalGoldStyles.BODY_SIZE; +import static com.demcha.compose.document.templates.cv.presets.CharcoalGoldStyles.DATE_WEIGHT; +import static com.demcha.compose.document.templates.cv.presets.CharcoalGoldStyles.DETAIL_SIZE; +import static com.demcha.compose.document.templates.cv.presets.CharcoalGoldStyles.EMPLOYER_SIZE; +import static com.demcha.compose.document.templates.cv.presets.CharcoalGoldStyles.EMPLOYER_TO_HIGHLIGHTS; +import static com.demcha.compose.document.templates.cv.presets.CharcoalGoldStyles.ENTRY_GAP; +import static com.demcha.compose.document.templates.cv.presets.CharcoalGoldStyles.ENTRY_INDENT; +import static com.demcha.compose.document.templates.cv.presets.CharcoalGoldStyles.EXPERIENCE_TO_ENTRIES; +import static com.demcha.compose.document.templates.cv.presets.CharcoalGoldStyles.HIGHLIGHT_ITEM_GAP; +import static com.demcha.compose.document.templates.cv.presets.CharcoalGoldStyles.HIGHLIGHT_LEADING; +import static com.demcha.compose.document.templates.cv.presets.CharcoalGoldStyles.INK; +import static com.demcha.compose.document.templates.cv.presets.CharcoalGoldStyles.MAIN_CONTENT_WIDTH; +import static com.demcha.compose.document.templates.cv.presets.CharcoalGoldStyles.MAIN_PAD_LEFT; +import static com.demcha.compose.document.templates.cv.presets.CharcoalGoldStyles.MAIN_PAD_RIGHT; +import static com.demcha.compose.document.templates.cv.presets.CharcoalGoldStyles.MAIN_PAD_TOP; +import static com.demcha.compose.document.templates.cv.presets.CharcoalGoldStyles.MARKER_DIAMETER; +import static com.demcha.compose.document.templates.cv.presets.CharcoalGoldStyles.MASTHEAD_RULE_THICKNESS; +import static com.demcha.compose.document.templates.cv.presets.CharcoalGoldStyles.MASTHEAD_RULE_WIDTH; +import static com.demcha.compose.document.templates.cv.presets.CharcoalGoldStyles.MASTHEAD_TO_TITLE; +import static com.demcha.compose.document.templates.cv.presets.CharcoalGoldStyles.NAME_FAMILY_SIZE; +import static com.demcha.compose.document.templates.cv.presets.CharcoalGoldStyles.NAME_GIVEN_SIZE; +import static com.demcha.compose.document.templates.cv.presets.CharcoalGoldStyles.JOB_TITLE_SIZE; +import static com.demcha.compose.document.templates.cv.presets.CharcoalGoldStyles.ROLE_SIZE; +import static com.demcha.compose.document.templates.cv.presets.CharcoalGoldStyles.ROLE_TO_EMPLOYER; +import static com.demcha.compose.document.templates.cv.presets.CharcoalGoldStyles.RULE; +import static com.demcha.compose.document.templates.cv.presets.CharcoalGoldStyles.RULE_THICKNESS; +import static com.demcha.compose.document.templates.cv.presets.CharcoalGoldStyles.RULE_TO_SUMMARY; +import static com.demcha.compose.document.templates.cv.presets.CharcoalGoldStyles.SMALL_SIZE; +import static com.demcha.compose.document.templates.cv.presets.CharcoalGoldStyles.SUMMARY_LEADING; +import static com.demcha.compose.document.templates.cv.presets.CharcoalGoldStyles.SUMMARY_TO_EXPERIENCE; +import static com.demcha.compose.document.templates.cv.presets.CharcoalGoldStyles.TITLE_TO_RULE; +import static com.demcha.compose.document.templates.cv.presets.CharcoalGoldStyles.textStyle; +import static com.demcha.compose.document.templates.cv.presets.CharcoalGoldStyles.tracked; +import static com.demcha.compose.document.templates.cv.presets.CharcoalGoldWidgets.mainHeading; + +/** + * The paper right column: the two-tone name over its tracked role, the + * summary, and the roles held on a dated rail. + */ +final class CharcoalGoldMain { + + private CharcoalGoldMain() { + } + + static void compose(SectionBuilder main, + CvIdentity identity, + ParagraphSection summary, + EntriesSection experience) { + main.name("MainColumn"); + main.spacing(0); + main.padding((float) MAIN_PAD_TOP, (float) MAIN_PAD_RIGHT, 0f, (float) MAIN_PAD_LEFT); + renderMasthead(main, identity); + if (SectionLookup.hasContent(summary)) { + renderSummary(main, summary); + } + if (SectionLookup.hasContent(experience)) { + renderExperience(main, experience); + } + } + + // -- masthead -------------------------------------------------------- + + /** + * The name in two tones and two sizes — the given name in ink, the + * family name larger and in gold — then the role tracked out, and the + * short gold rule that closes the block. + * + *

    The two halves come from {@code CvName}, which is why this preset + * wants a structured name rather than one string: nothing else could + * tell it where to change colour.

    + */ + private static void renderMasthead(SectionBuilder main, CvIdentity identity) { + main.addSection("Masthead", block -> { + block.spacing(0); + block.addParagraph(p -> p + .name("GivenName") + .text(identity.name().first()) + .textStyle(textStyle(NAME_GIVEN_SIZE, INK, false)) + .lineSpacing(1.0)); + block.addParagraph(p -> p + .name("FamilyName") + .text(identity.name().last()) + .textStyle(textStyle(NAME_FAMILY_SIZE, ACCENT, false)) + .lineSpacing(1.0) + .margin(0f, 0f, (float) MASTHEAD_TO_TITLE, 0f)); + block.addParagraph(p -> p + .name("JobTitle") + .text(tracked(identity.jobTitle())) + .textStyle(textStyle(JOB_TITLE_SIZE, INK, false)) + .margin(0f, 0f, (float) TITLE_TO_RULE, 0f)); + block.addLine(line -> line + .name("MastheadRule") + .horizontal(MASTHEAD_RULE_WIDTH) + .thickness(MASTHEAD_RULE_THICKNESS) + .color(ACCENT)); + }); + } + + // -- summary --------------------------------------------------------- + + /** + * The opening block, one paragraph per line of the section's body. This + * design gives it no heading, so the section's title is not drawn. + */ + private static void renderSummary(SectionBuilder main, ParagraphSection summary) { + main.addSection("Summary", block -> { + block.spacing(0); + block.margin((float) RULE_TO_SUMMARY, 0f, 0f, 0f); + List paragraphs = CharcoalGoldText.lines(summary.body()); + for (int i = 0; i < paragraphs.size(); i++) { + int index = i; + block.addParagraph(p -> p + .name("Summary_" + index) + .text(paragraphs.get(index)) + .textStyle(textStyle(BODY_SIZE, INK, false)) + .lineSpacing(SUMMARY_LEADING)); + } + }); + } + + // -- experience ------------------------------------------------------ + + /** + * The roles held: the dates in gold at the left, and the entry itself + * behind a hairline rail with a ringed marker capping each role. + */ + private static void renderExperience(SectionBuilder main, EntriesSection experience) { + main.addSection("Experience", block -> { + block.spacing(0); + block.margin((float) SUMMARY_TO_EXPERIENCE, 0f, 0f, 0f); + block.keepTogether(); + mainHeading(block, experience.title()); + renderExperienceTimeline(block, experience.entries()); + }); + } + + /** + * The roles, on one rail. + * + *

    Three columns now, where the row had two: the marker has a column of its own and the + * rail runs through its centre, so it can no longer be painted through. The leading column + * carries the date plus the half marker the old date cell lent it, which is what keeps the + * rail on the same line the body's border drew.

    + * + *

    Wrapped in a layer because a row nested in a row cell is refused and this sheet's main + * column is one — the same wrapper the entries already went through.

    + */ + private static void renderExperienceTimeline(SectionBuilder block, List entries) { + SectionBuilder holder = new SectionBuilder(); + holder.name("ExperienceRailHolder"); + holder.spacing(0); + holder.margin(new DocumentInsets(EXPERIENCE_TO_ENTRIES, 0, 0, 0)); + holder.addTimeline(timeline -> { + timeline.markerOnRail() + .connector(RULE, RULE_THICKNESS) + // The old date cell, less the indent the marker gap now supplies: a row + // spaces every pair of columns, so the gap is paid once before the axis + // and once after it. + .leadingColumn(DocumentRowColumn.fixed( + DATE_WEIGHT * MAIN_CONTENT_WIDTH + MARKER_DIAMETER - ENTRY_INDENT)) + .axisWidth(MARKER_DIAMETER) + .markerGap(ENTRY_INDENT - MARKER_DIAMETER / 2.0) + .gutter(0) + .spacing(ENTRY_GAP); + for (int i = 0; i < entries.size(); i++) { + CvEntry entry = entries.get(i); + int index = i; + timeline.entry(CharcoalGoldWidgets.ringMarker(index), e -> e + .leading(date -> date.addParagraph(p -> p + .name("Period_" + index) + .text(entry.date()) + .textStyle(textStyle(DETAIL_SIZE, ACCENT, false)))) + .content(body -> renderEntryBody(body, entry, index))); + } + }); + DocumentNode node = holder.build(); + // The timeline goes in through a layer stack rather than straight into the + // block. A timeline publishes a row, a row cannot nest inside a row cell, + // and this block can sit in one — the stack is what insulates it. Unwinding + // the stacks that turn out not to need it buys no capability, so they stay + // until that is done as its own deliberate cleanup. + block.addLayerStack(stack -> stack + .name("ExperienceRail") + .layer(node, LayerAlign.TOP_LEFT, 0)); + } + + private static void renderEntryBody(SectionBuilder body, CvEntry entry, int index) { + body.spacing(0); + body.addParagraph(p -> { + p.name("Role_" + index); + p.textStyle(textStyle(ROLE_SIZE, INK, true)); + p.margin(new DocumentInsets(0, 0, ROLE_TO_EMPLOYER, 0)); + CharcoalGoldText.title(p, entry, textStyle(ROLE_SIZE, INK, true)); + }); + body.addParagraph(p -> p + .name("Employer_" + index) + .text(employerLine(entry)) + .textStyle(textStyle(EMPLOYER_SIZE, INK, true)) + .margin(0f, 0f, (float) EMPLOYER_TO_HIGHLIGHTS, 0f)); + List highlights = CharcoalGoldText.lines(entry.body()); + if (!highlights.isEmpty()) { + body.addList(list -> list + .name("Highlights_" + index) + .items(highlights) + .marker(ListMarker.bullet()) + .textStyle(textStyle(SMALL_SIZE, INK, false)) + .itemSpacing(HIGHLIGHT_ITEM_GAP) + .lineSpacing(HIGHLIGHT_LEADING)); + } + } + + /** The employer, and the place beside it behind a raised dot. */ + private static String employerLine(CvEntry entry) { + return entry.place().isBlank() + ? entry.subtitle() + : entry.subtitle() + " · " + entry.place(); + } + + /** + * The role, with the ring positioned back over the rail beside it. + * + *

    The marker is placed from the title's own layer stack rather than + * from the row, so it sits on the role's first line whatever the title + * measures — and half its diameter left of the body's border, which puts + * the ring centred on the rail.

    + */ +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/CharcoalGoldStyles.java b/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/CharcoalGoldStyles.java new file mode 100644 index 000000000..a8c3eda27 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/CharcoalGoldStyles.java @@ -0,0 +1,200 @@ +package com.demcha.compose.document.templates.cv.presets; + +import com.demcha.compose.document.style.DocumentColor; +import com.demcha.compose.document.style.DocumentTextDecoration; +import com.demcha.compose.document.style.DocumentTextStyle; +import com.demcha.compose.document.templates.core.text.TextStyles; +import com.demcha.compose.font.FontName; + +/** + * The measured geometry, palette and type scale of the Charcoal Gold CV. + * + *

    Vertical rhythm here is authored as a pitch — the distance + * from one line's top to the next — rather than as a margin, which is how + * the design was drawn. {@link #gap} turns a pitch into the margin to set, + * by subtracting the line box the type already occupies.

    + */ +final class CharcoalGoldStyles { + + private CharcoalGoldStyles() { + } + + // -- page ------------------------------------------------------------ + + static final double PAGE_WIDTH = 595.276; + + static final double SIDEBAR_WEIGHT = 0.3197; + static final double MAIN_WEIGHT = 1.0 - SIDEBAR_WEIGHT; + + static final double SIDEBAR_PAD = 17.0; + static final double SIDEBAR_PAD_TOP = 24.3; + static final double SIDEBAR_CONTENT_WIDTH = + PAGE_WIDTH * SIDEBAR_WEIGHT - 2.0 * SIDEBAR_PAD; + + static final double MAIN_PAD_LEFT = 23.7; + static final double MAIN_PAD_RIGHT = 28.8; + static final double MAIN_PAD_TOP = 25.7; + static final double MAIN_CONTENT_WIDTH = + PAGE_WIDTH * MAIN_WEIGHT - MAIN_PAD_LEFT - MAIN_PAD_RIGHT; + + // -- type ------------------------------------------------------------ + + static final FontName FONT = FontName.LATO; + + static final double BODY_SIZE = 9.1; + static final double NAME_GIVEN_SIZE = 35.7; + static final double NAME_FAMILY_SIZE = 45.5; + static final double JOB_TITLE_SIZE = 11.6; + static final double MAIN_HEADING_SIZE = 11.0; + static final double SIDEBAR_HEADING_SIZE = 10.0; + static final double ROLE_SIZE = 10.5; + static final double EMPLOYER_SIZE = 6.9; + static final double DETAIL_SIZE = 7.8; + static final double SMALL_SIZE = 7.9; + + /** The share of its type size a line box occupies at the default leading. */ + static final double LINE_FACTOR = 1.2; + + // -- palette --------------------------------------------------------- + + static final DocumentColor SIDEBAR = DocumentColor.rgb(39, 45, 50); + static final DocumentColor PAPER = DocumentColor.rgb(254, 254, 254); + static final DocumentColor ACCENT = DocumentColor.rgb(186, 148, 88); + static final DocumentColor INK = DocumentColor.rgb(39, 45, 50); + static final DocumentColor SIDEBAR_INK = DocumentColor.rgb(251, 251, 251); + static final DocumentColor RULE = DocumentColor.rgb(218, 218, 219); + static final DocumentColor SIDEBAR_RULE = DocumentColor.rgb(61, 67, 69); + static final DocumentColor RATING_EMPTY = DocumentColor.rgb(119, 122, 125); + + // -- ornaments ------------------------------------------------------- + + static final double ACCENT_BAR_WIDTH = 1.7; + static final double HEADING_INDENT = 9.0; + static final double RULE_THICKNESS = 0.6; + static final double MARKER_DIAMETER = 6.2; + static final double RATING_DOT_DIAMETER = 4.5; + static final double SKILL_BULLET_DIAMETER = 2.3; + + /** + * The bullet gets a column of its own rather than an inline run plus + * counted spaces: its width is the design's own name indent, so neither + * the mark nor the name depends on how wide a space happens to be. + */ + static final double SKILL_BULLET_COLUMN = 9.5; + + static final double PHOTO_RING_WIDTH = 1.2; + static final double PHOTO_DIAMETER = SIDEBAR_CONTENT_WIDTH * 0.802; + + // -- sidebar rhythm -------------------------------------------------- + + static final double PHOTO_TO_CONTACT = 29.4; + static final double HEADING_TO_BODY = 12.0; + static final double BLOCK_TO_DIVIDER = 19.0; + static final double DIVIDER_TO_HEADING = 17.0; + static final double CONTACT_PITCH = 19.2; + static final double SKILL_PITCH = 16.6; + static final double LANGUAGE_PITCH = 16.9; + static final double EDUCATION_LINE_PITCH = 12.4; + static final double EDUCATION_ENTRY_GAP = 13.7; + static final double EDUCATION_INDENT = 12.4; + + static final double LANGUAGE_NAME_WEIGHT = 0.36; + static final double SKILL_NAME_WEIGHT = 0.72; + + // -- main rhythm ----------------------------------------------------- + + static final double MASTHEAD_TO_TITLE = 0.0; + static final double TITLE_TO_RULE = 12.0; + static final double MASTHEAD_RULE_WIDTH = MAIN_CONTENT_WIDTH * 0.093; + static final double MASTHEAD_RULE_THICKNESS = 1.4; + static final double RULE_TO_SUMMARY = 17.8; + static final double SUMMARY_LEADING = 1.38; + static final double SUMMARY_TO_EXPERIENCE = 28.4; + static final double EXPERIENCE_TO_ENTRIES = 19.0; + static final double ENTRY_GAP = 19.6; + static final double ENTRIES_TO_RULE = 18.0; + static final double CREDENTIALS_TO_TOOLS = 13.7; + + static final double DATE_WEIGHT = 0.194; + static final double MARKER_WEIGHT = MARKER_DIAMETER / MAIN_CONTENT_WIDTH; + static final double ENTRY_WEIGHT = 1.0 - DATE_WEIGHT - MARKER_WEIGHT; + static final double ENTRY_INDENT = 16.0; + static final double ROLE_TO_EMPLOYER = 3.0; + static final double EMPLOYER_TO_HIGHLIGHTS = 7.0; + static final double HIGHLIGHT_ITEM_GAP = 4.0; + static final double HIGHLIGHT_LEADING = 1.3; + + // -- credentials ----------------------------------------------------- + + static final double CREDENTIAL_LEFT_WEIGHT = 0.402; + static final double CREDENTIAL_GUTTER_WEIGHT = 0.124; + static final double CREDENTIAL_RIGHT_WEIGHT = + 1.0 - CREDENTIAL_LEFT_WEIGHT - CREDENTIAL_GUTTER_WEIGHT; + static final double CREDENTIAL_ICON_WEIGHT = 0.14; + static final double CREDENTIAL_HALF_GUTTER = + MAIN_CONTENT_WIDTH * CREDENTIAL_GUTTER_WEIGHT / 2.0; + static final double RULE_TO_CREDENTIALS = 22.0; + static final double CREDENTIAL_ENTRY_GAP = 6.5; + static final double CREDENTIAL_HEADING_TO_BODY = 17.3; + static final double CREDENTIAL_LINE_GAP = 2.0; + + /** The separator between two tools on the closing strip. */ + static final String TOOL_SEPARATOR = " | "; + + // -- helpers --------------------------------------------------------- + + static DocumentTextStyle textStyle(double size, DocumentColor color, boolean bold) { + return TextStyles.of(FONT, size, + bold ? DocumentTextDecoration.BOLD : DocumentTextDecoration.DEFAULT, color); + } + + /** + * The margin that realises an authored pitch: the distance between two + * line tops, less the line box the type above already fills. + * + * @param pitch the distance the design draws between line tops + * @param size the type size of the line above + * @return the margin to set, never negative + */ + static double gap(double pitch, double size) { + return Math.max(0.0, pitch - size * LINE_FACTOR); + } + + /** + * Letter-spacing by spaces rather than by sized runs: a space between + * letters and three between words. + * + *

    This design tracks only one line — the job title — and does it + * coarsely enough that real spaces reach it, which is simpler than the + * per-letter spacer runs the other ported sheets need.

    + * + * @param text the text to space out + * @return the spaced text + */ + static String tracked(String text) { + if (text == null || text.isEmpty()) { + return ""; + } + StringBuilder out = new StringBuilder(text.length() * 2); + boolean startOfWord = true; + for (int i = 0; i < text.length(); i++) { + char ch = text.charAt(i); + if (ch == ' ') { + out.append(" "); + startOfWord = true; + continue; + } + if (!startOfWord) { + out.append(' '); + } + out.append(ch); + startOfWord = false; + } + return out.toString(); + } + + /** Strips a title down to the letters and digits a node name can carry. */ + static String compact(String text) { + return text == null ? "" : text.replaceAll("[^A-Za-z0-9]", ""); + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/CharcoalGoldText.java b/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/CharcoalGoldText.java new file mode 100644 index 000000000..71d29f2e1 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/CharcoalGoldText.java @@ -0,0 +1,53 @@ +package com.demcha.compose.document.templates.cv.presets; + +import com.demcha.compose.document.dsl.ParagraphBuilder; +import com.demcha.compose.document.node.DocumentLinkOptions; +import com.demcha.compose.document.style.DocumentTextStyle; +import com.demcha.compose.document.templates.core.text.MarkdownInline; +import com.demcha.compose.document.templates.cv.data.CvEntry; + +import java.util.ArrayList; +import java.util.List; + +/** Turning model fields into the runs and lines this sheet draws. */ +final class CharcoalGoldText { + + private CharcoalGoldText() { + } + + /** + * One item per non-blank line — the family's way of carrying a list in a + * field the model types as a single string. The summary reads its own + * paragraphs this way too. + * + * @param body the field's text + * @return the lines, in order + */ + static List lines(String body) { + List out = new ArrayList<>(); + for (String line : body.split("\\R")) { + String clean = MarkdownInline.plainText(line).trim(); + if (!clean.isBlank()) { + out.add(clean); + } + } + return out; + } + + /** + * Writes an entry's title into a paragraph, as a link when the entry + * carries one. A link is an annotation rather than ink, so a linked + * title and a plain one are the same sheet. + * + * @param paragraph the paragraph being built + * @param entry the entry whose title and link to write + * @param style the style of the title + */ + static void title(ParagraphBuilder paragraph, CvEntry entry, DocumentTextStyle style) { + if (entry.link().isBlank()) { + paragraph.inlineText(entry.title(), style); + } else { + paragraph.inlineText(entry.title(), style, new DocumentLinkOptions(entry.link())); + } + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/CharcoalGoldWidgets.java b/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/CharcoalGoldWidgets.java new file mode 100644 index 000000000..38bda1ece --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/CharcoalGoldWidgets.java @@ -0,0 +1,141 @@ +package com.demcha.compose.document.templates.cv.presets; + +import com.demcha.compose.document.dsl.EllipseBuilder; +import com.demcha.compose.document.dsl.ParagraphBuilder; +import com.demcha.compose.document.dsl.RowBuilder; +import com.demcha.compose.document.dsl.SectionBuilder; +import com.demcha.compose.document.dsl.TimelineMarker; +import com.demcha.compose.document.node.DocumentNode; +import com.demcha.compose.document.node.LayerAlign; +import com.demcha.compose.document.node.RowVerticalAlign; +import com.demcha.compose.document.style.DocumentInsets; +import com.demcha.compose.document.style.DocumentRowColumn; +import com.demcha.compose.document.style.DocumentStroke; +import com.demcha.compose.document.style.DocumentTextStyle; + +import java.util.function.Consumer; + +import static com.demcha.compose.document.templates.cv.presets.CharcoalGoldStyles.ACCENT; +import static com.demcha.compose.document.templates.cv.presets.CharcoalGoldStyles.ACCENT_BAR_WIDTH; +import static com.demcha.compose.document.templates.cv.presets.CharcoalGoldStyles.HEADING_INDENT; +import static com.demcha.compose.document.templates.cv.presets.CharcoalGoldStyles.INK; +import static com.demcha.compose.document.templates.cv.presets.CharcoalGoldStyles.MAIN_HEADING_SIZE; +import static com.demcha.compose.document.templates.cv.presets.CharcoalGoldStyles.MARKER_DIAMETER; +import static com.demcha.compose.document.templates.cv.presets.CharcoalGoldStyles.PAPER; +import static com.demcha.compose.document.templates.cv.presets.CharcoalGoldStyles.RULE; +import static com.demcha.compose.document.templates.cv.presets.CharcoalGoldStyles.RULE_THICKNESS; +import static com.demcha.compose.document.templates.cv.presets.CharcoalGoldStyles.SIDEBAR_HEADING_SIZE; +import static com.demcha.compose.document.templates.cv.presets.CharcoalGoldStyles.SIDEBAR_INK; +import static com.demcha.compose.document.templates.cv.presets.CharcoalGoldStyles.compact; +import static com.demcha.compose.document.templates.cv.presets.CharcoalGoldStyles.textStyle; + +/** + * The pieces this sheet repeats: the heading with its gold bar, the ringed + * marker that caps a role, the two-space inline gap after a mark, and the + * row wrapper every measured row is built on. + */ +final class CharcoalGoldWidgets { + + private CharcoalGoldWidgets() { + } + + /** A sidebar heading: pale text behind the gold bar. */ + static void sidebarHeading(SectionBuilder block, String text) { + block.addSection("Heading_" + compact(text), heading -> heading + .spacing(0) + .accentLeft(ACCENT, ACCENT_BAR_WIDTH) + .padding(0f, 0f, 0f, (float) HEADING_INDENT) + .addParagraph(p -> p + .name("HeadingText_" + compact(text)) + .text(text) + .textStyle(textStyle(SIDEBAR_HEADING_SIZE, SIDEBAR_INK, false)))); + } + + /** + * A main-column heading: the same gold bar, with a hairline running from + * the words to the right edge. + * + *

    The rule is a weighted cell beside an {@code auto} one, so it takes + * whatever the words leave — no measurement needed, unlike a rule that + * has to be given a width.

    + */ + static void mainHeading(SectionBuilder block, String text) { + block.addSection("Heading_" + compact(text), heading -> { + heading.spacing(0); + heading.accentLeft(ACCENT, ACCENT_BAR_WIDTH); + heading.padding(0f, 0f, 0f, (float) HEADING_INDENT); + layeredRow(heading, "HeadingRow_" + compact(text), 0.0, 0.0, row -> { + row.verticalAlign(RowVerticalAlign.CENTER); + row.columns(DocumentRowColumn.auto(), DocumentRowColumn.weight(1.0)); + row.addParagraph(p -> p + .name("HeadingText_" + compact(text)) + .text(text) + .textStyle(textStyle(MAIN_HEADING_SIZE, INK, false))); + row.addLine(line -> line + .name("HeadingRule_" + compact(text)) + .fill() + .thickness(RULE_THICKNESS) + .color(RULE) + .margin(new DocumentInsets(0, 0, 0, HEADING_INDENT))); + }); + }); + } + + /** A main-column heading without the trailing rule, for the closing columns. */ + static void plainHeading(SectionBuilder block, String text) { + block.addSection("Heading_" + compact(text), heading -> heading + .spacing(0) + .accentLeft(ACCENT, ACCENT_BAR_WIDTH) + .padding(0f, 0f, 0f, (float) HEADING_INDENT) + .addParagraph(p -> p + .name("HeadingText_" + compact(text)) + .text(text) + .textStyle(textStyle(MAIN_HEADING_SIZE, INK, false)))); + } + + /** + * A row wrapped in a layer stack, which is where its vertical margins + * live. + * + *

    A row carries no margin of its own, so every measured row on this + * sheet is placed by the stack around it. That also gives the marker + * somewhere to be positioned from.

    + * + * @param parent the host section + * @param name node-name stem for the stack, its layer and the row + * @param marginTop the gap above + * @param marginBottom the gap below + * @param spec builds the row + */ + static void layeredRow(SectionBuilder parent, String name, double marginTop, + double marginBottom, Consumer spec) { + SectionBuilder layer = new SectionBuilder(); + layer.name(name + "Layer"); + layer.spacing(0); + layer.addRow(name, spec); + parent.addLayerStack(stack -> stack + .name(name + "Stack") + .margin(new DocumentInsets(marginTop, 0, marginBottom, 0)) + .layer(layer.build(), LayerAlign.TOP_LEFT, 0)); + } + + /** + * The hollow ring that caps a role on the experience rail, as a timeline marker. + * + *

    Declared rather than drawn with {@link com.demcha.compose.document.dsl.TimelineMarker#circle} + * so the ring keeps the node name the gates read it by.

    + */ + static TimelineMarker ringMarker(int index) { + return TimelineMarker.custom(MARKER_DIAMETER, MARKER_DIAMETER, + column -> column.addEllipse(ellipse -> ellipse + .name("MarkerRing_" + index) + .circle(MARKER_DIAMETER) + .fillColor(PAPER) + .stroke(DocumentStroke.of(INK, 0.8)))); + } + + /** The two spaces this design leaves between a mark and the text after it. */ + static void inlineGap(ParagraphBuilder paragraph, DocumentTextStyle style) { + paragraph.inlineText(" ", style); + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/EngineeringResume.java b/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/EngineeringResume.java index 40a190c87..07987b4cc 100644 --- a/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/EngineeringResume.java +++ b/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/EngineeringResume.java @@ -16,6 +16,8 @@ import com.demcha.compose.document.templates.cv.data.*; import com.demcha.compose.document.templates.core.theme.BrandTheme; +import com.demcha.compose.document.templates.core.identity.ContactUri; + import java.util.ArrayList; import java.util.List; import java.util.Locale; @@ -235,7 +237,8 @@ private static List contactParts(CvIdentity identity) { } List parts = new ArrayList<>(); addPart(parts, identity.contact().address(), null); - addPart(parts, identity.contact().phone(), null); + addPart(parts, identity.contact().phone(), + ContactUri.telLink(identity.contact().phone())); String email = identity.contact().email(); if (!email.isBlank()) { addPart(parts, email, diff --git a/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/Executive.java b/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/Executive.java index 95ae92d35..57b98d90a 100644 --- a/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/Executive.java +++ b/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/Executive.java @@ -20,6 +20,8 @@ import com.demcha.compose.document.templates.cv.widgets.SectionHeader; import com.demcha.compose.font.FontName; +import com.demcha.compose.document.templates.core.identity.ContactUri; + import java.util.List; import java.util.Locale; import java.util.Objects; @@ -145,14 +147,31 @@ private void addHeader(PageFlowBuilder flow, CvIdentity identity, .padding(DocumentInsets.zero()); Headline.uppercaseLeftAligned(section, identity.name().full(), theme, nameStyle()); - String meta = joinPipe(identity.contact().address(), - identity.contact().phone()); - if (!meta.isBlank()) { - section.addParagraph(paragraph -> paragraph - .text(meta) - .textStyle(metaStyle()) - .align(TextAlign.LEFT) - .margin(DocumentInsets.top(2))); + String address = identity.contact().address(); + String phone = identity.contact().phone(); + if (!address.isBlank() || !phone.isBlank()) { + // The number is its own run rather than part of one joined string, so + // it can carry a dialling target without the address being swept into + // it. The glyphs and their order are what the joined string gave. + section.addParagraph(paragraph -> { + paragraph.textStyle(metaStyle()) + .align(TextAlign.LEFT) + .margin(DocumentInsets.top(2)); + if (!address.isBlank()) { + paragraph.inlineText(address.trim(), metaStyle()); + } + if (!phone.isBlank()) { + if (!address.isBlank()) { + paragraph.inlineText(" | ", metaStyle()); + } + DocumentLinkOptions dial = ContactUri.telLink(phone); + if (dial == null) { + paragraph.inlineText(phone.trim(), metaStyle()); + } else { + paragraph.inlineText(phone.trim(), metaStyle(), dial); + } + } + }); } addLinkRow(section, identity); section.addLine(line -> line @@ -229,19 +248,5 @@ private DocumentTextStyle linkRowLinkStyle() { DocumentTextDecoration.UNDERLINE, ACCENT); } - - private static String joinPipe(String... parts) { - StringBuilder sb = new StringBuilder(); - for (String part : parts) { - if (part == null || part.isBlank()) { - continue; - } - if (sb.length() > 0) { - sb.append(" | "); - } - sb.append(part.trim()); - } - return sb.toString(); - } } } diff --git a/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/MidnightNavy.java b/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/MidnightNavy.java new file mode 100644 index 000000000..d7739bb18 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/MidnightNavy.java @@ -0,0 +1,224 @@ +package com.demcha.compose.document.templates.cv.presets; + +import com.demcha.compose.document.api.DocumentSession; +import com.demcha.compose.document.api.PageBackgroundFill; +import com.demcha.compose.document.style.DocumentInsets; +import com.demcha.compose.document.templates.api.DocumentTemplate; +import com.demcha.compose.document.templates.cv.components.SectionLookup; +import com.demcha.compose.document.templates.cv.data.CvDocument; +import com.demcha.compose.document.templates.cv.data.CvSection; +import com.demcha.compose.document.templates.cv.data.EntriesSection; +import com.demcha.compose.document.templates.cv.data.ParagraphSection; +import com.demcha.compose.document.templates.cv.data.SkillsSection; + +import java.util.List; +import java.util.Objects; + +import static com.demcha.compose.document.templates.cv.presets.MidnightNavyStyles.ASIDE_RATIO; +import static com.demcha.compose.document.templates.cv.presets.MidnightNavyStyles.MAIN_RATIO; +import static com.demcha.compose.document.templates.cv.presets.MidnightNavyStyles.NAVY; +import static com.demcha.compose.document.templates.cv.presets.MidnightNavyStyles.PAGE; + +/** + * Midnight Navy — a one-page CV on a full-height navy plate: an outlined + * monogram over the name and a tracked role, then contact, education, metered + * skills and dotted languages down the plate, beside a paper column carrying + * the summary, the roles held on a rail, three achievement discs and the + * certifications in divided columns. + * + *

    The plate is page chrome, not a fill

    + * + *

    The navy reaches the top, left and bottom paper edges, and a section's + * fill stops at its own box — so the plate is a page background sized by the + * same ratio the body row splits on, and the two cannot drift apart.

    + * + *

    Every horizontal pair goes through one wrapper

    + * + *

    A row nested directly in a row cell is refused, and both columns are row + * cells. Each of the sheet's horizontal pairs — a skill and its meter, a + * language and its dots, a title and its dates, a disc and its line, the + * certification columns — is therefore a row wrapped in a single layer of a + * stack.

    + * + *

    One page, strictly

    + * + *

    The body is a single row and a row is atomic, so this sheet does not + * paginate: a CV with more content than the design holds is refused with an + * {@code AtomicNodeTooLargeException} naming the node that could not fit, + * rather than being cut or silently clipped. That is the honest failure for a + * design whose two columns have to reach the same foot — splitting the row + * would leave the navy plate on one page and half the aside on the next. + * {@link TimelineMinimal} is the preset in this package built to flow.

    + * + *

    How a document reaches its berth

    + * + *

    The columns are fixed, so the preset assigns each section a berth by title + * rather than reading {@link + * com.demcha.compose.document.templates.cv.data.Slot}. It has seven:

    + * + *
      + *
    • education, an {@link EntriesSection} of a qualification, its + * institution and its years;
    • + *
    • skills and languages, {@link SkillsSection}s whose groups are + * flattened — the design draws no group names. A skill's level fills its + * meter; a language's fills the nearest of five dots;
    • + *
    • the summary, a {@link ParagraphSection};
    • + *
    • experience, an {@link EntriesSection} whose bodies are bullets;
    • + *
    • achievements, an {@link EntriesSection} whose entries carry a mark and + * one line of text — the design gives a card no heading over its line, so + * the entry's title is that line;
    • + *
    • certifications, an {@link EntriesSection} of a title, its issuer and + * its year, one to a column.
    • + *
    + * + *

    A section whose title matches no berth is not drawn, and a berth with no + * section takes its heading and its rule with it. The contact block's heading + * is the preset's own, because a document has no section to carry it — the + * channels come from {@code CvIdentity}.

    + * + *

    The marks and the links

    + * + *

    An achievement takes the mark its entry names in {@code CvEntry.icon()}, + * from this preset's own vocabulary, and an unknown token is reported as a data + * error naming the set.

    + * + *

    The email, the phone and each link are reachable from the PDF, with the + * {@code mailto:} and {@code tel:} targets built from the values. A link is + * drawn as its own label with the address behind it, so the plate's width does + * not depend on how long an address happens to be, and it takes the mark of the + * network it points at or the globe. A degree, a job title and a certification + * become links when their entry carries {@code CvEntry.link()}.

    + * + *

    Fonts

    + * + *

    The sheet is set in Barlow throughout. The templates artifact carries no + * fonts — register the family on the session, or the engine substitutes and the + * measured geometry no longer matches the type sitting in it. The role line's + * tracking is real spaces, so its width depends on the face's space advance + * specifically.

    + * + *

    Usage:

    + *
    {@code
    + * DocumentTemplate template = MidnightNavy.create();
    + * template.compose(session, cv);
    + * }
    + * + * @since 2.4.0 + */ +public final class MidnightNavy { + + /** Stable identifier of this preset. */ + public static final String ID = "midnight-navy"; + + /** Human-readable name of this preset. */ + public static final String DISPLAY_NAME = "Midnight Navy"; + + private static final List SUMMARY_KEYS = + List.of("summary", "profile", "about"); + private static final List EDUCATION_KEYS = + List.of("education", "qualifications"); + private static final List SKILL_KEYS = + List.of("skills", "expertise", "competencies"); + private static final List LANGUAGE_KEYS = + List.of("languages", "language"); + private static final List EXPERIENCE_KEYS = + List.of("experience", "employment", "work history", "career"); + private static final List ACHIEVEMENT_KEYS = + List.of("achievements", "awards", "highlights"); + private static final List CERTIFICATION_KEYS = + List.of("certifications", "certificates", "licences", "licenses"); + + private MidnightNavy() { + } + + /** + * Creates the template. + * + * @return a template composing a {@link CvDocument} + */ + public static DocumentTemplate create() { + return new Template(); + } + + private record Template() implements DocumentTemplate { + + @Override + public String id() { + return ID; + } + + @Override + public String displayName() { + return DISPLAY_NAME; + } + + @Override + public void compose(DocumentSession document, CvDocument doc) { + Objects.requireNonNull(document, "document"); + Objects.requireNonNull(doc, "doc"); + List sections = doc.sections(); + + ParagraphSection summary = berth(sections, ParagraphSection.class, SUMMARY_KEYS); + SkillsSection skills = berth(sections, SkillsSection.class, SKILL_KEYS); + SkillsSection languages = + berth(sections, SkillsSection.class, LANGUAGE_KEYS, skills); + EntriesSection education = berth(sections, EntriesSection.class, EDUCATION_KEYS); + EntriesSection experience = + berth(sections, EntriesSection.class, EXPERIENCE_KEYS, education); + EntriesSection achievements = berth(sections, EntriesSection.class, + ACHIEVEMENT_KEYS, education, experience); + EntriesSection certifications = berth(sections, EntriesSection.class, + CERTIFICATION_KEYS, education, experience, achievements); + + // Every length on the sheet is a share of the design's own grid, so + // the preset sets the page and its margins rather than following + // what the caller configured. + document.pageSize(PAGE); + document.margin(DocumentInsets.zero()); + document.pageBackgrounds(List.of(PageBackgroundFill.leftColumn(ASIDE_RATIO, NAVY))); + + document.pageFlow(page -> page + .name("MidnightNavyCv") + .padding(DocumentInsets.zero()) + .spacing(0) + .addRow("PageGrid", row -> { + row.spacing(0); + row.weights(ASIDE_RATIO, MAIN_RATIO); + row.addSection("Aside", column -> MidnightNavyAside.compose( + column, doc.identity(), education, skills, languages)); + row.addSection("Main", column -> MidnightNavyMain.compose( + column, summary, experience, achievements, certifications)); + })); + } + + /** + * The first section of the wanted shape whose title names this berth, + * skipping any another berth has already claimed, or {@code null} when + * the document fills no such berth. + */ + @SafeVarargs + private static T berth(List sections, Class type, + List keys, T... taken) { + for (CvSection section : sections) { + if (!type.isInstance(section) || isTaken(section, taken)) { + continue; + } + for (String key : keys) { + if (SectionLookup.titleContains(section.title(), key)) { + return type.cast(section); + } + } + } + return null; + } + + private static boolean isTaken(CvSection section, CvSection[] taken) { + for (CvSection claimed : taken) { + if (section == claimed) { + return true; + } + } + return false; + } + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/MidnightNavyAside.java b/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/MidnightNavyAside.java new file mode 100644 index 000000000..c059b9849 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/MidnightNavyAside.java @@ -0,0 +1,400 @@ +package com.demcha.compose.document.templates.cv.presets; + +import com.demcha.compose.document.dsl.LineBuilder; +import com.demcha.compose.document.dsl.ParagraphBuilder; +import com.demcha.compose.document.dsl.SectionBuilder; +import com.demcha.compose.document.dsl.ShapeBuilder; +import com.demcha.compose.document.dsl.ShapeContainerBuilder; +import com.demcha.compose.document.node.DocumentLinkOptions; +import com.demcha.compose.document.node.HorizontalAlign; +import com.demcha.compose.document.node.InlineImageAlignment; +import com.demcha.compose.document.node.LayerAlign; +import com.demcha.compose.document.node.RowVerticalAlign; +import com.demcha.compose.document.node.TextAlign; +import com.demcha.compose.document.style.DocumentInsets; +import com.demcha.compose.document.style.DocumentRowColumn; +import com.demcha.compose.document.style.DocumentStroke; +import com.demcha.compose.document.style.DocumentTextDecoration; +import com.demcha.compose.document.templates.core.identity.Contact; +import com.demcha.compose.document.templates.core.identity.ContactUri; +import com.demcha.compose.document.templates.core.identity.Link; +import com.demcha.compose.document.templates.cv.components.SectionLookup; +import com.demcha.compose.document.templates.cv.data.CvEntry; +import com.demcha.compose.document.templates.cv.data.CvIdentity; +import com.demcha.compose.document.templates.cv.data.CvSkill; +import com.demcha.compose.document.templates.cv.data.EntriesSection; +import com.demcha.compose.document.templates.cv.data.SkillGroup; +import com.demcha.compose.document.templates.cv.data.SkillsSection; + +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; +import java.util.OptionalDouble; + +import static com.demcha.compose.document.templates.cv.presets.MidnightNavyStyles.ASIDE_CONTENT_PAD; +import static com.demcha.compose.document.templates.cv.presets.MidnightNavyStyles.ASIDE_DIM; +import static com.demcha.compose.document.templates.cv.presets.MidnightNavyStyles.ASIDE_INNER_WIDTH; +import static com.demcha.compose.document.templates.cv.presets.MidnightNavyStyles.ASIDE_PAD_TOP; +import static com.demcha.compose.document.templates.cv.presets.MidnightNavyStyles.ASIDE_SMALL_SIZE; +import static com.demcha.compose.document.templates.cv.presets.MidnightNavyStyles.ASIDE_TEXT_SIZE; +import static com.demcha.compose.document.templates.cv.presets.MidnightNavyStyles.ASIDE_WIDTH; +import static com.demcha.compose.document.templates.cv.presets.MidnightNavyStyles.CONTACT_ICON_SIZE; +import static com.demcha.compose.document.templates.cv.presets.MidnightNavyStyles.CONTACT_ROW_GAP; +import static com.demcha.compose.document.templates.cv.presets.MidnightNavyStyles.CONTACT_RULE_WIDTH; +import static com.demcha.compose.document.templates.cv.presets.MidnightNavyStyles.EDUCATION_TITLE_SIZE; +import static com.demcha.compose.document.templates.cv.presets.MidnightNavyStyles.EDUCATION_YEARS_SIZE; +import static com.demcha.compose.document.templates.cv.presets.MidnightNavyStyles.FACE_SPACE_ADVANCE; +import static com.demcha.compose.document.templates.cv.presets.MidnightNavyStyles.LANGUAGE_DOTS; +import static com.demcha.compose.document.templates.cv.presets.MidnightNavyStyles.LANGUAGE_DOT_DIAMETER; +import static com.demcha.compose.document.templates.cv.presets.MidnightNavyStyles.LANGUAGE_DOT_PITCH; +import static com.demcha.compose.document.templates.cv.presets.MidnightNavyStyles.LANGUAGE_DOT_STROKE; +import static com.demcha.compose.document.templates.cv.presets.MidnightNavyStyles.LANGUAGE_GROUP_WIDTH; +import static com.demcha.compose.document.templates.cv.presets.MidnightNavyStyles.LANGUAGE_ROW_GAP; +import static com.demcha.compose.document.templates.cv.presets.MidnightNavyStyles.MONOGRAM_DIAMETER; +import static com.demcha.compose.document.templates.cv.presets.MidnightNavyStyles.MONOGRAM_PITCH; +import static com.demcha.compose.document.templates.cv.presets.MidnightNavyStyles.MONOGRAM_SIZE; +import static com.demcha.compose.document.templates.cv.presets.MidnightNavyStyles.MONOGRAM_STROKE; +import static com.demcha.compose.document.templates.cv.presets.MidnightNavyStyles.NAME_DIVIDER_WIDTH; +import static com.demcha.compose.document.templates.cv.presets.MidnightNavyStyles.NAME_SIZE; +import static com.demcha.compose.document.templates.cv.presets.MidnightNavyStyles.NAVY; +import static com.demcha.compose.document.templates.cv.presets.MidnightNavyStyles.ROLE_SIZE; +import static com.demcha.compose.document.templates.cv.presets.MidnightNavyStyles.SKILL_KNOB_HEIGHT; +import static com.demcha.compose.document.templates.cv.presets.MidnightNavyStyles.SKILL_KNOB_WIDTH; +import static com.demcha.compose.document.templates.cv.presets.MidnightNavyStyles.SKILL_ROW_GAP; +import static com.demcha.compose.document.templates.cv.presets.MidnightNavyStyles.SKILL_TRACK_THICKNESS; +import static com.demcha.compose.document.templates.cv.presets.MidnightNavyStyles.SKILL_TRACK_WIDTH; +import static com.demcha.compose.document.templates.cv.presets.MidnightNavyStyles.TRACK_DIM; +import static com.demcha.compose.document.templates.cv.presets.MidnightNavyStyles.WHITE; +import static com.demcha.compose.document.templates.cv.presets.MidnightNavyStyles.asideText; +import static com.demcha.compose.document.templates.cv.presets.MidnightNavyStyles.leading; +import static com.demcha.compose.document.templates.cv.presets.MidnightNavyStyles.px; +import static com.demcha.compose.document.templates.cv.presets.MidnightNavyStyles.style; + +/** + * The navy column: the monogram and name plate, then contact, education, skills + * and languages, each under a heading and its rule. + * + *

    The monogram is an outlined circle with no fill, so the plate shows + * through it, and the initials are anchored on its centre — they are content + * of the circle rather than a paragraph laid over it.

    + * + *

    A skill meter is three layers on one stack: the dim full-width track, the + * bright filled run, and the knob positioned along the track in proportion to + * the level. All three hang off {@link LayerAlign#CENTER_LEFT} so they share + * the track's axis; seating a layer at the stack's top instead draws one meter + * as three unaligned pieces.

    + */ +final class MidnightNavyAside { + + /** The heading the design gives the contact block, which no section carries. */ + static final String CONTACT_HEADING = "CONTACT"; + + private MidnightNavyAside() { + } + + /** + * Draws the column. + * + * @param column the aside cell + * @param identity whose CV this is + * @param education the degrees, or {@code null} + * @param skills the rated skills, or {@code null} + * @param languages the rated languages, or {@code null} + */ + static void compose(SectionBuilder column, CvIdentity identity, EntriesSection education, + SkillsSection skills, SkillsSection languages) { + // Only the top inset belongs to the plate: the monogram is centred on + // the plate's full width, not on the text column, so the horizontal + // inset is owned by the blocks under it instead. + column.spacing(0).padding(new DocumentInsets(ASIDE_PAD_TOP, 0, 0, 0)); + renderMonogram(column, identity); + renderNamePlate(column, identity); + renderContact(column, identity); + if (SectionLookup.hasContent(education)) { + renderEducation(column, education); + } + if (SectionLookup.hasContent(skills)) { + renderSkills(column, skills); + } + if (SectionLookup.hasContent(languages)) { + renderLanguages(column, languages); + } + } + + /** The outlined circle, carrying one initial per line of the name. */ + private static void renderMonogram(SectionBuilder column, CvIdentity identity) { + List initials = List.of( + initial(identity.name().first()), initial(identity.name().last())); + ShapeContainerBuilder circle = new ShapeContainerBuilder() + .name("Monogram") + .circle(MONOGRAM_DIAMETER) + .stroke(DocumentStroke.of(WHITE, MONOGRAM_STROKE)); + double top = -MONOGRAM_PITCH * (initials.size() - 1) / 2.0; + for (int i = 0; i < initials.size(); i++) { + circle.position(new ParagraphBuilder() + .name("MonogramLine" + (i + 1)) + .text(initials.get(i)) + .textStyle(style(MONOGRAM_SIZE, WHITE, DocumentTextDecoration.DEFAULT)) + .align(TextAlign.CENTER) + .build(), + 0, top + i * MONOGRAM_PITCH, LayerAlign.CENTER); + } + column.addAligned(HorizontalAlign.CENTER, circle.build()); + } + + private static String initial(String name) { + return name.isBlank() ? "" : name.strip().substring(0, 1).toUpperCase(Locale.ROOT); + } + + /** The name in two weights, the tracked role under it, and their divider. */ + private static void renderNamePlate(SectionBuilder column, CvIdentity identity) { + column.addParagraph(p -> p + .name("Name") + .inlineText(identity.name().first().toUpperCase(Locale.ROOT) + " ", + style(NAME_SIZE, WHITE, DocumentTextDecoration.DEFAULT)) + .inlineText(identity.name().last().toUpperCase(Locale.ROOT), + style(NAME_SIZE, WHITE, DocumentTextDecoration.BOLD)) + .align(TextAlign.CENTER) + .margin(new DocumentInsets(px(20.4), 0, 0, 0))); + + column.addParagraph(p -> p + .name("Role") + .text(MidnightNavyWidgets.tracked(identity.jobTitle().toUpperCase(Locale.ROOT))) + .textStyle(style(ROLE_SIZE, WHITE, DocumentTextDecoration.DEFAULT)) + .align(TextAlign.CENTER) + .margin(new DocumentInsets(px(13.1), 0, 0, 0))); + + MidnightNavyWidgets.nameDivider(column, NAME_DIVIDER_WIDTH, + (ASIDE_WIDTH - NAME_DIVIDER_WIDTH) / 2.0, px(21.7)); + } + + /** + * The contact block — a mark, then the value or the link's own label. + * + *

    The three channels are always drawn: a {@code Contact} rejects a blank + * field, so the triple is non-blank by construction. A link is drawn as its + * own label with the address behind it, so the column's width does not + * depend on how long an address happens to be.

    + */ + private static void renderContact(SectionBuilder column, CvIdentity identity) { + Contact contact = identity.contact(); + List channels = new ArrayList<>(); + channels.add(new Channel(MidnightNavyIcons.PHONE, contact.phone(), + ContactUri.tel(contact.phone()))); + channels.add(new Channel(MidnightNavyIcons.EMAIL, contact.email(), + "mailto:" + contact.email())); + channels.add(new Channel(MidnightNavyIcons.LOCATION, contact.address(), null)); + for (Link link : identity.links()) { + channels.add(new Channel(markFor(link), link.label(), link.url())); + } + + column.addSection("Contact", block -> { + block.spacing(0).padding(ASIDE_CONTENT_PAD) + .margin(new DocumentInsets(px(37.2), 0, 0, 0)); + MidnightNavyWidgets.asideHeading(block, CONTACT_HEADING, CONTACT_RULE_WIDTH, px(23.6)); + block.addSection("ContactRows", rows -> { + rows.spacing(CONTACT_ROW_GAP); + for (int i = 0; i < channels.size(); i++) { + Channel channel = channels.get(i); + int index = i; + rows.addParagraph(p -> { + p.name("Contact" + index); + p.inlineSvgIcon(MidnightNavyIcons.icon(channel.token()), + CONTACT_ICON_SIZE, InlineImageAlignment.CENTER); + p.inlineText(" ", asideText()); + if (channel.href() == null) { + p.inlineText(channel.label(), asideText()); + } else { + p.inlineText(channel.label(), asideText(), + new DocumentLinkOptions(channel.href())); + } + }); + } + }); + }); + } + + /** The degrees — the qualification, its institution and its years. */ + private static void renderEducation(SectionBuilder column, EntriesSection education) { + column.addSection("Education", block -> { + block.spacing(0).padding(ASIDE_CONTENT_PAD) + .margin(new DocumentInsets(px(55.4), 0, 0, 0)); + MidnightNavyWidgets.asideHeading(block, education.title(), ASIDE_INNER_WIDTH, px(20.4)); + block.addSection("EducationEntries", rows -> { + rows.spacing(px(29.8)); + for (CvEntry entry : education.entries()) { + rows.addSection("EducationEntry", item -> { + item.spacing(0).keepTogether(); + item.addParagraph(p -> { + p.name("Degree"); + p.lineSpacing(leading(20, EDUCATION_TITLE_SIZE)); + p.textStyle(style(EDUCATION_TITLE_SIZE, WHITE, + DocumentTextDecoration.BOLD)); + if (entry.link().isBlank()) { + p.inlineText(entry.title(), style(EDUCATION_TITLE_SIZE, WHITE, + DocumentTextDecoration.BOLD)); + } else { + p.inlineText(entry.title(), style(EDUCATION_TITLE_SIZE, WHITE, + DocumentTextDecoration.BOLD), + new DocumentLinkOptions(entry.link())); + } + }); + if (!entry.subtitle().isBlank()) { + item.addParagraph(p -> p + .name("Institution") + .text(entry.subtitle()) + .textStyle(style(ASIDE_SMALL_SIZE, ASIDE_DIM, + DocumentTextDecoration.DEFAULT)) + .lineSpacing(leading(18, ASIDE_SMALL_SIZE)) + .margin(new DocumentInsets(px(6.4), 0, 0, 0))); + } + if (!entry.date().isBlank()) { + item.addParagraph(p -> p + .name("Years") + .text(entry.date()) + .textStyle(style(EDUCATION_YEARS_SIZE, ASIDE_DIM, + DocumentTextDecoration.DEFAULT)) + .margin(new DocumentInsets(px(11.5), 0, 0, 0))); + } + }); + } + }); + }); + } + + /** The skills — a label beside a meter, one row each. */ + private static void renderSkills(SectionBuilder column, SkillsSection skills) { + List entries = flatten(skills); + column.addSection("Skills", block -> { + block.spacing(0).padding(ASIDE_CONTENT_PAD) + .margin(new DocumentInsets(px(37.4), 0, 0, 0)); + MidnightNavyWidgets.asideHeading(block, skills.title(), ASIDE_INNER_WIDTH, px(21)); + block.addSection("SkillRows", rows -> { + rows.spacing(SKILL_ROW_GAP); + for (CvSkill skill : entries) { + MidnightNavyWidgets.layeredRow(rows, "SkillRow", row -> { + row.spacing(0) + .verticalAlign(RowVerticalAlign.CENTER) + .columns(DocumentRowColumn.weight(1), + DocumentRowColumn.fixed(SKILL_TRACK_WIDTH)); + row.addParagraph(p -> p + .name("SkillLabel") + .text(skill.name()) + .textStyle(style(ASIDE_SMALL_SIZE, ASIDE_DIM, + DocumentTextDecoration.DEFAULT))); + row.addSection("SkillMeterCell", cell -> + renderSkillMeter(cell, skill.level())); + }); + } + }); + }); + } + + /** The three layers of one meter, all on the track's axis. */ + private static void renderSkillMeter(SectionBuilder cell, OptionalDouble level) { + double clamped = Math.max(0.0, Math.min(1.0, level.orElse(0.0))); + double filled = SKILL_TRACK_WIDTH * clamped; + cell.addLayerStack(stack -> { + stack.name("SkillMeter"); + stack.layer(new LineBuilder() + .name("SkillTrack") + .horizontal(SKILL_TRACK_WIDTH) + .thickness(SKILL_TRACK_THICKNESS) + .color(TRACK_DIM) + .build(), LayerAlign.CENTER_LEFT, 0); + stack.layer(new LineBuilder() + .name("SkillTrackFilled") + .horizontal(filled) + .thickness(SKILL_TRACK_THICKNESS) + .color(WHITE) + .build(), LayerAlign.CENTER_LEFT, 1); + stack.position(new ShapeBuilder() + .name("SkillKnob") + .size(SKILL_KNOB_WIDTH, SKILL_KNOB_HEIGHT) + .cornerRadius(SKILL_KNOB_HEIGHT / 2.0) + .fillColor(WHITE) + .build(), + filled - SKILL_KNOB_WIDTH / 2.0, 0, LayerAlign.CENTER_LEFT, 2); + }); + } + + /** + * The languages — a label beside five dots, filled to the rating. + * + *

    The rating is a fraction like every other level in the model, and the + * design shows it in fifths, so it is rounded to the nearest dot.

    + */ + private static void renderLanguages(SectionBuilder column, SkillsSection languages) { + List entries = flatten(languages); + column.addSection("Languages", block -> { + block.spacing(0).padding(ASIDE_CONTENT_PAD) + .margin(new DocumentInsets(px(52.7), 0, 0, 0)); + MidnightNavyWidgets.asideHeading(block, languages.title(), ASIDE_INNER_WIDTH, px(26.6)); + block.addSection("LanguageRows", rows -> { + rows.spacing(LANGUAGE_ROW_GAP); + for (CvSkill language : entries) { + int filled = (int) Math.round( + Math.max(0.0, Math.min(1.0, language.level().orElse(0.0))) + * LANGUAGE_DOTS); + MidnightNavyWidgets.layeredRow(rows, "LanguageRow", row -> { + row.spacing(0) + .verticalAlign(RowVerticalAlign.CENTER) + .columns(DocumentRowColumn.weight(1), + DocumentRowColumn.fixed(LANGUAGE_GROUP_WIDTH)); + row.addParagraph(p -> p + .name("LanguageLabel") + .text(language.name()) + .textStyle(style(ASIDE_TEXT_SIZE, WHITE, + DocumentTextDecoration.DEFAULT))); + row.addParagraph(p -> { + p.name("LanguageDots"); + for (int i = 0; i < LANGUAGE_DOTS; i++) { + if (i > 0) { + p.inlineText(dotGap(), asideText()); + } + if (i < filled) { + p.dot(LANGUAGE_DOT_DIAMETER, WHITE); + } else { + p.dot(LANGUAGE_DOT_DIAMETER, NAVY, + DocumentStroke.of(WHITE, LANGUAGE_DOT_STROKE)); + } + } + }); + }); + } + }); + }); + } + + /** + * The gap between two dots, as spaces at the row's own type size. There is + * no inline advance control, so the count comes from the measured pitch and + * the face's space advance rather than from the eye. + */ + private static String dotGap() { + int spaces = (int) Math.max(1, Math.round( + (LANGUAGE_DOT_PITCH - LANGUAGE_DOT_DIAMETER) + / (FACE_SPACE_ADVANCE * ASIDE_TEXT_SIZE))); + return " ".repeat(spaces); + } + + /** Every rated entry of every group, in the order they were given. */ + private static List flatten(SkillsSection section) { + List entries = new ArrayList<>(); + for (SkillGroup group : section.groups()) { + entries.addAll(group.entries()); + } + return entries; + } + + /** The mark for the network a link points at, or the globe. */ + private static String markFor(Link link) { + return link.url().toLowerCase(Locale.ROOT).contains("linkedin.") + ? MidnightNavyIcons.LINKEDIN + : MidnightNavyIcons.WEBSITE; + } + + /** One contact row: its mark, what it reads, and where it points. */ + private record Channel(String token, String label, String href) { + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/MidnightNavyIcons.java b/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/MidnightNavyIcons.java new file mode 100644 index 000000000..379df0a6c --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/MidnightNavyIcons.java @@ -0,0 +1,99 @@ +package com.demcha.compose.document.templates.cv.presets; + +import com.demcha.compose.document.svg.SvgIcon; + +import java.io.IOException; +import java.io.InputStream; +import java.io.UncheckedIOException; +import java.nio.charset.StandardCharsets; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Classpath loader for the Midnight Navy CV marks: the five contact glyphs and + * the three the achievement discs draw. + * + *

    They ship inside the templates artifact under + * {@code templates/cv/midnight-navy/icons/} as SVG, so they scale with the box + * they are drawn into rather than being resampled. Parsed icons are cached per + * token, so a sheet that repeats a mark reads each file once per JVM.

    + * + *

    {@link #ENTRY_TOKENS} is the vocabulary a document names through + * {@code CvEntry.icon()}. It is scoped to this preset — another preset packages + * its own set — and each mark carries the width it is drawn at, because an + * icon's box is not its glyph and the sizes are per-kind.

    + */ +final class MidnightNavyIcons { + + static final String PHONE = "phone"; + static final String EMAIL = "email"; + static final String LOCATION = "location"; + static final String LINKEDIN = "linkedin"; + static final String WEBSITE = "website"; + + private static final String ICON_ROOT = "/templates/cv/midnight-navy/icons/"; + + /** The width each mark is drawn at, as the design sizes it. */ + private static final Map SIZES = Map.of( + PHONE, 8.5, + EMAIL, 8.5, + LOCATION, 8.5, + LINKEDIN, 8.5, + WEBSITE, 8.5, + "trophy", 15.0, + "growth", 15.0, + "award", 15.0); + + /** The marks a document may name on an achievement. */ + static final Set ENTRY_TOKENS = Set.of("trophy", "growth", "award"); + + private static final Map CACHE = new ConcurrentHashMap<>(); + + private MidnightNavyIcons() { + } + + /** + * The width one mark is drawn at. + * + * @param token one of the packaged tokens + * @return the drawn width in points + * @throws IllegalArgumentException when the token names no packaged mark, + * listing the ones a document may choose — an icon token is data, + * so a wrong one is a data error + */ + static double size(String token) { + Double size = SIZES.get(token); + if (size == null) { + throw new IllegalArgumentException( + "Unknown midnight navy CV icon token '" + token + + "'. This preset packages " + ENTRY_TOKENS.stream().sorted().toList() + + " for an achievement mark."); + } + return size; + } + + /** + * Reads one mark. + * + * @param token one of the packaged tokens + * @return the parsed icon + */ + static SvgIcon icon(String token) { + size(token); + return CACHE.computeIfAbsent(token, MidnightNavyIcons::read); + } + + private static SvgIcon read(String token) { + String resourcePath = ICON_ROOT + token + ".svg"; + try (InputStream input = MidnightNavyIcons.class.getResourceAsStream(resourcePath)) { + if (input == null) { + throw new IllegalStateException("Missing midnight navy CV icon: " + resourcePath); + } + return SvgIcon.parse(new String(input.readAllBytes(), StandardCharsets.UTF_8)); + } catch (IOException e) { + throw new UncheckedIOException( + "Failed to read midnight navy CV icon: " + resourcePath, e); + } + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/MidnightNavyMain.java b/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/MidnightNavyMain.java new file mode 100644 index 000000000..fd8a62294 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/MidnightNavyMain.java @@ -0,0 +1,325 @@ +package com.demcha.compose.document.templates.cv.presets; + +import com.demcha.compose.document.dsl.SectionBuilder; +import com.demcha.compose.document.dsl.TimelineMarker; +import com.demcha.compose.document.node.DocumentLinkOptions; +import com.demcha.compose.document.node.DocumentNode; +import com.demcha.compose.document.node.LayerAlign; +import com.demcha.compose.document.node.RowVerticalAlign; +import com.demcha.compose.document.node.TextAlign; +import com.demcha.compose.document.style.DocumentInsets; +import com.demcha.compose.document.style.DocumentRowColumn; +import com.demcha.compose.document.style.DocumentTextDecoration; +import com.demcha.compose.document.templates.cv.components.SectionLookup; +import com.demcha.compose.document.templates.cv.data.CvEntry; +import com.demcha.compose.document.templates.cv.data.EntriesSection; +import com.demcha.compose.document.templates.cv.data.ParagraphSection; + +import java.util.List; + +import static com.demcha.compose.document.templates.cv.presets.MidnightNavyStyles.ACHIEVEMENT_DISC_DIAMETER; +import static com.demcha.compose.document.templates.cv.presets.MidnightNavyStyles.ACHIEVEMENT_DISC_GAP; +import static com.demcha.compose.document.templates.cv.presets.MidnightNavyStyles.ACHIEVEMENT_ICON_SIZE; +import static com.demcha.compose.document.templates.cv.presets.MidnightNavyStyles.ACHIEVEMENT_RULE_THICKNESS; +import static com.demcha.compose.document.templates.cv.presets.MidnightNavyStyles.BODY; +import static com.demcha.compose.document.templates.cv.presets.MidnightNavyStyles.BODY_SIZE; +import static com.demcha.compose.document.templates.cv.presets.MidnightNavyStyles.CERT_DIVIDER_GAP; +import static com.demcha.compose.document.templates.cv.presets.MidnightNavyStyles.CERT_DIVIDER_WIDTH; +import static com.demcha.compose.document.templates.cv.presets.MidnightNavyStyles.CERT_MARKER_DIAMETER; +import static com.demcha.compose.document.templates.cv.presets.MidnightNavyStyles.ENTRY_GAP; +import static com.demcha.compose.document.templates.cv.presets.MidnightNavyStyles.HAIRLINE; +import static com.demcha.compose.document.templates.cv.presets.MidnightNavyStyles.INK; +import static com.demcha.compose.document.templates.cv.presets.MidnightNavyStyles.JOB_TITLE_SIZE; +import static com.demcha.compose.document.templates.cv.presets.MidnightNavyStyles.MAIN_PAD_LEFT; +import static com.demcha.compose.document.templates.cv.presets.MidnightNavyStyles.MAIN_PAD_RIGHT; +import static com.demcha.compose.document.templates.cv.presets.MidnightNavyStyles.MAIN_PAD_TOP; +import static com.demcha.compose.document.templates.cv.presets.MidnightNavyStyles.MAIN_RULE_THICKNESS; +import static com.demcha.compose.document.templates.cv.presets.MidnightNavyStyles.MAIN_SECTION_GAP; +import static com.demcha.compose.document.templates.cv.presets.MidnightNavyStyles.MARKER_DIAMETER; +import static com.demcha.compose.document.templates.cv.presets.MidnightNavyStyles.MUTED; +import static com.demcha.compose.document.templates.cv.presets.MidnightNavyStyles.NAVY; +import static com.demcha.compose.document.templates.cv.presets.MidnightNavyStyles.RAIL_GUTTER; +import static com.demcha.compose.document.templates.cv.presets.MidnightNavyStyles.RAIL_WIDTH; +import static com.demcha.compose.document.templates.cv.presets.MidnightNavyStyles.leading; +import static com.demcha.compose.document.templates.cv.presets.MidnightNavyStyles.px; +import static com.demcha.compose.document.templates.cv.presets.MidnightNavyStyles.style; + +/** + * The paper column: the summary, the roles held on a rail, the achievement + * discs and the certifications. + * + *

    The rail

    + * + *

    The roles are a timeline: one rail, one marker per role, sitting on it. It was built + * from a per-entry left accent with the marker positioned into a layer stack, because a + * timeline could not then put a marker on its rail. It can — {@code markerOnRail()} anchors + * the rail through the marker's centre — so the rail is the timeline's, and the layer stack + * and its offsets are gone.

    + * + *

    Two things keep the drawing where it was. The rail's ends come from + * {@code ENTRY_BOUNDS}, which spans the entries' own boxes: the gap between roles is padding + * inside an entry's box and the last entry carries none, so consecutive segments meet and + * nothing trails past the final marker, exactly as the accents did. And the axis column + * centres the rail on itself, so the timeline starts half an axis to the left and the rail + * lands back on the column edge the heading rule shares.

    + */ +final class MidnightNavyMain { + + private MidnightNavyMain() { + } + + /** + * Draws the column. + * + * @param column the main cell + * @param summary the opening prose, or {@code null} + * @param experience the roles held, or {@code null} + * @param achievements the discs, or {@code null} + * @param certifications the closing columns, or {@code null} + */ + static void compose(SectionBuilder column, ParagraphSection summary, EntriesSection experience, + EntriesSection achievements, EntriesSection certifications) { + column.spacing(MAIN_SECTION_GAP) + .padding(new DocumentInsets(MAIN_PAD_TOP, MAIN_PAD_RIGHT, 0, MAIN_PAD_LEFT)); + if (SectionLookup.hasContent(summary)) { + renderSummary(column, summary); + } + if (SectionLookup.hasContent(experience)) { + renderExperience(column, experience); + } + if (SectionLookup.hasContent(achievements)) { + renderAchievements(column, achievements); + } + if (SectionLookup.hasContent(certifications)) { + renderCertifications(column, certifications); + } + } + + private static void renderSummary(SectionBuilder column, ParagraphSection summary) { + column.addSection("Summary", block -> { + block.spacing(0); + MidnightNavyWidgets.mainHeading(block, summary.title(), MAIN_RULE_THICKNESS, px(22.4)); + block.addParagraph(p -> p + .name("SummaryText") + .text(summary.body()) + .textStyle(style(BODY_SIZE, BODY, DocumentTextDecoration.DEFAULT)) + .lineSpacing(leading(22.7, BODY_SIZE))); + }); + } + + private static void renderExperience(SectionBuilder column, EntriesSection experience) { + column.addSection("Experience", block -> { + block.spacing(0); + MidnightNavyWidgets.mainHeading(block, experience.title(), MAIN_RULE_THICKNESS, px(23)); + renderExperienceTimeline(block, experience.entries()); + }); + } + + /** + * The roles, on one rail. + * + *

    The axis column centres the rail on itself, so the timeline starts half an axis + * left of the column the heading rule sits on and the rail lands back on that edge — + * where the per-entry accent drew it before. The rail's ends come from + * {@code ENTRY_BOUNDS}: the gap between roles is padding inside an entry's own box and + * the last entry carries none, which is what made consecutive accents meet.

    + */ + private static void renderExperienceTimeline(SectionBuilder block, List entries) { + SectionBuilder holder = new SectionBuilder(); + holder.name("ExperienceRailHolder"); + { + SectionBuilder host = holder; + host.spacing(0) + .margin(new DocumentInsets(0, 0, 0, -MARKER_DIAMETER / 2.0)); + host.addTimeline(timeline -> { + timeline.markerOnRail() + .connector(HAIRLINE, RAIL_WIDTH) + .axisWidth(MARKER_DIAMETER) + .markerGap(RAIL_GUTTER - MARKER_DIAMETER / 2.0) + .gutter(0) + .spacing(ENTRY_GAP) + .keepEntriesTogether(); + for (CvEntry entry : entries) { + timeline.entry(entryMarker(), + e -> e.content(section -> renderExperienceEntry(section, entry))); + } + }); + } + DocumentNode node = holder.build(); + // The timeline goes in through a layer stack rather than straight into the + // block. A timeline publishes a row, a row cannot nest inside a row cell, + // and this block can sit in one — the stack is what insulates it. Unwinding + // the stacks that turn out not to need it buys no capability, so they stay + // until that is done as its own deliberate cleanup. + block.addLayerStack(stack -> stack + .name("ExperienceRail") + .layer(node, LayerAlign.TOP_LEFT, 0)); + } + + /** + * The navy disc, in a box tall enough to carry the drop that sets it on the title's cap + * height. A marker's declared box is what the timeline reserves and derives the rail + * from, so the drop rides inside the marker rather than being positioned against the + * header row. + */ + private static TimelineMarker entryMarker() { + return TimelineMarker.custom(MARKER_DIAMETER, MARKER_DIAMETER + px(3.1), + column -> column.addEllipse(ellipse -> ellipse + .name("EntryMarker") + .circle(MARKER_DIAMETER) + .fillColor(NAVY) + .margin(new DocumentInsets(px(3.1), 0, 0, 0)))); + } + + private static void renderExperienceEntry(SectionBuilder section, CvEntry entry) { + section.spacing(0); + // A row nested directly in a row cell is refused, and a timeline entry lays its + // content out in one — so the header goes through the same wrapper as every other + // horizontal pair in this sheet. + MidnightNavyWidgets.layeredRow(section, "EntryHeader", row -> { + row.spacing(0).weights(0.62, 0.38); + row.addParagraph(p -> { + p.name("EntryTitle"); + p.textStyle(style(JOB_TITLE_SIZE, INK, DocumentTextDecoration.BOLD)); + if (entry.link().isBlank()) { + p.inlineText(entry.title(), + style(JOB_TITLE_SIZE, INK, DocumentTextDecoration.BOLD)); + } else { + p.inlineText(entry.title(), + style(JOB_TITLE_SIZE, INK, DocumentTextDecoration.BOLD), + new DocumentLinkOptions(entry.link())); + } + }); + row.addParagraph(p -> p + .name("EntryPeriod") + .text(entry.date()) + .textStyle(style(BODY_SIZE, MUTED, DocumentTextDecoration.DEFAULT)) + .align(TextAlign.RIGHT)); + }); + + MidnightNavyWidgets.layeredRow(section, "EntrySubheader", row -> { + row.spacing(0).weights(0.62, 0.38); + row.addParagraph(p -> p + .name("EntryCompany") + .text(entry.subtitle()) + .textStyle(style(BODY_SIZE, MUTED, DocumentTextDecoration.ITALIC)) + .margin(new DocumentInsets(px(5.4), 0, 0, 0))); + row.addParagraph(p -> p + .name("EntryLocation") + .text(entry.place()) + .textStyle(style(BODY_SIZE, MUTED, DocumentTextDecoration.DEFAULT)) + .align(TextAlign.RIGHT) + .margin(new DocumentInsets(px(5.4), 0, 0, 0))); + }); + + section.addList(list -> list + .name("EntryBullets") + .marker("·") + .items(MidnightNavyWidgets.lines(entry.body())) + .textStyle(style(BODY_SIZE, BODY, DocumentTextDecoration.DEFAULT)) + .lineSpacing(leading(23.7, BODY_SIZE)) + .itemSpacing(leading(23.7, BODY_SIZE)) + .margin(new DocumentInsets(px(24.8), 0, 0, 0))); + } + + /** + * The achievements — one disc and its line per card, side by side. + * + *

    A card shows the entry's title beside its mark: the design gives a card + * one line of text and no heading over it, so the title is that line.

    + */ + private static void renderAchievements(SectionBuilder column, EntriesSection achievements) { + column.addSection("Achievements", block -> { + block.spacing(0).keepTogether(); + MidnightNavyWidgets.mainHeading(block, achievements.title(), + ACHIEVEMENT_RULE_THICKNESS, px(32.4)); + MidnightNavyWidgets.layeredRow(block, "AchievementCards", row -> { + row.spacing(0).evenWeights().verticalAlign(RowVerticalAlign.TOP); + for (CvEntry card : achievements.entries()) { + row.addSection("AchievementCard", cell -> renderAchievementCard(cell, card)); + } + }); + }); + } + + private static void renderAchievementCard(SectionBuilder cell, CvEntry card) { + MidnightNavyWidgets.layeredRow(cell, "AchievementCardRow", row -> { + row.spacing(ACHIEVEMENT_DISC_GAP) + .verticalAlign(RowVerticalAlign.CENTER) + .columns(DocumentRowColumn.fixed(ACHIEVEMENT_DISC_DIAMETER), + DocumentRowColumn.weight(1)); + row.addSection("AchievementDiscCell", disc -> disc + .addContainer(container -> container + .name("AchievementDisc") + .circle(ACHIEVEMENT_DISC_DIAMETER) + .fillColor(NAVY) + .center(MidnightNavyIcons.icon(card.icon()) + .node(ACHIEVEMENT_ICON_SIZE)))); + row.addParagraph(p -> p + .name("AchievementText") + .text(card.title()) + .textStyle(style(BODY_SIZE, BODY, DocumentTextDecoration.DEFAULT)) + .lineSpacing(leading(22, BODY_SIZE))); + }); + } + + /** + * The certifications — even columns divided by a hairline. + * + *

    The divider is an accent on the column, so its height derives from the + * entry beside it rather than from a line whose length would have to be + * computed.

    + */ + private static void renderCertifications(SectionBuilder column, EntriesSection certifications) { + column.addSection("Certifications", block -> { + block.spacing(0).margin(new DocumentInsets(px(-13), 0, 0, 0)); + MidnightNavyWidgets.mainHeading(block, certifications.title(), + MAIN_RULE_THICKNESS, px(28.4)); + List entries = certifications.entries(); + MidnightNavyWidgets.layeredRow(block, "CertificationColumns", row -> { + row.spacing(0).evenWeights().verticalAlign(RowVerticalAlign.TOP); + for (int i = 0; i < entries.size(); i++) { + CvEntry entry = entries.get(i); + boolean first = i == 0; + row.addSection("CertificationColumn", cell -> { + if (!first) { + cell.accentLeft(HAIRLINE, CERT_DIVIDER_WIDTH) + .padding(new DocumentInsets(0, 0, 0, CERT_DIVIDER_GAP)); + } + renderCertificationEntry(cell, entry); + }); + } + }); + }); + } + + private static void renderCertificationEntry(SectionBuilder cell, CvEntry entry) { + cell.spacing(px(5.7)); + cell.addParagraph(p -> { + p.name("CertificationTitle"); + p.dot(CERT_MARKER_DIAMETER, INK); + if (entry.link().isBlank()) { + p.inlineText(" " + entry.title(), + style(BODY_SIZE, INK, DocumentTextDecoration.BOLD)); + } else { + p.inlineText(" " + entry.title(), + style(BODY_SIZE, INK, DocumentTextDecoration.BOLD), + new DocumentLinkOptions(entry.link())); + } + }); + if (!entry.subtitle().isBlank()) { + cell.addParagraph(p -> p + .name("CertificationIssuer") + .text(entry.subtitle()) + .textStyle(style(BODY_SIZE, MUTED, DocumentTextDecoration.ITALIC)) + .margin(new DocumentInsets(0, 0, 0, px(22)))); + } + if (!entry.date().isBlank()) { + cell.addParagraph(p -> p + .name("CertificationYear") + .text(entry.date()) + .textStyle(style(BODY_SIZE, MUTED, DocumentTextDecoration.DEFAULT)) + .margin(new DocumentInsets(0, 0, 0, px(22)))); + } + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/MidnightNavyStyles.java b/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/MidnightNavyStyles.java new file mode 100644 index 000000000..4d6e25d0f --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/MidnightNavyStyles.java @@ -0,0 +1,180 @@ +package com.demcha.compose.document.templates.cv.presets; + +import com.demcha.compose.document.api.DocumentPageSize; +import com.demcha.compose.document.style.DocumentColor; +import com.demcha.compose.document.style.DocumentInsets; +import com.demcha.compose.document.style.DocumentTextDecoration; +import com.demcha.compose.document.style.DocumentTextStyle; +import com.demcha.compose.font.FontName; + +/** + * Design tokens for {@link MidnightNavy} — the palette, the type scale and the + * geometry every part of the sheet measures against. + * + *

    The design was drawn at 1054 px across an A4 page, so {@link #PX} converts + * every length below from that raster to points: re-measuring the design at a + * different resolution changes this one number rather than each constant that + * came off it.

    + * + *

    Sizes are pinned by cap height rather than chosen, so a heading is as tall + * as the design's heading whatever the face's own metrics say, and + * {@link #leading} turns a measured line pitch into the additive value the + * engine's {@code lineSpacing} actually is.

    + */ +final class MidnightNavyStyles { + + private MidnightNavyStyles() { + } + + static final DocumentPageSize PAGE = DocumentPageSize.A4; + static final double PAGE_WIDTH = PAGE.width(); + + /** The design's raster is 1054 px wide; every length below came off it. */ + static final double PX = PAGE_WIDTH / 1054.0; + + /** Measured: the navy plate runs from the left paper edge to x=327 inclusive. */ + static final double ASIDE_RATIO = 328.0 / 1054.0; + static final double MAIN_RATIO = 1.0 - ASIDE_RATIO; + + static final double ASIDE_WIDTH = PAGE_WIDTH * ASIDE_RATIO; + static final double MAIN_CELL_WIDTH = PAGE_WIDTH - ASIDE_WIDTH; + + static final double ASIDE_PAD_LEFT = px(52); + static final double ASIDE_PAD_RIGHT = px(43); + static final double ASIDE_PAD_TOP = px(54); + static final double ASIDE_INNER_WIDTH = ASIDE_WIDTH - ASIDE_PAD_LEFT - ASIDE_PAD_RIGHT; + + /** + * The inset every aside content block carries; the identity block does not. + * + *

    The monogram is centred on the plate's full width rather than on the + * text column, so the horizontal inset belongs to the four blocks under it + * instead of to the column.

    + */ + static final DocumentInsets ASIDE_CONTENT_PAD = + new DocumentInsets(0, ASIDE_PAD_RIGHT, 0, ASIDE_PAD_LEFT); + + static final double MAIN_PAD_LEFT = px(53); + static final double MAIN_PAD_RIGHT = px(74); + static final double MAIN_PAD_TOP = px(71.3); + static final double MAIN_INNER_WIDTH = MAIN_CELL_WIDTH - MAIN_PAD_LEFT - MAIN_PAD_RIGHT; + + static final DocumentColor NAVY = DocumentColor.rgb(19, 34, 50); + static final DocumentColor INK = DocumentColor.rgb(16, 27, 44); + static final DocumentColor BODY = DocumentColor.rgb(51, 51, 51); + static final DocumentColor MUTED = DocumentColor.rgb(63, 74, 90); + static final DocumentColor RULE = DocumentColor.rgb(180, 186, 194); + static final DocumentColor HAIRLINE = DocumentColor.rgb(226, 227, 228); + static final DocumentColor ASIDE_RULE = DocumentColor.rgb(107, 116, 128); + static final DocumentColor ASIDE_DIVIDER = DocumentColor.rgb(195, 200, 206); + static final DocumentColor ASIDE_DIM = DocumentColor.rgb(213, 219, 225); + static final DocumentColor TRACK_DIM = DocumentColor.rgb(159, 166, 174); + static final DocumentColor WHITE = DocumentColor.WHITE; + + static final FontName FACE = FontName.BARLOW; + + /** The face's cap band as a fraction of its type size, measured off a render. */ + private static final double FACE_CAP_RATIO = 0.700; + + /** + * The gap between two language dots is spaces at the row's own type size, + * so the count is derived from the measured pitch and the face's space + * advance (0.200 em, read off the TTF) rather than tuned by eye. Setting the + * space at a larger size instead would buy the same gap and a line box three + * times too tall. + */ + static final double FACE_SPACE_ADVANCE = 0.200; + + static final double NAME_SIZE = sizeForCap(23); + static final double ROLE_SIZE = sizeForCap(10); + static final double MONOGRAM_SIZE = sizeForCap(31); + static final double ASIDE_HEAD_SIZE = sizeForCap(13); + static final double ASIDE_TEXT_SIZE = sizeForCap(10); + static final double ASIDE_SMALL_SIZE = sizeForCap(9); + static final double EDUCATION_TITLE_SIZE = sizeForCap(11); + static final double EDUCATION_YEARS_SIZE = sizeForCap(11); + static final double MAIN_HEAD_SIZE = sizeForCap(14); + static final double BODY_SIZE = sizeForCap(11); + static final double JOB_TITLE_SIZE = sizeForCap(12); + + static final double MONOGRAM_DIAMETER = px(135); + static final double MONOGRAM_STROKE = px(2.8); + + /** Measured: the first initial's cap top at 82 px, the second's at 129. */ + static final double MONOGRAM_PITCH = px(47); + static final double NAME_DIVIDER_WIDTH = px(201); + + static final double ASIDE_RULE_THICKNESS = px(2); + + /** The contact rule is measurably shorter than the other three. */ + static final double CONTACT_RULE_WIDTH = px(100); + + static final double CONTACT_ICON_SIZE = 8.5; + static final double CONTACT_ROW_GAP = px(13.5); + + static final double SKILL_TRACK_WIDTH = px(107); + static final double SKILL_TRACK_THICKNESS = px(2); + static final double SKILL_KNOB_WIDTH = px(9); + static final double SKILL_KNOB_HEIGHT = px(6); + static final double SKILL_ROW_GAP = px(11.3); + + static final int LANGUAGE_DOTS = 5; + static final double LANGUAGE_DOT_DIAMETER = px(10); + static final double LANGUAGE_DOT_PITCH = px(21.5); + static final double LANGUAGE_GROUP_WIDTH = + LANGUAGE_DOT_DIAMETER + (LANGUAGE_DOTS - 1) * LANGUAGE_DOT_PITCH; + static final double LANGUAGE_ROW_GAP = px(19.9); + static final double LANGUAGE_DOT_STROKE = px(1.4); + + static final double MAIN_RULE_THICKNESS = px(2); + static final double ACHIEVEMENT_RULE_THICKNESS = px(1); + static final double MAIN_SECTION_GAP = px(63); + + static final double RAIL_WIDTH = px(2.5); + static final double RAIL_GUTTER = px(27); + static final double MARKER_DIAMETER = px(9); + static final double ENTRY_GAP = px(46); + + static final double ACHIEVEMENT_DISC_DIAMETER = px(54); + static final double ACHIEVEMENT_ICON_SIZE = px(28); + static final double ACHIEVEMENT_DISC_GAP = px(13); + + static final double CERT_DIVIDER_WIDTH = px(1.2); + static final double CERT_DIVIDER_GAP = px(24); + static final double CERT_MARKER_DIAMETER = px(6); + + /** The type size whose cap band is {@code capPixels} of the design tall. */ + static double sizeForCap(double capPixels) { + return px(capPixels) / FACE_CAP_RATIO; + } + + /** + * A measured line pitch, as the additive value {@code lineSpacing} actually + * is: {@code lineSpacing} adds points between lines and the line + * box is 1.2 times the type size, so a measured leading converts as + * {@code leading - 1.2 * size} — never as a ratio. It is deliberately not + * clamped: this design sets three blocks tighter than their own line box, + * and clamping would silently open them back up. + */ + static double leading(double measuredPixels, double size) { + return px(measuredPixels) - 1.2 * size; + } + + static double px(double designPixels) { + return designPixels * PX; + } + + static DocumentTextStyle style(double size, DocumentColor color, + DocumentTextDecoration decoration) { + return DocumentTextStyle.builder() + .fontName(FACE) + .size(size) + .color(color) + .decoration(decoration) + .build(); + } + + static DocumentTextStyle asideText() { + return style(ASIDE_TEXT_SIZE, WHITE, DocumentTextDecoration.DEFAULT); + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/MidnightNavyWidgets.java b/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/MidnightNavyWidgets.java new file mode 100644 index 000000000..438f8034c --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/MidnightNavyWidgets.java @@ -0,0 +1,159 @@ +package com.demcha.compose.document.templates.cv.presets; + +import com.demcha.compose.document.dsl.RowBuilder; +import com.demcha.compose.document.dsl.SectionBuilder; +import com.demcha.compose.document.node.DocumentNode; +import com.demcha.compose.document.node.LayerAlign; +import com.demcha.compose.document.style.DocumentInsets; +import com.demcha.compose.document.style.DocumentTextDecoration; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; + +import static com.demcha.compose.document.templates.cv.presets.MidnightNavyStyles.ASIDE_DIVIDER; +import static com.demcha.compose.document.templates.cv.presets.MidnightNavyStyles.ASIDE_HEAD_SIZE; +import static com.demcha.compose.document.templates.cv.presets.MidnightNavyStyles.ASIDE_RULE; +import static com.demcha.compose.document.templates.cv.presets.MidnightNavyStyles.ASIDE_RULE_THICKNESS; +import static com.demcha.compose.document.templates.cv.presets.MidnightNavyStyles.INK; +import static com.demcha.compose.document.templates.cv.presets.MidnightNavyStyles.MAIN_HEAD_SIZE; +import static com.demcha.compose.document.templates.cv.presets.MidnightNavyStyles.MAIN_INNER_WIDTH; +import static com.demcha.compose.document.templates.cv.presets.MidnightNavyStyles.RULE; +import static com.demcha.compose.document.templates.cv.presets.MidnightNavyStyles.WHITE; +import static com.demcha.compose.document.templates.cv.presets.MidnightNavyStyles.px; +import static com.demcha.compose.document.templates.cv.presets.MidnightNavyStyles.style; + +/** + * The pieces more than one part of the Midnight Navy sheet draws: the two kinds + * of heading, the wrapper every horizontal pair goes through, and the two text + * helpers the design's tracking needs. + */ +final class MidnightNavyWidgets { + + private MidnightNavyWidgets() { + } + + /** + * A row that survives inside a row cell. + * + *

    A row nested directly in a row cell is refused by the layout compiler, + * and both columns of this sheet are row cells — so every horizontal pair in + * the preset goes through here, wrapped in a single layer of a stack.

    + * + * @param parent the section to add the row to + * @param name the node name, for review and rollback + * @param spec describes the row + */ + static void layeredRow(SectionBuilder parent, String name, Consumer spec) { + SectionBuilder holder = new SectionBuilder(); + holder.name(name + "Holder"); + holder.addRow(name, spec); + DocumentNode node = holder.build(); + parent.addLayerStack(stack -> stack + .name(name + "Layer") + .layer(node, LayerAlign.TOP_LEFT, 0)); + } + + /** + * An aside heading over its rule. + * + * @param block the block the heading opens + * @param label the heading + * @param ruleWidth the rule's length — the contact block's is shorter + * @param gapBelow the gap from the rule to the first line under it + */ + static void asideHeading(SectionBuilder block, String label, double ruleWidth, + double gapBelow) { + block.addParagraph(p -> p + .name("AsideHeading" + compact(label)) + .text(label) + .textStyle(style(ASIDE_HEAD_SIZE, WHITE, DocumentTextDecoration.BOLD))); + block.addLine(line -> line + .name("AsideRule" + compact(label)) + .horizontal(ruleWidth) + .thickness(ASIDE_RULE_THICKNESS) + .color(ASIDE_RULE) + .margin(new DocumentInsets(px(2), 0, gapBelow - px(2), 0))); + } + + /** + * A main-column heading over its full-width rule. + * + * @param block the block the heading opens + * @param label the heading + * @param ruleThickness the rule's weight — the achievements rule is finer + * @param gapBelow the gap from the rule to the first line under it + */ + static void mainHeading(SectionBuilder block, String label, double ruleThickness, + double gapBelow) { + block.addParagraph(p -> p + .name("MainHeading" + compact(label)) + .text(label) + .textStyle(style(MAIN_HEAD_SIZE, INK, DocumentTextDecoration.BOLD))); + block.addLine(line -> line + .name("MainRule" + compact(label)) + .horizontal(MAIN_INNER_WIDTH) + .thickness(ruleThickness) + .color(RULE) + .margin(new DocumentInsets(px(7.3), 0, gapBelow, 0))); + } + + /** The divider under the name plate, in the aside's own dim rule colour. */ + static void nameDivider(SectionBuilder column, double width, double leftInset, double gapAbove) { + column.addLine(line -> line + .name("NameDivider") + .horizontal(width) + .thickness(px(1)) + .color(ASIDE_DIVIDER) + .margin(new DocumentInsets(gapAbove, 0, 0, leftInset))); + } + + /** + * The role line's wide tracking, as spaces between characters. + * + *

    There is no letter-spacing API, so the tracking is real spaces and its + * width depends on the face's space advance: change the family and the + * tracking has to be re-checked.

    + * + * @param text the line to track + * @return the same line with a space between every pair of characters and + * three between words + */ + static String tracked(String text) { + if (text == null || text.isEmpty()) { + return ""; + } + StringBuilder out = new StringBuilder(text.length() * 2); + boolean startOfWord = true; + for (int i = 0; i < text.length(); i++) { + char ch = text.charAt(i); + if (ch == ' ') { + out.append(" "); + startOfWord = true; + continue; + } + if (!startOfWord) { + out.append(' '); + } + out.append(ch); + startOfWord = false; + } + return out.toString(); + } + + /** A label reduced to what can go in a node name. */ + static String compact(String text) { + return text == null ? "" : text.replaceAll("[^A-Za-z0-9]", ""); + } + + /** A body split into the lines it was written as, blanks dropped. */ + static List lines(String body) { + List out = new ArrayList<>(); + for (String line : body.split(String.valueOf((char) 10))) { + if (!line.isBlank()) { + out.add(line.strip()); + } + } + return out; + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/MintEditorial.java b/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/MintEditorial.java index 676f05ab9..93a68ad01 100644 --- a/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/MintEditorial.java +++ b/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/MintEditorial.java @@ -29,6 +29,8 @@ import com.demcha.compose.document.templates.core.identity.SvgGlyph; import com.demcha.compose.document.svg.SvgIcon; +import com.demcha.compose.document.templates.core.identity.ContactUri; + import java.io.IOException; import java.io.InputStream; import java.io.UncheckedIOException; @@ -694,7 +696,8 @@ private void addContact(SectionBuilder section, CvIdentity identity) { String phone = identity.contact().phone(); if (!phone.isBlank()) { IconTextRow.render(block, glyph("phone.svg"), ICON_COLOR, - CONTACT_ICON_SIZE, phone, style, null, + CONTACT_ICON_SIZE, phone, style, + ContactUri.telLink(phone), DocumentInsets.bottom(13)); } String email = identity.contact().email(); diff --git a/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/MonogramSidebar.java b/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/MonogramSidebar.java index bab7d77fb..bc8488cf1 100644 --- a/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/MonogramSidebar.java +++ b/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/MonogramSidebar.java @@ -21,6 +21,8 @@ import com.demcha.compose.document.templates.core.identity.SvgGlyph; import com.demcha.compose.font.FontName; +import com.demcha.compose.document.templates.core.identity.ContactUri; + import java.util.*; /** @@ -897,7 +899,8 @@ private static List contactItems(CvIdentity identity) { return List.of(); } List items = new ArrayList<>(); - addContactItem(items, "phone.svg", identity.contact().phone(), null); + addContactItem(items, "phone.svg", identity.contact().phone(), + ContactUri.telLink(identity.contact().phone())); String email = identity.contact().email(); if (!email.isBlank()) { addContactItem(items, "email.svg", email, diff --git a/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/NavySidebar.java b/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/NavySidebar.java new file mode 100644 index 000000000..eb8bc2bf8 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/NavySidebar.java @@ -0,0 +1,219 @@ +package com.demcha.compose.document.templates.cv.presets; + +import com.demcha.compose.document.api.DocumentSession; +import com.demcha.compose.document.api.PageBackgroundFill; +import com.demcha.compose.document.style.DocumentInsets; +import com.demcha.compose.document.templates.api.DocumentTemplate; +import com.demcha.compose.document.templates.cv.components.SectionLookup; +import com.demcha.compose.document.templates.cv.data.CvDocument; +import com.demcha.compose.document.templates.cv.data.CvSection; +import com.demcha.compose.document.templates.cv.data.EntriesSection; +import com.demcha.compose.document.templates.cv.data.ParagraphSection; +import com.demcha.compose.document.templates.cv.data.RowsSection; +import com.demcha.compose.document.templates.cv.data.SkillsSection; + +import java.util.List; +import java.util.Objects; + +import static com.demcha.compose.document.templates.cv.presets.NavySidebarStyles.MAIN_WEIGHT; +import static com.demcha.compose.document.templates.cv.presets.NavySidebarStyles.NAVY; +import static com.demcha.compose.document.templates.cv.presets.NavySidebarStyles.PAGE; +import static com.demcha.compose.document.templates.cv.presets.NavySidebarStyles.SIDEBAR_WEIGHT; + +/** + * Navy Sidebar — a one-page A4 CV in two columns: a navy plate carrying a + * ringed portrait, the contact channels, the degrees, the skills and the + * languages, beside a white column carrying the name, the summary, the roles + * held on a timeline rail, the achievements and the certifications. + * + *

    The preset owns its page: it sets A4 with no margin and paints the navy + * plate as a page background, so the column reaches the foot of the sheet + * whatever the sidebar holds.

    + * + *

    One page, and what happens past it

    + * + *

    This sheet holds one page of content. The two columns are a single row, + * and a row is atomic — it cannot be split — so a CV longer than the sheet + * does not flow onto a second page: composing it raises + * {@code AtomicNodeTooLargeException}, naming the node and the height it + * needed. It draws no cap of its own, because a CV that quietly loses a job + * is worse than one that refuses to compose. {@link TimelineMinimal} is the + * preset in this package that splits its own columns across pages.

    + * + *

    How a document reaches its berth

    + * + *

    The columns are fixed, so the preset assigns each section a berth by + * title rather than reading {@link + * com.demcha.compose.document.templates.cv.data.Slot}: summary, experience, + * achievements and certifications to the main column; education, skills and + * languages to the sidebar. A section whose title matches no berth is not + * drawn, and a berth with no section takes its leading hairline with it.

    + * + *

    Each berth wants a particular shape. The summary, the achievements and + * the certifications are {@link ParagraphSection}s, and the two lists are + * drawn one bullet per line of the body. Experience and education are + * {@link EntriesSection}s. Languages are a {@link RowsSection}, because this + * design writes the proficiency out — "Native", "Advanced" — which a + * levelled skill could not carry back; its {@code RowStyle} is not read, + * since the rows are drawn to this design rather than to a family style.

    + * + *

    Three things are drawn in capitals whatever the document writes: the + * name, the role, and each degree. Headings are upper-cased too, and set + * with the tracking this design uses throughout.

    + * + *

    Content this sheet has no place for is not drawn: the summary block's + * own title, since it is the one block with no heading; a skill's level, + * since there are no meters here; and a {@code SkillGroup}'s category. A + * berth is filled by the first section that names it, so a second section + * naming the same berth is not drawn either. An education entry is drawn as + * four lines — the degree, the institution, the {@code place}, and the years + * — and its body has nowhere to go on this sheet.

    + * + *

    The portrait

    + * + *

    The photograph comes from {@code CvIdentity.portrait()}. An identity + * without one draws the ring around an empty navy disc rather than leaving a + * hole in the column.

    + * + *

    The phone, the email and each link are reachable from the PDF, with the + * {@code tel:} and {@code mailto:} targets built from the values. The + * packaged set has one network mark, so every link the identity carries is + * drawn behind it whatever the network is; a document that lists more than + * one profile should say which is which in the label.

    + * + *

    A channel is one paragraph, the mark and the value sharing a line, so a + * value wider than the sidebar's text column wraps under the mark and leaves + * it alone on the first line. The column is about 124pt, which at this type + * size is a little over twenty characters — long addresses want shortening + * rather than a wider column.

    + * + *

    Fonts

    + * + *

    The sheet is set in Lato. The templates artifact does not carry it — + * register it on the session, or the engine substitutes and the measured + * geometry here no longer matches the type sitting in it.

    + * + *

    Usage:

    + *
    {@code
    + * DocumentTemplate template = NavySidebar.create();
    + * template.compose(session, cv);
    + * }
    + */ +public final class NavySidebar { + + /** Stable identifier of this preset. */ + public static final String ID = "navy-sidebar"; + + /** Human-readable name of this preset. */ + public static final String DISPLAY_NAME = "Navy Sidebar"; + + /** + * The margin this preset expects: none. It sets the page margin itself, + * because the navy plate runs to the paper edge. + */ + public static final double RECOMMENDED_MARGIN = 0.0; + + private static final List SUMMARY_KEYS = + List.of("summary", "profile", "about"); + private static final List EXPERIENCE_KEYS = + List.of("experience", "employment", "work history", "career"); + private static final List ACHIEVEMENT_KEYS = + List.of("achievements", "achievement", "awards"); + private static final List CERTIFICATION_KEYS = + List.of("certifications", "certification", "licences", "licenses"); + private static final List EDUCATION_KEYS = + List.of("education", "qualifications"); + private static final List SKILL_KEYS = + List.of("skills", "expertise", "competencies"); + private static final List LANGUAGE_KEYS = + List.of("languages", "language"); + + private NavySidebar() { + } + + /** + * Creates the template. + * + * @return a template composing a {@link CvDocument} onto its own page + */ + public static DocumentTemplate create() { + return new Template(); + } + + private record Template() implements DocumentTemplate { + + @Override + public String id() { + return ID; + } + + @Override + public String displayName() { + return DISPLAY_NAME; + } + + @Override + public void compose(DocumentSession document, CvDocument doc) { + Objects.requireNonNull(document, "document"); + Objects.requireNonNull(doc, "doc"); + List sections = doc.sections(); + + document.pageSize(PAGE) + .margin(DocumentInsets.zero()) + // The plate is a page background rather than a section + // fill: a section is only as tall as what it holds, and + // the navy column has to reach the foot of the sheet + // whatever the sidebar carries. + .pageBackgrounds(List.of( + PageBackgroundFill.leftColumn(SIDEBAR_WEIGHT, NAVY))); + + EntriesSection education = berth(sections, EntriesSection.class, EDUCATION_KEYS); + SkillsSection skills = berth(sections, SkillsSection.class, SKILL_KEYS); + RowsSection languages = berth(sections, RowsSection.class, LANGUAGE_KEYS); + ParagraphSection summary = berth(sections, ParagraphSection.class, SUMMARY_KEYS); + EntriesSection experience = berth(sections, EntriesSection.class, EXPERIENCE_KEYS); + ParagraphSection achievements = + berth(sections, ParagraphSection.class, ACHIEVEMENT_KEYS); + ParagraphSection certifications = + berth(sections, ParagraphSection.class, CERTIFICATION_KEYS); + + document.pageFlow(page -> page + .name("NavySidebarCv") + .padding(DocumentInsets.zero()) + // spacing(0): every gap in this design is authored on the + // node that owns it, so a flow gap would add to all of + // them. + .spacing(0) + .addRow("PageGrid", row -> { + row.spacing(0); + row.weights(SIDEBAR_WEIGHT, MAIN_WEIGHT); + row.addSection("Sidebar", aside -> + NavySidebarAside.compose(aside, doc.identity(), + education, skills, languages)); + row.addSection("Main", main -> + NavySidebarMain.compose(main, doc.identity(), + summary, experience, achievements, certifications)); + })); + } + + /** + * The first section of the wanted shape whose title names this berth, + * or {@code null} when the document fills no such berth. + */ + private static T berth(List sections, + Class type, + List keys) { + for (CvSection section : sections) { + if (!type.isInstance(section)) { + continue; + } + for (String key : keys) { + if (SectionLookup.titleContains(section.title(), key)) { + return type.cast(section); + } + } + } + return null; + } + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/NavySidebarAside.java b/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/NavySidebarAside.java new file mode 100644 index 000000000..07513dd65 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/NavySidebarAside.java @@ -0,0 +1,311 @@ +package com.demcha.compose.document.templates.cv.presets; + +import com.demcha.compose.document.dsl.EllipseBuilder; +import com.demcha.compose.document.dsl.ImageBuilder; +import com.demcha.compose.document.dsl.ParagraphBuilder; +import com.demcha.compose.document.dsl.SectionBuilder; +import com.demcha.compose.document.dsl.ShapeContainerBuilder; +import com.demcha.compose.document.image.DocumentImageData; +import com.demcha.compose.document.node.DocumentLinkOptions; +import com.demcha.compose.document.node.DocumentNode; +import com.demcha.compose.document.node.InlineImageAlignment; +import com.demcha.compose.document.node.LayerAlign; +import com.demcha.compose.document.style.ClipPolicy; +import com.demcha.compose.document.style.DocumentInsets; +import com.demcha.compose.document.style.DocumentTextDecoration; +import com.demcha.compose.document.templates.core.identity.ContactUri; +import com.demcha.compose.document.templates.core.identity.Link; +import com.demcha.compose.document.templates.cv.components.SectionLookup; +import com.demcha.compose.document.templates.cv.data.CvEntry; +import com.demcha.compose.document.templates.cv.data.CvIdentity; +import com.demcha.compose.document.templates.cv.data.CvRow; +import com.demcha.compose.document.templates.cv.data.CvSkill; +import com.demcha.compose.document.templates.cv.data.EntriesSection; +import com.demcha.compose.document.templates.cv.data.RowsSection; +import com.demcha.compose.document.templates.cv.data.SkillGroup; +import com.demcha.compose.document.templates.cv.data.SkillsSection; + +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; + +import static com.demcha.compose.document.templates.cv.presets.NavySidebarStyles.AVATAR_DIAMETER; +import static com.demcha.compose.document.templates.cv.presets.NavySidebarStyles.AVATAR_RING; +import static com.demcha.compose.document.templates.cv.presets.NavySidebarStyles.AVATAR_RING_WIDTH; +import static com.demcha.compose.document.templates.cv.presets.NavySidebarStyles.AVATAR_TO_HEADING; +import static com.demcha.compose.document.templates.cv.presets.NavySidebarStyles.BODY_SIZE; +import static com.demcha.compose.document.templates.cv.presets.NavySidebarStyles.BULLET_LEADING; +import static com.demcha.compose.document.templates.cv.presets.NavySidebarStyles.CONTACT_ROW_GAP; +import static com.demcha.compose.document.templates.cv.presets.NavySidebarStyles.EDU_DEGREE_GAP; +import static com.demcha.compose.document.templates.cv.presets.NavySidebarStyles.EDU_ENTRY_GAP; +import static com.demcha.compose.document.templates.cv.presets.NavySidebarStyles.EDU_LINE_GAP; +import static com.demcha.compose.document.templates.cv.presets.NavySidebarStyles.LANGUAGE_ROW_GAP; +import static com.demcha.compose.document.templates.cv.presets.NavySidebarStyles.LANGUAGE_VALUE_X; +import static com.demcha.compose.document.templates.cv.presets.NavySidebarStyles.LIST_BULLET_DIAMETER; +import static com.demcha.compose.document.templates.cv.presets.NavySidebarStyles.NAVY; +import static com.demcha.compose.document.templates.cv.presets.NavySidebarStyles.PAGE_MARGIN; +import static com.demcha.compose.document.templates.cv.presets.NavySidebarStyles.SIDEBAR_BAND_HEIGHT; +import static com.demcha.compose.document.templates.cv.presets.NavySidebarStyles.SIDEBAR_INNER_WIDTH; +import static com.demcha.compose.document.templates.cv.presets.NavySidebarStyles.SIDEBAR_PAD_TOP; +import static com.demcha.compose.document.templates.cv.presets.NavySidebarStyles.SIDEBAR_PAD_X; +import static com.demcha.compose.document.templates.cv.presets.NavySidebarStyles.SIDEBAR_STRONG; +import static com.demcha.compose.document.templates.cv.presets.NavySidebarStyles.SIDEBAR_TEXT; +import static com.demcha.compose.document.templates.cv.presets.NavySidebarStyles.SKILL_ITEM_GAP; +import static com.demcha.compose.document.templates.cv.presets.NavySidebarStyles.compact; +import static com.demcha.compose.document.templates.cv.presets.NavySidebarStyles.sidebarBody; +import static com.demcha.compose.document.templates.cv.presets.NavySidebarStyles.style; +import static com.demcha.compose.document.templates.cv.presets.NavySidebarWidgets.sidebarHeading; + +/** + * The navy left column: the ringed portrait, the contact channels, the + * degrees, the skills and the languages. + */ +final class NavySidebarAside { + + /** + * The contact heading. It is not read from the document because the + * channels are not a section — they come off the identity, which carries + * no title of its own. + */ + static final String CONTACT_HEADING = "Contact"; + + private NavySidebarAside() { + } + + static void compose(SectionBuilder section, + CvIdentity identity, + EntriesSection education, + SkillsSection skills, + RowsSection languages) { + section.spacing(0) + .padding(new DocumentInsets( + SIDEBAR_PAD_TOP, SIDEBAR_PAD_X, PAGE_MARGIN, SIDEBAR_PAD_X)); + section.addSection("Avatar", inner -> { + inner.padding(new DocumentInsets(0, 0, AVATAR_TO_HEADING, 0)); + renderPortrait(inner, identity); + }); + section.addSection("Contact", inner -> renderContact(inner, identity)); + if (SectionLookup.hasContent(education)) { + section.addSection("Education", inner -> renderEducation(inner, education)); + } + if (SectionLookup.hasContent(skills)) { + section.addSection("Skills", inner -> renderSkills(inner, skills)); + } + if (SectionLookup.hasContent(languages)) { + section.addSection("Languages", inner -> renderLanguages(inner, languages)); + } + } + + // -- portrait -------------------------------------------------------- + + /** + * The photograph, clipped to a disc inside a pale ring. An identity + * carrying no portrait draws the ring around a navy disc, which is the + * same shape with nothing in it rather than a hole in the column. + */ + private static void renderPortrait(SectionBuilder section, CvIdentity identity) { + DocumentNode circle = identity.portrait() + .map(NavySidebarAside::clippedPhoto) + .orElseGet(NavySidebarAside::emptyDisc); + section.addContainer(ring -> ring + .name("AvatarRing") + .circle(AVATAR_DIAMETER + 2.0 * AVATAR_RING_WIDTH) + .fillColor(AVATAR_RING) + .center(circle)); + } + + private static DocumentNode clippedPhoto(DocumentImageData image) { + return new ShapeContainerBuilder() + .name("AvatarCircle") + .circle(AVATAR_DIAMETER) + .clipPolicy(ClipPolicy.CLIP_PATH) + .fillColor(NAVY) + // size(), not fitToBounds(): the disc clips, so the photo is + // drawn to fill it and whatever falls outside is cut away. + .center(new ImageBuilder() + .name("AvatarPhoto") + .source(image) + .size(AVATAR_DIAMETER, AVATAR_DIAMETER) + .build()) + .build(); + } + + /** + * The disc with no photograph in it. It is an ellipse rather than the + * same clipping container holding nothing, because a shape container + * with no layer is rejected — the clip needs something to cut. + */ + private static DocumentNode emptyDisc() { + return new EllipseBuilder() + .name("AvatarCircle") + .circle(AVATAR_DIAMETER) + .fillColor(NAVY) + .build(); + } + + // -- contact --------------------------------------------------------- + + /** + * The contact channels, in the order the design sets them: phone, email, + * address, then whatever links the identity carries. + * + *

    The phone, the email and each link are reachable from the PDF: the + * dial and mail targets are built from the values, so a document does + * not carry the same address twice. The annotations sit beside the + * content stream and draw nothing, which is why a clickable channel is + * the same ink as a plain one.

    + */ + private static void renderContact(SectionBuilder section, CvIdentity identity) { + sidebarHeading(section, CONTACT_HEADING, true); + for (Channel channel : channels(identity)) { + renderChannel(section, channel); + } + } + + private static void renderChannel(SectionBuilder section, Channel channel) { + double size = NavySidebarIcons.size(channel.token()); + DocumentLinkOptions link = channel.href() == null + ? null + : new DocumentLinkOptions(channel.href()); + ParagraphBuilder row = new ParagraphBuilder() + .name("Contact_" + compact(channel.token())) + .textStyle(sidebarBody()) + .inlineImage(NavySidebarIcons.image(channel.token()), size, size, + InlineImageAlignment.CENTER, 0.0, link); + // The gap between the mark and the value is set as spaces rather than + // an indent: the row is one paragraph, so the mark and the text share + // a baseline and wrap together. + if (link == null) { + row.inlineText(" " + channel.value(), sidebarBody()); + } else { + row.inlineText(" " + channel.value(), sidebarBody(), link); + } + section.add(row.margin(new DocumentInsets(0, 0, CONTACT_ROW_GAP, 0)).build()); + } + + private static List channels(CvIdentity identity) { + List channels = new ArrayList<>(); + String phone = identity.contact().phone(); + channels.add(new Channel(NavySidebarIcons.PHONE, phone, ContactUri.tel(phone))); + String email = identity.contact().email(); + channels.add(new Channel(NavySidebarIcons.EMAIL, email, "mailto:" + email)); + channels.add(new Channel(NavySidebarIcons.LOCATION, + identity.contact().address(), null)); + for (Link link : identity.links()) { + channels.add(new Channel(NavySidebarIcons.LINKEDIN, link.label(), link.url())); + } + return channels; + } + + /** + * A contact row. The packaged set has one network mark, so every link + * takes it — this design ships no globe. + */ + private record Channel(String token, String value, String href) { + } + + // -- education ------------------------------------------------------- + + /** + * Each degree as four lines: the qualification in capitals, the + * institution, where it was, and the years. The third line is the + * entry's {@code place} — the field the model gives a location, rather + * than the body, which this design has nowhere to put. + */ + private static void renderEducation(SectionBuilder section, EntriesSection education) { + sidebarHeading(section, education.title(), false); + for (CvEntry entry : education.entries()) { + section.addParagraph(p -> p + .name("Degree_" + compact(entry.title())) + .text(entry.title().toUpperCase(Locale.ROOT)) + .textStyle(style(BODY_SIZE, SIDEBAR_STRONG, DocumentTextDecoration.BOLD)) + .lineSpacing(BULLET_LEADING) + .margin(new DocumentInsets(0, 0, EDU_DEGREE_GAP, 0))); + line(section, "Institution_" + compact(entry.title()), entry.subtitle(), + EDU_LINE_GAP); + line(section, "Campus_" + compact(entry.title()), entry.place(), EDU_LINE_GAP); + line(section, "Years_" + compact(entry.title()), entry.date(), EDU_ENTRY_GAP); + } + } + + private static void line(SectionBuilder section, String name, String text, double gapBelow) { + section.addParagraph(p -> p + .name(name) + .text(text) + .textStyle(sidebarBody()) + .margin(new DocumentInsets(0, 0, gapBelow, 0))); + } + + // -- skills ---------------------------------------------------------- + + /** + * The skills as a plain bullet list. This design draws no meters, so a + * level the document carries is not read here. + */ + private static void renderSkills(SectionBuilder section, SkillsSection skills) { + sidebarHeading(section, skills.title(), false); + section.addList(list -> list + .name("SkillsList") + .bullet() + .items(names(skills)) + .textStyle(sidebarBody()) + .lineSpacing(BULLET_LEADING) + .itemSpacing(SKILL_ITEM_GAP) + .margin(DocumentInsets.zero())); + } + + /** + * The section's skills as one list of names. This design sets no group + * headings, so a document that groups its skills gets them in the order + * the groups were declared. + */ + private static List names(SkillsSection section) { + List out = new ArrayList<>(); + for (SkillGroup group : section.groups()) { + for (CvSkill skill : group.entries()) { + out.add(skill.name()); + } + } + return out; + } + + // -- languages ------------------------------------------------------- + + /** + * Language rows: a dotted label at the left, the proficiency anchored at + * a fixed offset behind a dash so the second column lines up whatever + * the names measure. + * + *

    The rows are {@link CvRow}s rather than levelled skills because + * this design writes the proficiency out — "Native", "Advanced" — and a + * number could not carry those words back.

    + */ + private static void renderLanguages(SectionBuilder section, RowsSection languages) { + sidebarHeading(section, languages.title(), false); + // The row gap is section spacing rather than a margin on each band: + // the bands are shape containers, and a container's margin would sit + // inside the clip rather than between the rows. + section.spacing(LANGUAGE_ROW_GAP); + for (CvRow row : languages.rows()) { + DocumentNode label = new ParagraphBuilder() + .name("Language_" + compact(row.label())) + .textStyle(sidebarBody()) + .dot(LIST_BULLET_DIAMETER, SIDEBAR_TEXT) + .inlineText(" " + row.label(), sidebarBody()) + .margin(DocumentInsets.zero()) + .build(); + DocumentNode value = new ParagraphBuilder() + .name("Proficiency_" + compact(row.label())) + .text("– " + row.body()) + .textStyle(sidebarBody()) + .margin(DocumentInsets.zero()) + .build(); + section.addContainer(band -> band + .name("LanguageRow_" + compact(row.label())) + .rectangle(SIDEBAR_INNER_WIDTH, SIDEBAR_BAND_HEIGHT) + .clipPolicy(ClipPolicy.OVERFLOW_VISIBLE) + .centerLeft(label) + .position(value, LANGUAGE_VALUE_X, 0, LayerAlign.CENTER_LEFT)); + } + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/NavySidebarIcons.java b/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/NavySidebarIcons.java new file mode 100644 index 000000000..eb0888509 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/NavySidebarIcons.java @@ -0,0 +1,85 @@ +package com.demcha.compose.document.templates.cv.presets; + +import com.demcha.compose.document.image.DocumentImageData; + +import java.io.IOException; +import java.io.InputStream; +import java.io.UncheckedIOException; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Classpath loader for the Navy Sidebar CV marks — the four contact glyphs + * in the sidebar and the three section badges in the main column. + * + *

    They ship inside the templates artifact under + * {@code templates/cv/navy-sidebar/icons/} as PNG, each at the point size the + * design draws it at: a pin needs more height than an envelope to read as the + * same weight, and the badge glyphs are set larger than the contact ones + * because they sit inside a filled disc.

    + */ +final class NavySidebarIcons { + + static final String PHONE = "phone"; + static final String EMAIL = "email"; + static final String LOCATION = "location"; + static final String LINKEDIN = "linkedin"; + static final String BRIEFCASE = "briefcase"; + static final String TROPHY = "trophy"; + static final String CERTIFICATE = "certificate"; + + private static final String ICON_ROOT = "/templates/cv/navy-sidebar/icons/"; + + private static final Map SIZES = Map.of( + PHONE, 10.0, + EMAIL, 10.0, + LOCATION, 11.0, + LINKEDIN, 10.5, + BRIEFCASE, 11.3, + TROPHY, 11.3, + CERTIFICATE, 11.3); + + private static final Map CACHE = new ConcurrentHashMap<>(); + + private NavySidebarIcons() { + } + + /** + * The drawn size of one mark. + * + * @param token one of the constants on this class + * @return the size in points + * @throws IllegalArgumentException when the token names no packaged mark + */ + static double size(String token) { + Double size = SIZES.get(token); + if (size == null) { + throw new IllegalArgumentException("Unknown navy sidebar CV icon token: " + token); + } + return size; + } + + /** + * Reads one mark, caching the decoded image per token so a CV that + * repeats a channel reads the file once per JVM. + * + * @param token one of the constants on this class + * @return the image data + */ + static DocumentImageData image(String token) { + return CACHE.computeIfAbsent(token, NavySidebarIcons::read); + } + + private static DocumentImageData read(String token) { + String resourcePath = ICON_ROOT + token + ".png"; + try (InputStream input = NavySidebarIcons.class.getResourceAsStream(resourcePath)) { + if (input == null) { + throw new IllegalStateException("Missing navy sidebar CV icon: " + resourcePath); + } + return DocumentImageData.fromBytes(input.readAllBytes()); + } catch (IOException e) { + throw new UncheckedIOException( + "Failed to read navy sidebar CV icon: " + resourcePath, e); + } + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/NavySidebarMain.java b/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/NavySidebarMain.java new file mode 100644 index 000000000..761bc90c1 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/NavySidebarMain.java @@ -0,0 +1,305 @@ +package com.demcha.compose.document.templates.cv.presets; + +import com.demcha.compose.document.dsl.ParagraphBuilder; +import com.demcha.compose.document.dsl.SectionBuilder; +import com.demcha.compose.document.dsl.TimelineMarker; +import com.demcha.compose.document.dsl.TimelineRailEnd; +import com.demcha.compose.document.node.DocumentNode; +import com.demcha.compose.document.node.LayerAlign; +import com.demcha.compose.document.node.TextAlign; +import com.demcha.compose.document.style.ClipPolicy; +import com.demcha.compose.document.style.DocumentInsets; +import com.demcha.compose.document.style.DocumentStroke; +import com.demcha.compose.document.style.DocumentTextDecoration; +import com.demcha.compose.document.style.DocumentTextStyle; +import com.demcha.compose.document.templates.core.text.MarkdownInline; +import com.demcha.compose.document.templates.cv.components.SectionLookup; +import com.demcha.compose.document.templates.cv.data.CvEntry; +import com.demcha.compose.document.templates.cv.data.CvIdentity; +import com.demcha.compose.document.templates.cv.data.EntriesSection; +import com.demcha.compose.document.templates.cv.data.ParagraphSection; + +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; + +import static com.demcha.compose.document.templates.cv.presets.NavySidebarStyles.ACCENT; +import static com.demcha.compose.document.templates.cv.presets.NavySidebarStyles.BODY; +import static com.demcha.compose.document.templates.cv.presets.NavySidebarStyles.BODY_LEADING; +import static com.demcha.compose.document.templates.cv.presets.NavySidebarStyles.BODY_SIZE; +import static com.demcha.compose.document.templates.cv.presets.NavySidebarStyles.BULLET_ITEM_GAP; +import static com.demcha.compose.document.templates.cv.presets.NavySidebarStyles.BULLET_LEADING; +import static com.demcha.compose.document.templates.cv.presets.NavySidebarStyles.DATE_OVERFLOW; +import static com.demcha.compose.document.templates.cv.presets.NavySidebarStyles.ENTRY_EMPLOYER_TO_BULLETS; +import static com.demcha.compose.document.templates.cv.presets.NavySidebarStyles.ENTRY_GAP; +import static com.demcha.compose.document.templates.cv.presets.NavySidebarStyles.ENTRY_HEAD_BAND_HEIGHT; +import static com.demcha.compose.document.templates.cv.presets.NavySidebarStyles.ENTRY_TEXT_INSET; +import static com.demcha.compose.document.templates.cv.presets.NavySidebarStyles.ENTRY_TITLE_TO_EMPLOYER; +import static com.demcha.compose.document.templates.cv.presets.NavySidebarStyles.ENTRY_WIDTH; +import static com.demcha.compose.document.templates.cv.presets.NavySidebarStyles.HEADER_TO_BODY; +import static com.demcha.compose.document.templates.cv.presets.NavySidebarStyles.INK; +import static com.demcha.compose.document.templates.cv.presets.NavySidebarStyles.JOB_TITLE_SIZE; +import static com.demcha.compose.document.templates.cv.presets.NavySidebarStyles.MAIN_CONTENT_WIDTH; +import static com.demcha.compose.document.templates.cv.presets.NavySidebarStyles.MAIN_PAD_TOP; +import static com.demcha.compose.document.templates.cv.presets.NavySidebarStyles.MAIN_RULE; +import static com.demcha.compose.document.templates.cv.presets.NavySidebarStyles.MAIN_RULE_THICKNESS; +import static com.demcha.compose.document.templates.cv.presets.NavySidebarStyles.MARKER_DIAMETER; +import static com.demcha.compose.document.templates.cv.presets.NavySidebarStyles.NAME_SIZE; +import static com.demcha.compose.document.templates.cv.presets.NavySidebarStyles.NAME_TO_ROLE; +import static com.demcha.compose.document.templates.cv.presets.NavySidebarStyles.NAVY; +import static com.demcha.compose.document.templates.cv.presets.NavySidebarStyles.PAGE_MARGIN; +import static com.demcha.compose.document.templates.cv.presets.NavySidebarStyles.RAIL; +import static com.demcha.compose.document.templates.cv.presets.NavySidebarStyles.RAIL_MARGIN_LEFT; +import static com.demcha.compose.document.templates.cv.presets.NavySidebarStyles.RAIL_WIDTH; +import static com.demcha.compose.document.templates.cv.presets.NavySidebarStyles.ROLE_SIZE; +import static com.demcha.compose.document.templates.cv.presets.NavySidebarStyles.ROLE_TO_SUMMARY; +import static com.demcha.compose.document.templates.cv.presets.NavySidebarStyles.RULE_GAP_ABOVE; +import static com.demcha.compose.document.templates.cv.presets.NavySidebarStyles.RULE_GAP_BELOW; +import static com.demcha.compose.document.templates.cv.presets.NavySidebarStyles.SUMMARY_RULE_GAP_BELOW; +import static com.demcha.compose.document.templates.cv.presets.NavySidebarStyles.SUMMARY_SIZE; +import static com.demcha.compose.document.templates.cv.presets.NavySidebarStyles.TITLE_OVERFLOW; +import static com.demcha.compose.document.templates.cv.presets.NavySidebarStyles.TROUGH; +import static com.demcha.compose.document.templates.cv.presets.NavySidebarStyles.body; +import static com.demcha.compose.document.templates.cv.presets.NavySidebarStyles.compact; +import static com.demcha.compose.document.templates.cv.presets.NavySidebarStyles.style; +import static com.demcha.compose.document.templates.cv.presets.NavySidebarStyles.tracked; +import static com.demcha.compose.document.templates.cv.presets.NavySidebarWidgets.indentedList; +import static com.demcha.compose.document.templates.cv.presets.NavySidebarWidgets.sectionHeader; + +/** + * The white right column: the name and role, the summary, then the roles + * held on a timeline rail, the achievements and the certifications, each + * opened by a hairline and a badged heading. + */ +final class NavySidebarMain { + + private NavySidebarMain() { + } + + static void compose(SectionBuilder section, + CvIdentity identity, + ParagraphSection summary, + EntriesSection experience, + ParagraphSection achievements, + ParagraphSection certifications) { + section.spacing(0) + .padding(new DocumentInsets(MAIN_PAD_TOP, PAGE_MARGIN, PAGE_MARGIN, TROUGH)); + section.addSection("Identity", inner -> renderIdentity(inner, identity)); + if (SectionLookup.hasContent(summary)) { + section.addSection("Summary", inner -> renderSummary(inner, summary)); + } + if (SectionLookup.hasContent(experience)) { + rule(section, SUMMARY_RULE_GAP_BELOW); + section.addSection("Experience", inner -> renderExperience(inner, experience)); + } + if (SectionLookup.hasContent(achievements)) { + rule(section, RULE_GAP_BELOW); + section.addSection("Achievements", inner -> + renderBulletBlock(inner, NavySidebarIcons.TROPHY, achievements, + "AchievementsList")); + } + if (SectionLookup.hasContent(certifications)) { + rule(section, RULE_GAP_BELOW); + section.addSection("Certifications", inner -> + renderBulletBlock(inner, NavySidebarIcons.CERTIFICATE, certifications, + "CertificationsList")); + } + } + + /** + * The hairline that opens a block. It belongs to the block below it, so + * a block the document does not fill takes its rule with it instead of + * leaving one hanging. The gap below is authored per rule: the one that + * opens the experience block is set four points wider than the two that + * follow it. + * + * @param section the main column + * @param gapBelow the gap between the rule and the block it opens + */ + private static void rule(SectionBuilder section, double gapBelow) { + section.addLine(line -> line + .name("SectionRule") + .horizontal(MAIN_CONTENT_WIDTH) + .thickness(MAIN_RULE_THICKNESS) + .color(MAIN_RULE) + .margin(new DocumentInsets(RULE_GAP_ABOVE, 0, gapBelow, 0))); + } + + // -- identity -------------------------------------------------------- + + private static void renderIdentity(SectionBuilder section, CvIdentity identity) { + DocumentTextStyle nameStyle = style(NAME_SIZE, INK, DocumentTextDecoration.DEFAULT); + section.addParagraph(p -> p + .name("Name") + .text(identity.name().full().toUpperCase(Locale.ROOT)) + .textStyle(nameStyle) + .align(TextAlign.LEFT) + .margin(new DocumentInsets(0, 0, NAME_TO_ROLE, 0))); + + DocumentTextStyle roleStyle = style(ROLE_SIZE, ACCENT, DocumentTextDecoration.BOLD); + section.addParagraph(p -> { + p.name("Role"); + p.textStyle(roleStyle); + tracked(p, identity.jobTitle().toUpperCase(Locale.ROOT), roleStyle); + p.align(TextAlign.LEFT); + p.margin(new DocumentInsets(0, 0, ROLE_TO_SUMMARY, 0)); + }); + } + + // -- summary --------------------------------------------------------- + + /** + * The opening paragraph. It is the one block this design gives no + * heading, so the section's title is not drawn. + */ + private static void renderSummary(SectionBuilder section, ParagraphSection summary) { + section.addParagraph(p -> p + .name("Summary") + .text(summary.body()) + .textStyle(style(SUMMARY_SIZE, BODY, DocumentTextDecoration.DEFAULT)) + .lineSpacing(BODY_LEADING) + .margin(DocumentInsets.zero())); + } + + // -- experience ------------------------------------------------------ + + /** + * The roles held, strung on a vertical rail with a filled marker at each + * one. + * + *

    The rail stops at the last marker, which is what {@code MARKER_TO_MARKER} means. It + * used to be the section's left accent, and an accent runs the full height of what its + * section holds — so the last role's body was composed in a second section outside the + * rail to keep the line from running on to the foot of the block. The extent says it + * directly now, and that sibling section is gone.

    + * + *

    Wrapped in a layer because a row nested in a row cell is refused and this sheet's + * main column is one.

    + */ + private static void renderExperience(SectionBuilder section, EntriesSection experience) { + sectionHeader(section, NavySidebarIcons.BRIEFCASE, experience.title()); + SectionBuilder holder = new SectionBuilder(); + holder.name("ExperienceRailHolder"); + holder.spacing(0); + // The rail starts where the first marker's band does, which is half a title's + // overhang below the heading; the axis column centres the rail on itself, so the + // timeline starts half a marker further left than the accent did. + holder.margin(new DocumentInsets(HEADER_TO_BODY + TITLE_OVERFLOW, 0, 0, + RAIL_MARGIN_LEFT - MARKER_DIAMETER / 2.0)); + holder.addTimeline(timeline -> { + timeline.markerOnRail() + .rail(rail -> rail + .stroke(DocumentStroke.of(RAIL, RAIL_WIDTH)) + .from(TimelineRailEnd.MARKER).to(TimelineRailEnd.MARKER)) + .axisWidth(MARKER_DIAMETER) + .markerGap(ENTRY_TEXT_INSET - MARKER_DIAMETER / 2.0) + .gutter(0) + .spacing(ENTRY_GAP + TITLE_OVERFLOW); + for (CvEntry entry : experience.entries()) { + timeline.entry(entryMarker(entry), e -> e.content(body -> renderEntry(body, entry))); + } + }); + DocumentNode node = holder.build(); + // The timeline goes in through a layer stack rather than straight into the + // section. A timeline publishes a row, a row cannot nest inside a row cell, + // and this section can sit in one — the stack is what insulates it. + // Unwinding the stacks that turn out not to need it buys no capability, so + // they stay until that is done as its own deliberate cleanup. + section.addLayerStack(stack -> stack + .name("ExperienceRail") + .layer(node, LayerAlign.TOP_LEFT, 0)); + } + + /** The filled disc that marks a role, in a box as tall as the head band it rides. */ + private static TimelineMarker entryMarker(CvEntry entry) { + return TimelineMarker.custom(MARKER_DIAMETER, ENTRY_HEAD_BAND_HEIGHT, + column -> column.addEllipse(ellipse -> ellipse + .name("Marker_" + compact(entry.title())) + .circle(MARKER_DIAMETER) + .fillColor(NAVY))); + } + + /** One role: the head band carrying its title and dates, then the body. */ + private static void renderEntry(SectionBuilder body, CvEntry entry) { + body.spacing(0); + renderEntryHead(body, entry); + renderEntryBody(body, entry); + } + + /** + * The marker, the position in capitals and the dates, all on the + * marker's axis: the band is as tall as the marker, and the two pieces + * of type are pulled up by half their own overhang to centre on it. + */ + private static void renderEntryHead(SectionBuilder rail, CvEntry entry) { + DocumentNode title = new ParagraphBuilder() + .name("JobTitle_" + compact(entry.title())) + .text(entry.title().toUpperCase(Locale.ROOT)) + .textStyle(style(JOB_TITLE_SIZE, INK, DocumentTextDecoration.BOLD)) + .margin(DocumentInsets.zero()) + .build(); + DocumentNode dates = new ParagraphBuilder() + .name("JobDates_" + compact(entry.title())) + .text(entry.date()) + .textStyle(body()) + .align(TextAlign.RIGHT) + .margin(DocumentInsets.zero()) + .build(); + // The marker has left the band for the timeline's axis column, so the band starts at + // the content column and the title no longer walks in past the rail. + rail.addContainer(head -> head + .name("EntryHead_" + compact(entry.title())) + .rectangle(ENTRY_WIDTH - ENTRY_TEXT_INSET, ENTRY_HEAD_BAND_HEIGHT) + .clipPolicy(ClipPolicy.OVERFLOW_VISIBLE) + .position(title, 0, -TITLE_OVERFLOW, LayerAlign.CENTER_LEFT) + .position(dates, 0, -DATE_OVERFLOW, LayerAlign.CENTER_RIGHT)); + } + + /** The employer in accent, then one bullet per line of the entry body. */ + private static void renderEntryBody(SectionBuilder rail, CvEntry entry) { + // No left inset: the content column already starts where the text did, and the gap + // below an entry is the timeline's spacing rather than the last list's bottom margin. + rail.addParagraph(p -> p + .name("Employer_" + compact(entry.title())) + .text(entry.subtitle()) + .textStyle(style(BODY_SIZE, ACCENT, DocumentTextDecoration.DEFAULT)) + .margin(new DocumentInsets( + ENTRY_TITLE_TO_EMPLOYER + TITLE_OVERFLOW, 0, + ENTRY_EMPLOYER_TO_BULLETS, 0))); + List highlights = lines(entry.body()); + if (!highlights.isEmpty()) { + rail.addList(list -> list + .name("Highlights_" + compact(entry.title())) + .bullet() + .items(highlights) + .textStyle(body()) + .lineSpacing(BULLET_LEADING) + .itemSpacing(BULLET_ITEM_GAP) + .margin(DocumentInsets.zero())); + } + } + + // -- closing blocks -------------------------------------------------- + + /** A badged heading over an indented bullet list, one bullet per line. */ + private static void renderBulletBlock(SectionBuilder section, String iconToken, + ParagraphSection block, String listName) { + sectionHeader(section, iconToken, block.title()); + indentedList(section, listName, lines(block.body())); + } + + /** + * One bullet per non-blank line — the family's way of carrying a list in + * a field the model types as a single string. + */ + private static List lines(String body) { + List out = new ArrayList<>(); + for (String line : body.split("\\R")) { + String clean = MarkdownInline.plainText(line).trim(); + if (!clean.isBlank()) { + out.add(clean); + } + } + return out; + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/NavySidebarStyles.java b/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/NavySidebarStyles.java new file mode 100644 index 000000000..0584c1372 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/NavySidebarStyles.java @@ -0,0 +1,192 @@ +package com.demcha.compose.document.templates.cv.presets; + +import com.demcha.compose.document.api.DocumentPageSize; +import com.demcha.compose.document.dsl.ParagraphBuilder; +import com.demcha.compose.document.style.DocumentColor; +import com.demcha.compose.document.style.DocumentTextDecoration; +import com.demcha.compose.document.style.DocumentTextStyle; +import com.demcha.compose.document.templates.core.text.TextStyles; +import com.demcha.compose.font.FontName; + +/** + * The measured geometry, palette and type scale of the Navy Sidebar CV. + * + *

    The bands in this design are sized from the type they hold rather than + * chosen: an entry head is as tall as its marker, and the title and the dates + * set in it are taller than that, so each is pushed up by half the difference + * to sit on the marker's axis. Those overflows are computed here so the two + * ends of the band cannot drift apart.

    + */ +final class NavySidebarStyles { + + private NavySidebarStyles() { + } + + // -- page ------------------------------------------------------------ + + static final DocumentPageSize PAGE = DocumentPageSize.A4; + static final double PAGE_WIDTH = PAGE.width(); + + static final double SIDEBAR_WEIGHT = 0.297; + static final double MAIN_WEIGHT = 1.0 - SIDEBAR_WEIGHT; + + static final double SIDEBAR_PAD_X = 26.5; + static final double PAGE_MARGIN = 36.5; + + /** The gutter between the navy plate and the main column's text. */ + static final double TROUGH = 25.4; + + static final double SIDEBAR_WIDTH = PAGE_WIDTH * SIDEBAR_WEIGHT; + static final double MAIN_WIDTH = PAGE_WIDTH * MAIN_WEIGHT; + static final double SIDEBAR_INNER_WIDTH = SIDEBAR_WIDTH - 2.0 * SIDEBAR_PAD_X; + static final double MAIN_CONTENT_WIDTH = MAIN_WIDTH - TROUGH - PAGE_MARGIN; + + // -- ornaments ------------------------------------------------------- + + /** The portrait fills the sidebar's text column exactly. */ + static final double AVATAR_DIAMETER = SIDEBAR_INNER_WIDTH; + static final double AVATAR_RING_WIDTH = 1.6; + + static final double BADGE_DIAMETER = 22.6; + static final double BADGE_GAP = 7.3; + static final double SECTION_INDENT = BADGE_DIAMETER + BADGE_GAP; + + static final double MARKER_DIAMETER = 6.8; + static final double RAIL_WIDTH = 0.8; + static final double RAIL_X = BADGE_DIAMETER / 2.0; + static final double RAIL_MARGIN_LEFT = RAIL_X - RAIL_WIDTH / 2.0; + static final double ENTRY_TEXT_INSET = SECTION_INDENT - RAIL_MARGIN_LEFT; + static final double ENTRY_WIDTH = MAIN_CONTENT_WIDTH - RAIL_MARGIN_LEFT; + + static final double SIDEBAR_RULE_THICKNESS = 0.7; + static final double MAIN_RULE_THICKNESS = 0.7; + static final double LIST_BULLET_DIAMETER = 2.4; + + // -- type ------------------------------------------------------------ + + static final FontName HEADING_FONT = FontName.LATO; + static final FontName BODY_FONT = FontName.LATO; + + static final double BODY_SIZE = 9.0; + static final double SUMMARY_SIZE = 10.5; + static final double NAME_SIZE = 30.0; + static final double ROLE_SIZE = 13.5; + static final double HEADING_SIZE = 12.0; + static final double JOB_TITLE_SIZE = 11.5; + static final double BODY_LEADING = 1.28; + static final double BULLET_LEADING = 1.35; + + static final double TRACKING_EM = 0.17; + + /** + * The advance of a space in Lato, as a fraction of type size. + * {@link #tracked} sizes its spacer runs against this. + */ + static final double SPACE_ADVANCE_EM = 0.25; + + // -- bands ----------------------------------------------------------- + + static final double HEADING_BAND_HEIGHT = BADGE_DIAMETER; + static final double JOB_TITLE_BAND_HEIGHT = JOB_TITLE_SIZE * BULLET_LEADING; + static final double ENTRY_HEAD_BAND_HEIGHT = MARKER_DIAMETER; + static final double DATE_BAND_HEIGHT = BODY_SIZE * BULLET_LEADING; + static final double SIDEBAR_BAND_HEIGHT = BODY_SIZE * BULLET_LEADING; + + /** How far the job title rises above the marker band it is set on. */ + static final double TITLE_OVERFLOW = + (JOB_TITLE_BAND_HEIGHT - ENTRY_HEAD_BAND_HEIGHT) / 2.0; + + /** The same, for the dates set at the other end of that band. */ + static final double DATE_OVERFLOW = + (DATE_BAND_HEIGHT - ENTRY_HEAD_BAND_HEIGHT) / 2.0; + + static final double LANGUAGE_LABEL_WEIGHT = 0.41; + static final double LANGUAGE_VALUE_X = SIDEBAR_INNER_WIDTH * LANGUAGE_LABEL_WEIGHT; + + // -- vertical rhythm ------------------------------------------------- + + static final double SIDEBAR_PAD_TOP = 25.0; + static final double AVATAR_TO_HEADING = 22.0; + static final double SIDEBAR_BLOCK_GAP = 18.0; + static final double HEADING_TO_RULE = 7.0; + static final double RULE_TO_BODY = 10.0; + static final double CONTACT_ROW_GAP = 10.5; + static final double EDU_DEGREE_GAP = 4.0; + static final double EDU_LINE_GAP = 1.5; + static final double EDU_ENTRY_GAP = 13.0; + static final double SKILL_ITEM_GAP = 2.4; + static final double LANGUAGE_ROW_GAP = 6.0; + + static final double MAIN_PAD_TOP = 30.0; + static final double NAME_TO_ROLE = 6.0; + static final double ROLE_TO_SUMMARY = 16.0; + static final double RULE_GAP_ABOVE = 16.0; + static final double RULE_GAP_BELOW = 11.0; + static final double SUMMARY_RULE_GAP_BELOW = 15.0; + static final double HEADER_TO_BODY = 11.0; + static final double ENTRY_TITLE_TO_EMPLOYER = 3.0; + static final double ENTRY_EMPLOYER_TO_BULLETS = 7.0; + static final double ENTRY_GAP = 22.0; + static final double BULLET_ITEM_GAP = 6.1; + static final double PLAIN_ITEM_GAP = 5.1; + + // -- palette --------------------------------------------------------- + + static final DocumentColor NAVY = DocumentColor.rgb(32, 44, 59); + static final DocumentColor INK = DocumentColor.rgb(30, 40, 51); + static final DocumentColor ACCENT = DocumentColor.rgb(43, 84, 149); + static final DocumentColor BODY = DocumentColor.rgb(83, 87, 92); + static final DocumentColor SIDEBAR_STRONG = DocumentColor.WHITE; + static final DocumentColor SIDEBAR_TEXT = DocumentColor.rgb(233, 235, 238); + static final DocumentColor SIDEBAR_RULE = DocumentColor.rgba(255, 255, 255, 115); + static final DocumentColor MAIN_RULE = DocumentColor.rgb(228, 230, 233); + static final DocumentColor RAIL = DocumentColor.rgb(110, 116, 123); + static final DocumentColor AVATAR_RING = DocumentColor.rgb(226, 228, 231); + + // -- styles ---------------------------------------------------------- + + static DocumentTextStyle body() { + return style(BODY_SIZE, BODY, DocumentTextDecoration.DEFAULT); + } + + static DocumentTextStyle sidebarBody() { + return style(BODY_SIZE, SIDEBAR_TEXT, DocumentTextDecoration.DEFAULT); + } + + /** + * Both faces are Lato, so the weight is what the decoration selects; the + * split is kept because the design names a heading face and a body face, + * and a document set in two families would want it back. + */ + static DocumentTextStyle style(double size, DocumentColor color, + DocumentTextDecoration decoration) { + boolean bold = decoration == DocumentTextDecoration.BOLD + || decoration == DocumentTextDecoration.BOLD_ITALIC; + return TextStyles.of(bold ? HEADING_FONT : BODY_FONT, size, decoration, color); + } + + /** + * Writes text letter by letter with a sized space between each pair, + * which is how this design gets its letter-spacing: a text style carries + * no tracking, so the gap is a run of its own whose type size is chosen + * to advance by {@link #TRACKING_EM} of the surrounding size. + * + * @param paragraph the paragraph being built + * @param text the text to space out + * @param style the style of the letters + */ + static void tracked(ParagraphBuilder paragraph, String text, DocumentTextStyle style) { + DocumentTextStyle spacer = style.withSize(TRACKING_EM * style.size() / SPACE_ADVANCE_EM); + for (int i = 0; i < text.length(); i++) { + paragraph.inlineText(String.valueOf(text.charAt(i)), style); + if (i < text.length() - 1) { + paragraph.inlineText(" ", spacer); + } + } + } + + /** Strips a title down to the letters and digits a node name can carry. */ + static String compact(String text) { + return text == null ? "" : text.replaceAll("[^A-Za-z0-9]", ""); + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/NavySidebarWidgets.java b/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/NavySidebarWidgets.java new file mode 100644 index 000000000..aa0b78c5f --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/NavySidebarWidgets.java @@ -0,0 +1,133 @@ +package com.demcha.compose.document.templates.cv.presets; + +import com.demcha.compose.document.dsl.ImageBuilder; +import com.demcha.compose.document.dsl.ParagraphBuilder; +import com.demcha.compose.document.dsl.SectionBuilder; +import com.demcha.compose.document.dsl.ShapeContainerBuilder; +import com.demcha.compose.document.node.DocumentNode; +import com.demcha.compose.document.node.LayerAlign; +import com.demcha.compose.document.style.ClipPolicy; +import com.demcha.compose.document.style.DocumentInsets; +import com.demcha.compose.document.style.DocumentTextDecoration; +import com.demcha.compose.document.style.DocumentTextStyle; + +import java.util.List; +import java.util.Locale; + +import static com.demcha.compose.document.templates.cv.presets.NavySidebarStyles.BADGE_DIAMETER; +import static com.demcha.compose.document.templates.cv.presets.NavySidebarStyles.BULLET_LEADING; +import static com.demcha.compose.document.templates.cv.presets.NavySidebarStyles.HEADER_TO_BODY; +import static com.demcha.compose.document.templates.cv.presets.NavySidebarStyles.HEADING_BAND_HEIGHT; +import static com.demcha.compose.document.templates.cv.presets.NavySidebarStyles.HEADING_SIZE; +import static com.demcha.compose.document.templates.cv.presets.NavySidebarStyles.HEADING_TO_RULE; +import static com.demcha.compose.document.templates.cv.presets.NavySidebarStyles.INK; +import static com.demcha.compose.document.templates.cv.presets.NavySidebarStyles.MAIN_CONTENT_WIDTH; +import static com.demcha.compose.document.templates.cv.presets.NavySidebarStyles.NAVY; +import static com.demcha.compose.document.templates.cv.presets.NavySidebarStyles.PLAIN_ITEM_GAP; +import static com.demcha.compose.document.templates.cv.presets.NavySidebarStyles.RULE_TO_BODY; +import static com.demcha.compose.document.templates.cv.presets.NavySidebarStyles.SECTION_INDENT; +import static com.demcha.compose.document.templates.cv.presets.NavySidebarStyles.SIDEBAR_BLOCK_GAP; +import static com.demcha.compose.document.templates.cv.presets.NavySidebarStyles.SIDEBAR_INNER_WIDTH; +import static com.demcha.compose.document.templates.cv.presets.NavySidebarStyles.SIDEBAR_RULE; +import static com.demcha.compose.document.templates.cv.presets.NavySidebarStyles.SIDEBAR_RULE_THICKNESS; +import static com.demcha.compose.document.templates.cv.presets.NavySidebarStyles.SIDEBAR_STRONG; +import static com.demcha.compose.document.templates.cv.presets.NavySidebarStyles.body; +import static com.demcha.compose.document.templates.cv.presets.NavySidebarStyles.compact; +import static com.demcha.compose.document.templates.cv.presets.NavySidebarStyles.style; +import static com.demcha.compose.document.templates.cv.presets.NavySidebarStyles.tracked; + +/** + * The two headings this design uses — tracked capitals over a hairline in + * the navy sidebar, tracked capitals beside a filled badge in the main + * column — and the indented bullet list the closing sections are set in. + */ +final class NavySidebarWidgets { + + private NavySidebarWidgets() { + } + + /** + * A sidebar heading: tracked white capitals over a translucent rule the + * width of the sidebar's text column. + * + * @param section the sidebar column + * @param title the heading text, drawn in capitals + * @param first true for the block directly under the portrait, which + * takes no gap above + */ + static void sidebarHeading(SectionBuilder section, String title, boolean first) { + DocumentTextStyle headingStyle = + style(HEADING_SIZE, SIDEBAR_STRONG, DocumentTextDecoration.BOLD); + section.addParagraph(p -> { + p.name("SidebarHeading_" + compact(title)); + p.textStyle(headingStyle); + tracked(p, title.toUpperCase(Locale.ROOT), headingStyle); + p.margin(new DocumentInsets(first ? 0 : SIDEBAR_BLOCK_GAP, 0, HEADING_TO_RULE, 0)); + }); + section.addLine(line -> line + .name("SidebarRule_" + compact(title)) + .horizontal(SIDEBAR_INNER_WIDTH) + .thickness(SIDEBAR_RULE_THICKNESS) + .color(SIDEBAR_RULE) + .margin(new DocumentInsets(0, 0, RULE_TO_BODY, 0))); + } + + /** + * A main-column heading: the section's mark inside a navy disc, with + * tracked capitals on the disc's axis. + * + *

    It is a container rather than a row because the badge and the words + * are centred against each other; a row of sections would top-align + * them.

    + * + * @param section the main column + * @param iconToken the packaged mark for this section + * @param title the heading text, drawn in capitals + */ + static void sectionHeader(SectionBuilder section, String iconToken, String title) { + DocumentNode badge = new ShapeContainerBuilder() + .name("Badge_" + compact(title)) + .circle(BADGE_DIAMETER) + .clipPolicy(ClipPolicy.CLIP_PATH) + .fillColor(NAVY) + .center(new ImageBuilder() + .name("BadgeGlyph_" + compact(title)) + .source(NavySidebarIcons.image(iconToken)) + .size(NavySidebarIcons.size(iconToken), NavySidebarIcons.size(iconToken)) + .build()) + .build(); + + DocumentTextStyle headingStyle = style(HEADING_SIZE, INK, DocumentTextDecoration.BOLD); + ParagraphBuilder heading = new ParagraphBuilder() + .name("SectionTitle_" + compact(title)) + .textStyle(headingStyle); + tracked(heading, title.toUpperCase(Locale.ROOT), headingStyle); + DocumentNode headingNode = heading.margin(DocumentInsets.zero()).build(); + + section.addContainer(band -> band + .name("SectionHeader_" + compact(title)) + .rectangle(MAIN_CONTENT_WIDTH, HEADING_BAND_HEIGHT) + .clipPolicy(ClipPolicy.OVERFLOW_VISIBLE) + .centerLeft(badge) + .position(headingNode, SECTION_INDENT, 0, LayerAlign.CENTER_LEFT)); + } + + /** + * A bullet list indented to clear the badge, so its text starts on the + * same axis as the heading beside it. + * + * @param section the main column + * @param name node name of the list + * @param items one bullet per element + */ + static void indentedList(SectionBuilder section, String name, List items) { + section.addList(list -> list + .name(name) + .bullet() + .items(items) + .textStyle(body()) + .lineSpacing(BULLET_LEADING) + .itemSpacing(PLAIN_ITEM_GAP) + .margin(new DocumentInsets(HEADER_TO_BODY, 0, 0, SECTION_INDENT))); + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/OrangeOps.java b/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/OrangeOps.java new file mode 100644 index 000000000..01d732a3e --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/OrangeOps.java @@ -0,0 +1,274 @@ +package com.demcha.compose.document.templates.cv.presets; + +import com.demcha.compose.document.api.DocumentSession; +import com.demcha.compose.document.style.DocumentInsets; +import com.demcha.compose.document.templates.api.DocumentTemplate; +import com.demcha.compose.document.templates.cv.components.SectionLookup; +import com.demcha.compose.document.templates.cv.data.CvDocument; +import com.demcha.compose.document.templates.cv.data.CvSection; +import com.demcha.compose.document.templates.cv.data.EntriesSection; +import com.demcha.compose.document.templates.cv.data.ParagraphSection; +import com.demcha.compose.document.templates.cv.data.SkillsSection; +import com.demcha.compose.font.FontName; + +import java.util.List; +import java.util.Objects; + +import static com.demcha.compose.document.templates.cv.presets.OrangeOpsStyles.ASIDE_CELL_WEIGHT; +import static com.demcha.compose.document.templates.cv.presets.OrangeOpsStyles.COLUMN_RULE_THICKNESS; +import static com.demcha.compose.document.templates.cv.presets.OrangeOpsStyles.GUTTER; +import static com.demcha.compose.document.templates.cv.presets.OrangeOpsStyles.MAIN_CELL_WEIGHT; +import static com.demcha.compose.document.templates.cv.presets.OrangeOpsStyles.MARGIN_BOTTOM; +import static com.demcha.compose.document.templates.cv.presets.OrangeOpsStyles.MARGIN_TOP; +import static com.demcha.compose.document.templates.cv.presets.OrangeOpsStyles.MARGIN_X; +import static com.demcha.compose.document.templates.cv.presets.OrangeOpsStyles.PAGE; +import static com.demcha.compose.document.templates.cv.presets.OrangeOpsStyles.RULE; + +/** + * Orange Ops — a one-page operations CV: a two-tone name over a dark role bar + * with accent slashes, a contact strip across the page, and a body split into a + * narrow aside of skills, achievement discs, a degree and certifications + * against a wide column of profile, roles, a metric strip and closing lines. + * + *

    The caller registers the display family

    + * + *

    The sheet is set in Lato, with Oswald for the name, the headings and the + * achievement titles. Oswald is not one of the families + * {@code graph-compose-fonts} carries and the templates artifact carries no + * fonts at all, so the family is named here and registered by whoever + * composes the document:

    + * + *
    {@code
    + * document.registerFontFamily(
    + *         FontFamilyDefinition.classpath(OrangeOps.DISPLAY_FONT, "/fonts/oswald/Oswald-Regular.ttf")
    + *                 .wordFamily("Oswald")
    + *                 .boldResource("/fonts/oswald/Oswald-SemiBold.ttf")
    + *                 .build());
    + * DocumentTemplate template = OrangeOps.create();
    + * template.compose(document, cv);
    + * }
    + * + *

    Without that registration the engine substitutes, and every length on the + * sheet was measured against a condensed face — the name alone is set at the + * size whose cap band matches the design's, so a wider face runs it off the + * page.

    + * + *

    Why the page margins are zero

    + * + *

    The role bar bleeds past the left margin to the paper's edge, and a + * section's bleed extends its own fill rather than a shape inside it — so it + * cannot carry the trapezoid out to the edge. The session therefore takes + * {@link DocumentInsets} of zero on both sides and each band applies the + * horizontal margin as its own padding: one constant in four places rather than + * a special case for one band.

    + * + *

    Every horizontal pair inside a column is a table or a layer stack

    + * + *

    The aside and the main column are row cells, and a row cannot nest inside + * one. So every horizontal arrangement inside them is a table, a layer stack, + * or an inline run — never a nested row. A table cell here is only ever given a + * paragraph or a spacer: a section with composite children in a cell reserves + * its box and draws nothing.

    + * + *

    One page, and what happens past it

    + * + *

    Each column is a stack of blocks, so a CV with more content than the + * design holds runs onto a second page rather than losing anything — but the + * sheet is drawn for one page, and a longer one will look like a one-page + * design that ran over. {@link TimelineMinimal} is the preset in this package + * built to paginate.

    + * + *

    How a document reaches its berth

    + * + *

    The columns are fixed, so the preset assigns each section a berth by title + * rather than reading {@link + * com.demcha.compose.document.templates.cv.data.Slot}. It has eight — four in + * the aside and four in the main column, in the order the design stacks + * them:

    + * + *
      + *
    • skills, a {@link SkillsSection} whose groups are flattened into one + * dotted list — the design draws no group names;
    • + *
    • achievements, an {@link EntriesSection} whose entries carry a mark, a + * title and a body — the discs;
    • + *
    • education, an {@link EntriesSection} whose first entry is drawn, its + * body one line per line;
    • + *
    • certifications, an {@link EntriesSection} of a title over its issuer;
    • + *
    • the profile, a {@link ParagraphSection};
    • + *
    • experience, an {@link EntriesSection} whose bodies are bullets;
    • + *
    • the metric strip, an {@link EntriesSection} whose entries carry the + * number as the title and the caption below it as the body;
    • + *
    • the closing lines, an {@link EntriesSection} of a label and its value.
    • + *
    + * + *

    A section whose title matches no berth is not drawn, and a berth with no + * section takes its heading, its rule and its join hairline with it.

    + * + *

    A main-column berth's title is split at its first opening bracket: what + * comes before is the heading, and the bracket and everything after it is set + * smaller beside it, which is how the design writes {@code KEY KPI SNAPSHOT + * (Recent 12 Months)}. An aside heading is drawn whole.

    + * + *

    The marks and the links

    + * + *

    An achievement, the degree, a metric and a closing line each take the mark + * its entry names in {@code CvEntry.icon()}, from this preset's own vocabulary, + * and an unknown token is reported as a data error naming the set. An entry + * with no token is drawn without a mark.

    + * + *

    The email, the phone and each link are reachable from the PDF, with the + * {@code mailto:} and {@code tel:} targets built from the values. A link is + * drawn as its own label with the address behind it, so the contact strip's + * width does not depend on how long an address happens to be, and it takes the + * mark of the network it points at or the globe. An achievement title, a job + * title, the degree and a certification become links when their entry carries + * {@code CvEntry.link()}.

    + * + * @since 2.4.0 + */ +public final class OrangeOps { + + /** Stable identifier of this preset. */ + public static final String ID = "orange-ops"; + + /** Human-readable name of this preset. */ + public static final String DISPLAY_NAME = "Orange Ops"; + + /** + * The display family this preset sets its name and headings in. + * + *

    Named, not carried — register a family under this name on the session + * before composing. See the class documentation.

    + */ + public static final FontName DISPLAY_FONT = OrangeOpsStyles.DISPLAY_FONT; + + private static final List PROFILE_KEYS = + List.of("profile", "summary", "about"); + private static final List SKILL_KEYS = + List.of("skills", "expertise", "competencies"); + private static final List ACHIEVEMENT_KEYS = + List.of("achievements", "awards", "highlights"); + private static final List EDUCATION_KEYS = + List.of("education", "qualifications"); + private static final List CERTIFICATION_KEYS = + List.of("certifications", "certificates", "licences", "licenses"); + private static final List EXPERIENCE_KEYS = + List.of("experience", "employment", "work history", "career"); + private static final List METRIC_KEYS = + List.of("kpi", "metrics", "snapshot", "results"); + private static final List ADDITIONAL_KEYS = + List.of("additional", "other", "extras", "interests"); + + private OrangeOps() { + } + + /** + * Creates the template. + * + * @return a template composing a {@link CvDocument} + */ + public static DocumentTemplate create() { + return new Template(); + } + + private record Template() implements DocumentTemplate { + + @Override + public String id() { + return ID; + } + + @Override + public String displayName() { + return DISPLAY_NAME; + } + + @Override + public void compose(DocumentSession document, CvDocument doc) { + Objects.requireNonNull(document, "document"); + Objects.requireNonNull(doc, "doc"); + List sections = doc.sections(); + + SkillsSection skills = berth(sections, SkillsSection.class, SKILL_KEYS); + ParagraphSection profile = berth(sections, ParagraphSection.class, PROFILE_KEYS); + + // Six berths take the same shape, so each is claimed against what + // the ones before it took: the metric keys are broad enough to + // catch an achievements block titled "Results", and the closing + // keys broad enough to catch anything left over. + EntriesSection achievements = + berth(sections, EntriesSection.class, ACHIEVEMENT_KEYS); + EntriesSection education = + berth(sections, EntriesSection.class, EDUCATION_KEYS, achievements); + EntriesSection certifications = berth(sections, EntriesSection.class, + CERTIFICATION_KEYS, achievements, education); + EntriesSection experience = berth(sections, EntriesSection.class, + EXPERIENCE_KEYS, achievements, education, certifications); + EntriesSection metrics = berth(sections, EntriesSection.class, + METRIC_KEYS, achievements, education, certifications, experience); + EntriesSection additional = berth(sections, EntriesSection.class, ADDITIONAL_KEYS, + achievements, education, certifications, experience, metrics); + + // Every length on the sheet is a share of the design's own grid, so + // the preset sets the page and its margins rather than following + // what the caller configured. + document.pageSize(PAGE).margin(new DocumentInsets(MARGIN_TOP, 0, MARGIN_BOTTOM, 0)); + document.pageFlow(page -> { + page.name("OrangeOpsCv"); + page.spacing(0); + OrangeOpsMasthead.render(page, doc.identity()); + page.addRow("Body", row -> { + row.spacing(0); + row.weights(ASIDE_CELL_WEIGHT, MAIN_CELL_WEIGHT); + row.addSection("Aside", side -> { + side.spacing(0); + side.padding(0f, (float) (GUTTER / 2), 0f, (float) MARGIN_X); + OrangeOpsAside.compose(side, skills, achievements, education, + certifications); + }); + row.addSection("Main", main -> { + main.spacing(0); + main.padding(0f, (float) MARGIN_X, 0f, (float) (GUTTER / 2)); + // The divider is the main cell's own left accent: its x + // is that cell's left edge, so it cannot drift from the + // split, and its height is the column's. A page + // background would fix both by ratio and drift the + // moment the content changed. + main.accentLeft(RULE, COLUMN_RULE_THICKNESS); + OrangeOpsMain.compose(main, profile, experience, metrics, additional); + }); + }); + }); + } + + /** + * The first section of the wanted shape whose title names this berth, + * skipping any another berth has already claimed, or {@code null} when + * the document fills no such berth. + */ + @SafeVarargs + private static T berth(List sections, Class type, + List keys, T... taken) { + for (CvSection section : sections) { + if (!type.isInstance(section) || isTaken(section, taken)) { + continue; + } + for (String key : keys) { + if (SectionLookup.titleContains(section.title(), key)) { + return type.cast(section); + } + } + } + return null; + } + + private static boolean isTaken(CvSection section, CvSection[] taken) { + for (CvSection claimed : taken) { + if (section == claimed) { + return true; + } + } + return false; + } + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/OrangeOpsAside.java b/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/OrangeOpsAside.java new file mode 100644 index 000000000..a55c0b52c --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/OrangeOpsAside.java @@ -0,0 +1,315 @@ +package com.demcha.compose.document.templates.cv.presets; + +import com.demcha.compose.document.dsl.SectionBuilder; +import com.demcha.compose.document.node.DocumentLinkOptions; +import com.demcha.compose.document.node.DocumentNode; +import com.demcha.compose.document.node.LayerAlign; +import com.demcha.compose.document.style.DocumentColor; +import com.demcha.compose.document.style.DocumentInsets; +import com.demcha.compose.document.style.DocumentTextStyle; +import com.demcha.compose.document.templates.cv.components.SectionLookup; +import com.demcha.compose.document.templates.cv.data.CvEntry; +import com.demcha.compose.document.templates.cv.data.CvSkill; +import com.demcha.compose.document.templates.cv.data.EntriesSection; +import com.demcha.compose.document.templates.cv.data.SkillGroup; +import com.demcha.compose.document.templates.cv.data.SkillsSection; + +import java.util.ArrayList; +import java.util.List; + +import static com.demcha.compose.document.templates.cv.presets.OrangeOpsStyles.ACCENT; +import static com.demcha.compose.document.templates.cv.presets.OrangeOpsStyles.ACHIEVEMENT_BODY_PITCH; +import static com.demcha.compose.document.templates.cv.presets.OrangeOpsStyles.ACHIEVEMENT_BODY_SIZE; +import static com.demcha.compose.document.templates.cv.presets.OrangeOpsStyles.ACHIEVEMENT_CIRCLE; +import static com.demcha.compose.document.templates.cv.presets.OrangeOpsStyles.ACHIEVEMENT_GAP; +import static com.demcha.compose.document.templates.cv.presets.OrangeOpsStyles.ACHIEVEMENT_ICON_COLUMN; +import static com.demcha.compose.document.templates.cv.presets.OrangeOpsStyles.ACHIEVEMENT_TEXT_INSET; +import static com.demcha.compose.document.templates.cv.presets.OrangeOpsStyles.ACHIEVEMENT_TITLE_SIZE; +import static com.demcha.compose.document.templates.cv.presets.OrangeOpsStyles.ACHIEVEMENT_TITLE_TO_BODY; +import static com.demcha.compose.document.templates.cv.presets.OrangeOpsStyles.ASIDE_JOIN_INK; +import static com.demcha.compose.document.templates.cv.presets.OrangeOpsStyles.ASIDE_WIDTH; +import static com.demcha.compose.document.templates.cv.presets.OrangeOpsStyles.BODY; +import static com.demcha.compose.document.templates.cv.presets.OrangeOpsStyles.BODY_FONT; +import static com.demcha.compose.document.templates.cv.presets.OrangeOpsStyles.BODY_SIZE; +import static com.demcha.compose.document.templates.cv.presets.OrangeOpsStyles.BULLET_SIZE; +import static com.demcha.compose.document.templates.cv.presets.OrangeOpsStyles.CERT_GAP; +import static com.demcha.compose.document.templates.cv.presets.OrangeOpsStyles.CERT_ISSUER_SIZE; +import static com.demcha.compose.document.templates.cv.presets.OrangeOpsStyles.CERT_TEXT_INSET; +import static com.demcha.compose.document.templates.cv.presets.OrangeOpsStyles.CERT_TITLE_SIZE; +import static com.demcha.compose.document.templates.cv.presets.OrangeOpsStyles.CERT_TITLE_TO_ISSUER; +import static com.demcha.compose.document.templates.cv.presets.OrangeOpsStyles.DISPLAY_FONT; +import static com.demcha.compose.document.templates.cv.presets.OrangeOpsStyles.EDUCATION_CIRCLE; +import static com.demcha.compose.document.templates.cv.presets.OrangeOpsStyles.EDUCATION_ICON_COLUMN; +import static com.demcha.compose.document.templates.cv.presets.OrangeOpsStyles.EDUCATION_LINE_PITCH; +import static com.demcha.compose.document.templates.cv.presets.OrangeOpsStyles.EDUCATION_LINE_SIZE; +import static com.demcha.compose.document.templates.cv.presets.OrangeOpsStyles.EDUCATION_TITLE_SIZE; +import static com.demcha.compose.document.templates.cv.presets.OrangeOpsStyles.INK; +import static com.demcha.compose.document.templates.cv.presets.OrangeOpsStyles.LATO; +import static com.demcha.compose.document.templates.cv.presets.OrangeOpsStyles.MUTED; +import static com.demcha.compose.document.templates.cv.presets.OrangeOpsStyles.RULE_TO_BODY; +import static com.demcha.compose.document.templates.cv.presets.OrangeOpsStyles.SKILL_BULLET_GAP; +import static com.demcha.compose.document.templates.cv.presets.OrangeOpsStyles.SKILL_PITCH; +import static com.demcha.compose.document.templates.cv.presets.OrangeOpsStyles.gap; +import static com.demcha.compose.document.templates.cv.presets.OrangeOpsStyles.leading; +import static com.demcha.compose.document.templates.cv.presets.OrangeOpsStyles.style; + +/** + * The narrow column: skills, achievements, education and certifications, each + * under a text-width accent rule and separated from the next by a hairline. + * + *

    Why a card is a layer stack and not a table row

    + * + *

    An achievement and the degree are both a disc beside a block of text, and + * a table row can put the two side by side but cannot put the disc at the + * top of the pair: a cell anchors its content to the foot of the row + * whatever anchor it is given, which leaves every disc level with the middle of + * its card. Two layers anchored {@link LayerAlign#TOP_LEFT} — the disc with a + * right margin that ends its box at the split, the text with a matching left + * margin — anchor where they are asked to.

    + */ +final class OrangeOpsAside { + + private OrangeOpsAside() { + } + + /** + * Draws the column. + * + * @param side the aside cell + * @param skills the skill list, or {@code null} + * @param achievements the achievement cards, or {@code null} + * @param education the degree, or {@code null} + * @param certifications the certification list, or {@code null} + */ + static void compose(SectionBuilder side, SkillsSection skills, EntriesSection achievements, + EntriesSection education, EntriesSection certifications) { + List blocks = new ArrayList<>(); + if (SectionLookup.hasContent(skills)) { + blocks.add(new OrangeOpsWidgets.Block("Skills", + column -> renderSkills(column, skills), LATO, BODY_SIZE, false)); + } + if (SectionLookup.hasContent(achievements)) { + blocks.add(new OrangeOpsWidgets.Block("Achievements", + column -> renderAchievements(column, achievements), + LATO, ACHIEVEMENT_BODY_SIZE, false)); + } + if (SectionLookup.hasContent(education)) { + blocks.add(new OrangeOpsWidgets.Block("Education", + column -> renderEducation(column, education), + LATO, EDUCATION_LINE_SIZE, false)); + } + if (SectionLookup.hasContent(certifications)) { + blocks.add(new OrangeOpsWidgets.Block("Certifications", + column -> renderCertifications(column, certifications), + LATO, CERT_ISSUER_SIZE, false)); + } + OrangeOpsWidgets.stack(side, blocks, ASIDE_JOIN_INK); + } + + /** + * The skills — a list of charcoal names, each opened by an accent dot. + * + *

    The dot is the list's marker and {@link OrangeOpsStyles#SKILL_BULLET_GAP} + * the gap after it. It was a paragraph per name, because a list marker used + * to take the list's own text colour and these dots are accent against + * charcoal; the gap was four literal spaces, which is a measurement of the + * face rather than a distance the design states.

    + * + *

    The design draws one flat run of names, so a section that groups its + * skills has its groups flattened in the order they were given; the group + * names are not drawn.

    + */ + private static void renderSkills(SectionBuilder side, SkillsSection skills) { + side.addSection("Skills", block -> { + double itemGap = gap(SKILL_PITCH, LATO.lineBox(BODY_SIZE)); + block.spacing(itemGap); + OrangeOpsWidgets.asideHeading(block, "Skills", skills.title()); + List names = new ArrayList<>(); + for (SkillGroup group : skills.groups()) { + for (CvSkill skill : group.entries()) { + names.add(skill.name()); + } + } + DocumentTextStyle nameStyle = style(BODY_FONT, BODY_SIZE, BODY, false); + // The section's own spacing is the item pitch, which is tighter than + // the gap under the accent rule; the list's top margin makes up the + // difference rather than the rule being nudged. It sits on the list + // because the list's first item is the block's first item. + float lead = (float) Math.max(0.0, RULE_TO_BODY - itemGap); + block.addList(list -> { + list.name("SkillNames") + .textStyle(nameStyle) + .lineSpacing(0) + .marker(marker -> marker.dot(BULLET_SIZE, ACCENT)) + .hangingIndent(true) + .markerGap(SKILL_BULLET_GAP) + .itemSpacing(itemGap) + .normalizeMarkers(false) + .margin(new DocumentInsets(lead, 0, 0, 0)); + for (String name : names) { + list.addItem(name); + } + }); + }); + } + + /** The achievements — an accent disc beside a display-face title over its body. */ + private static void renderAchievements(SectionBuilder side, EntriesSection achievements) { + side.addSection("Achievements", block -> { + block.spacing(0); + OrangeOpsWidgets.asideHeading(block, "Achievements", achievements.title()); + List entries = achievements.entries(); + for (int i = 0; i < entries.size(); i++) { + CvEntry entry = entries.get(i); + int index = i; + boolean first = i == 0; + boolean last = i == entries.size() - 1; + + SectionBuilder text = new SectionBuilder(); + text.name("AchievementText" + index); + text.spacing(0); + text.margin(new DocumentInsets(0, ACHIEVEMENT_TEXT_INSET, 0, + ACHIEVEMENT_ICON_COLUMN)); + text.addParagraph(p -> { + p.name("AchievementTitle" + index); + p.lineSpacing(0); + p.textStyle(style(DISPLAY_FONT, ACHIEVEMENT_TITLE_SIZE, ACCENT, true)); + if (entry.link().isBlank()) { + p.inlineText(entry.title(), + style(DISPLAY_FONT, ACHIEVEMENT_TITLE_SIZE, ACCENT, true)); + } else { + p.inlineText(entry.title(), + style(DISPLAY_FONT, ACHIEVEMENT_TITLE_SIZE, ACCENT, true), + new DocumentLinkOptions(entry.link())); + } + p.margin(0f, 0f, (float) ACHIEVEMENT_TITLE_TO_BODY, 0f); + }); + text.addParagraph(p -> p + .name("AchievementBody" + index) + .text(entry.body()) + .lineSpacing(leading(ACHIEVEMENT_BODY_PITCH, ACHIEVEMENT_BODY_SIZE)) + .textStyle(style(BODY_FONT, ACHIEVEMENT_BODY_SIZE, BODY, false))); + + block.addLayerStack(stack -> { + stack.name("AchievementCard" + index); + stack.margin(new DocumentInsets( + first ? RULE_TO_BODY : 0, 0, last ? 0 : ACHIEVEMENT_GAP, 0)); + if (!entry.icon().isBlank()) { + stack.layer(badgeCell("AchievementBadge" + index, entry.icon(), + ACHIEVEMENT_CIRCLE, ACHIEVEMENT_ICON_COLUMN, ACCENT), + LayerAlign.TOP_LEFT, 0); + } + stack.layer(text.build(), LayerAlign.TOP_LEFT, 1); + }); + } + }); + } + + /** + * The degree — the same disc-beside-text card, in ink rather than accent. + * + *

    The design shows one degree, so the first entry is the one drawn. Its + * body carries the institution, the place and the years, one to a line.

    + */ + private static void renderEducation(SectionBuilder side, EntriesSection education) { + side.addSection("Education", block -> { + block.spacing(0); + OrangeOpsWidgets.asideHeading(block, "Education", education.title()); + CvEntry entry = education.entries().get(0); + + SectionBuilder text = new SectionBuilder(); + text.name("EducationText"); + text.spacing(0); + text.margin(new DocumentInsets(0, 0, 0, EDUCATION_ICON_COLUMN)); + text.addParagraph(p -> { + p.name("EducationDegree"); + p.lineSpacing(leading(EDUCATION_LINE_PITCH, EDUCATION_TITLE_SIZE)); + p.textStyle(style(BODY_FONT, EDUCATION_TITLE_SIZE, INK, true)); + if (entry.link().isBlank()) { + p.inlineText(entry.title(), style(BODY_FONT, EDUCATION_TITLE_SIZE, INK, true)); + } else { + p.inlineText(entry.title(), style(BODY_FONT, EDUCATION_TITLE_SIZE, INK, true), + new DocumentLinkOptions(entry.link())); + } + }); + List detail = OrangeOpsWidgets.lines(entry.body()); + for (int i = 0; i < detail.size(); i++) { + String line = detail.get(i); + int index = i; + text.addParagraph(p -> p + .name("EducationLine" + index) + .text(line) + .lineSpacing(0) + .textStyle(style(BODY_FONT, EDUCATION_LINE_SIZE, MUTED, false)) + .margin((float) gap(EDUCATION_LINE_PITCH, + LATO.lineBox(EDUCATION_LINE_SIZE)), 0f, 0f, 0f)); + } + + block.addLayerStack(stack -> { + stack.name("EducationEntry"); + stack.margin(new DocumentInsets(RULE_TO_BODY, 0, 0, 0)); + if (!entry.icon().isBlank()) { + stack.layer(badgeCell("EducationBadge", entry.icon(), EDUCATION_CIRCLE, + EDUCATION_ICON_COLUMN, INK), LayerAlign.TOP_LEFT, 0); + } + stack.layer(text.build(), LayerAlign.TOP_LEFT, 1); + }); + }); + } + + /** + * The certifications — an accent-dotted title over a muted issuer line. + * + *

    The issuer is padded to the title's text left edge rather than to the + * dot's, so it hangs under the title the way the design shows.

    + */ + private static void renderCertifications(SectionBuilder side, EntriesSection certifications) { + side.addSection("Certifications", block -> { + block.spacing(0); + OrangeOpsWidgets.asideHeading(block, "Certifications", certifications.title()); + List entries = certifications.entries(); + for (int i = 0; i < entries.size(); i++) { + CvEntry entry = entries.get(i); + int index = i; + boolean first = i == 0; + boolean last = i == entries.size() - 1; + block.addParagraph(p -> { + p.name("CertTitle" + index); + p.lineSpacing(0); + p.textStyle(style(BODY_FONT, CERT_TITLE_SIZE, INK, true)); + p.dot(BULLET_SIZE, ACCENT); + p.inlineText(" ", style(BODY_FONT, CERT_TITLE_SIZE, INK, true)); + if (entry.link().isBlank()) { + p.inlineText(entry.title(), style(BODY_FONT, CERT_TITLE_SIZE, INK, true)); + } else { + p.inlineText(entry.title(), style(BODY_FONT, CERT_TITLE_SIZE, INK, true), + new DocumentLinkOptions(entry.link())); + } + p.margin((float) (first ? RULE_TO_BODY : 0), 0f, + (float) CERT_TITLE_TO_ISSUER, 0f); + }); + block.addParagraph(p -> p + .name("CertIssuer" + index) + .text(entry.subtitle()) + .lineSpacing(0) + .textStyle(style(BODY_FONT, CERT_ISSUER_SIZE, MUTED, false)) + .padding(0f, 0f, 0f, (float) CERT_TEXT_INSET) + .margin(0f, 0f, (float) (last ? 0 : CERT_GAP), 0f)); + } + }); + } + + /** + * A badge in a box that ends where the text column starts, so the two + * layers of a card meet on the split rather than overlapping. + */ + private static DocumentNode badgeCell(String name, String token, double diameter, + double column, DocumentColor fill) { + SectionBuilder cell = new SectionBuilder(); + cell.name(name + "Cell"); + cell.spacing(0); + cell.margin(new DocumentInsets(0, ASIDE_WIDTH - column, 0, 0)); + cell.add(OrangeOpsWidgets.badge(name, token, diameter, fill)); + return cell.build(); + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/OrangeOpsIcons.java b/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/OrangeOpsIcons.java new file mode 100644 index 000000000..13ecf4d32 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/OrangeOpsIcons.java @@ -0,0 +1,213 @@ +package com.demcha.compose.document.templates.cv.presets; + +import com.demcha.compose.document.style.DocumentColor; +import com.demcha.compose.document.svg.SvgIcon; + +import java.awt.Color; +import java.io.IOException; +import java.io.InputStream; +import java.io.UncheckedIOException; +import java.nio.charset.StandardCharsets; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import static com.demcha.compose.document.templates.cv.presets.OrangeOpsStyles.BADGE_GLYPH_SHARE; + +/** + * Classpath loader for the Orange Ops CV marks: the contact glyphs, the four + * achievement marks, the four metric marks, the graduation cap and the four + * marks the closing block draws. + * + *

    {@link #WEBSITE} is the design's own globe under a second name. A link + * that is not a network the preset recognises still needs a mark, and giving + * the fallback its own token keeps the closing block's {@code systems} line + * reading as what it is rather than doubling as the web glyph.

    + * + *

    They ship inside the templates artifact under + * {@code templates/cv/orange-ops/icons/} as SVG, so they scale with the box + * they are drawn into rather than being resampled. Sources and parsed icons are + * cached per token, so a sheet that repeats a mark reads each file once per + * JVM.

    + * + *

    {@link #ENTRY_TOKENS} is the vocabulary a document names through + * {@code CvEntry.icon()}. It is scoped to this preset — another preset packages + * its own set — and each mark carries the width it is drawn at, because an + * icon's box is not its glyph and the sizes are per-kind.

    + */ +final class OrangeOpsIcons { + + static final String PHONE = "phone"; + static final String EMAIL = "email"; + static final String LOCATION = "location"; + static final String LINKEDIN = "linkedin"; + static final String WEBSITE = "website"; + static final String GRADUATION = "graduation"; + + private static final String ICON_ROOT = "/templates/cv/orange-ops/icons/"; + + /** The width each mark is drawn at, as the design sizes it. */ + private static final Map SIZES = Map.ofEntries( + Map.entry(PHONE, 8.5), + Map.entry(EMAIL, 8.5), + Map.entry(LOCATION, 8.5), + Map.entry(LINKEDIN, 8.5), + Map.entry(WEBSITE, 8.5), + Map.entry("achievement-productivity", 16.0), + Map.entry("achievement-accuracy", 16.0), + Map.entry("achievement-safety", 16.5), + Map.entry("achievement-savings", 16.0), + Map.entry(GRADUATION, 15.0), + Map.entry("kpi-productivity", 20.0), + Map.entry("kpi-accuracy", 20.0), + Map.entry("kpi-delivery", 20.0), + Map.entry("kpi-safety", 20.0), + Map.entry("systems", 9.5), + Map.entry("languages", 9.5), + Map.entry("driving", 9.5), + Map.entry("interests", 9.5)); + + /** The marks a document may name on an entry. */ + static final Set ENTRY_TOKENS = Set.of( + "achievement-productivity", "achievement-accuracy", "achievement-safety", + "achievement-savings", GRADUATION, + "kpi-productivity", "kpi-accuracy", "kpi-delivery", "kpi-safety", + "systems", "languages", "driving", "interests"); + + private static final Pattern VIEW_BOX = Pattern.compile("viewBox\\s*=\\s*\"([^\"]+)\""); + + private static final Map SOURCES = new ConcurrentHashMap<>(); + private static final Map CACHE = new ConcurrentHashMap<>(); + private static final Map BADGES = new ConcurrentHashMap<>(); + + private OrangeOpsIcons() { + } + + /** + * The width one mark is drawn at. + * + * @param token one of the packaged tokens + * @return the drawn width in points + * @throws IllegalArgumentException when the token names no packaged mark, + * listing the ones a document may choose — an icon token is data, + * so a wrong one is a data error + */ + static double size(String token) { + Double size = SIZES.get(token); + if (size == null) { + throw new IllegalArgumentException( + "Unknown orange ops CV icon token '" + token + + "'. This preset packages " + ENTRY_TOKENS.stream().sorted().toList() + + " for an entry mark."); + } + return size; + } + + /** + * Reads one mark. + * + * @param token one of the packaged tokens + * @return the parsed icon + */ + static SvgIcon icon(String token) { + size(token); + return CACHE.computeIfAbsent(token, t -> SvgIcon.parse(source(t))); + } + + /** + * One mark re-emitted inside a filled disc. + * + *

    The design draws the achievement and education badges as a filled + * circle owning an icon, and a shape container is what that is for. It is + * not what the engine renders here: a shape container placed inside a table + * cell — one level below the row cell the aside already is — reserves its + * box and composes none of its children, which leaves bare discs. So the + * disc is drawn into the mark: the glyph keeps its own user space + * and the viewBox is widened around it with a filled circle behind, which + * leaves one leaf node where there were two and keeps every table cell + * holding nothing but a paragraph. Widening a viewBox also stays inside the + * reader's documented subset in the way a transform would not.

    + * + * @param token one of the packaged tokens + * @param fill the disc colour + * @return the composed icon + */ + static SvgIcon badge(String token, DocumentColor fill) { + // Validated the same way an inline mark is: a badge reads the source + // rather than the size, so without this an unknown token would surface + // as a missing resource instead of the data error naming the set. + size(token); + Color rgb = fill.color(); + String key = token + '@' + rgb.getRGB(); + return BADGES.computeIfAbsent(key, ignored -> SvgIcon.parse(discAround(token, rgb))); + } + + private static String discAround(String token, Color fill) { + String source = source(token); + double[] box = viewBox(source); + double glyph = Math.max(box[2], box[3]); + double disc = glyph / BADGE_GLYPH_SHARE; + double pad = (disc - glyph) / 2.0; + double cx = box[0] + box[2] / 2.0; + double cy = box[1] + box[3] / 2.0; + double r = disc / 2.0; + String hex = String.format("#%02X%02X%02X", fill.getRed(), fill.getGreen(), fill.getBlue()); + return "" + innerMarkup(source) + ""; + } + + /** The four viewBox numbers, or the square the mark set defaults to. */ + private static double[] viewBox(String svg) { + Matcher matcher = VIEW_BOX.matcher(svg); + if (!matcher.find()) { + return new double[] {0, 0, 256, 256}; + } + String[] parts = matcher.group(1).trim().split("[\\s,]+"); + if (parts.length != 4) { + return new double[] {0, 0, 256, 256}; + } + double[] box = new double[4]; + for (int i = 0; i < 4; i++) { + box[i] = Double.parseDouble(parts[i]); + } + return box; + } + + /** Everything the source draws, between its own svg tags. */ + private static String innerMarkup(String svg) { + int open = svg.indexOf('>', svg.indexOf(""); + if (open < 0 || close < 0 || close <= open) { + throw new IllegalStateException("Orange ops CV icon markup is not an element"); + } + return svg.substring(open + 1, close); + } + + private static String fmt(double value) { + return String.valueOf(Math.round(value * 100.0) / 100.0); + } + + private static String source(String token) { + return SOURCES.computeIfAbsent(token, OrangeOpsIcons::read); + } + + private static String read(String token) { + String resourcePath = ICON_ROOT + token + ".svg"; + try (InputStream input = OrangeOpsIcons.class.getResourceAsStream(resourcePath)) { + if (input == null) { + throw new IllegalStateException("Missing orange ops CV icon: " + resourcePath); + } + return new String(input.readAllBytes(), StandardCharsets.UTF_8); + } catch (IOException e) { + throw new UncheckedIOException( + "Failed to read orange ops CV icon: " + resourcePath, e); + } + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/OrangeOpsMain.java b/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/OrangeOpsMain.java new file mode 100644 index 000000000..9f6ccffc2 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/OrangeOpsMain.java @@ -0,0 +1,377 @@ +package com.demcha.compose.document.templates.cv.presets; + +import com.demcha.compose.document.dsl.ParagraphBuilder; +import com.demcha.compose.document.dsl.SectionBuilder; +import com.demcha.compose.document.dsl.SpacerBuilder; +import com.demcha.compose.document.node.DocumentLinkOptions; +import com.demcha.compose.document.node.ListMarker; +import com.demcha.compose.document.node.TextAlign; +import com.demcha.compose.document.style.DocumentColor; +import com.demcha.compose.document.style.DocumentInsets; +import com.demcha.compose.document.table.DocumentTableCell; +import com.demcha.compose.document.table.DocumentTableColumn; +import com.demcha.compose.document.table.DocumentTableStyle; +import com.demcha.compose.document.table.DocumentTableTextAnchor; +import com.demcha.compose.document.templates.cv.components.SectionLookup; +import com.demcha.compose.document.templates.cv.data.CvEntry; +import com.demcha.compose.document.templates.cv.data.EntriesSection; +import com.demcha.compose.document.templates.cv.data.ParagraphSection; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.IntFunction; + +import static com.demcha.compose.document.templates.cv.presets.OrangeOpsStyles.ACCENT; +import static com.demcha.compose.document.templates.cv.presets.OrangeOpsStyles.ADDITIONAL_PITCH; +import static com.demcha.compose.document.templates.cv.presets.OrangeOpsStyles.ADDITIONAL_RULE_TO_BODY; +import static com.demcha.compose.document.templates.cv.presets.OrangeOpsStyles.BODY; +import static com.demcha.compose.document.templates.cv.presets.OrangeOpsStyles.BODY_FONT; +import static com.demcha.compose.document.templates.cv.presets.OrangeOpsStyles.BODY_SIZE; +import static com.demcha.compose.document.templates.cv.presets.OrangeOpsStyles.BULLET_ITEM_PITCH; +import static com.demcha.compose.document.templates.cv.presets.OrangeOpsStyles.BULLET_PITCH; +import static com.demcha.compose.document.templates.cv.presets.OrangeOpsStyles.COMPANY_TO_BULLETS; +import static com.demcha.compose.document.templates.cv.presets.OrangeOpsStyles.DATE_COLUMN_RATIO; +import static com.demcha.compose.document.templates.cv.presets.OrangeOpsStyles.EXPERIENCE_RULE_TO_BODY; +import static com.demcha.compose.document.templates.cv.presets.OrangeOpsStyles.INK; +import static com.demcha.compose.document.templates.cv.presets.OrangeOpsStyles.JOB_GAP; +import static com.demcha.compose.document.templates.cv.presets.OrangeOpsStyles.JOB_META_SIZE; +import static com.demcha.compose.document.templates.cv.presets.OrangeOpsStyles.JOB_TITLE_SIZE; +import static com.demcha.compose.document.templates.cv.presets.OrangeOpsStyles.JOB_TITLE_TO_COMPANY; +import static com.demcha.compose.document.templates.cv.presets.OrangeOpsStyles.LATO; +import static com.demcha.compose.document.templates.cv.presets.OrangeOpsStyles.MAIN_JOIN_INK; +import static com.demcha.compose.document.templates.cv.presets.OrangeOpsStyles.MAIN_WIDTH; +import static com.demcha.compose.document.templates.cv.presets.OrangeOpsStyles.METRIC_CAPTION_SIZE; +import static com.demcha.compose.document.templates.cv.presets.OrangeOpsStyles.METRIC_ICON_TO_VALUE; +import static com.demcha.compose.document.templates.cv.presets.OrangeOpsStyles.METRIC_RULE_TO_BODY; +import static com.demcha.compose.document.templates.cv.presets.OrangeOpsStyles.METRIC_SEPARATOR; +import static com.demcha.compose.document.templates.cv.presets.OrangeOpsStyles.METRIC_VALUE_SIZE; +import static com.demcha.compose.document.templates.cv.presets.OrangeOpsStyles.METRIC_VALUE_TO_CAPTION; +import static com.demcha.compose.document.templates.cv.presets.OrangeOpsStyles.MUTED; +import static com.demcha.compose.document.templates.cv.presets.OrangeOpsStyles.NO_STROKE; +import static com.demcha.compose.document.templates.cv.presets.OrangeOpsStyles.PROFILE_PITCH; +import static com.demcha.compose.document.templates.cv.presets.OrangeOpsStyles.PROFILE_RULE_TO_BODY; +import static com.demcha.compose.document.templates.cv.presets.OrangeOpsStyles.RULE; +import static com.demcha.compose.document.templates.cv.presets.OrangeOpsStyles.cellStyle; +import static com.demcha.compose.document.templates.cv.presets.OrangeOpsStyles.gap; +import static com.demcha.compose.document.templates.cv.presets.OrangeOpsStyles.leading; +import static com.demcha.compose.document.templates.cv.presets.OrangeOpsStyles.style; + +/** + * The wide column: the profile, the roles held, the metric strip, and the + * closing lines — each under a fixed-length accent rule and separated from the + * next by a hairline. + * + *

    A parenthetical in a heading is set smaller

    + * + *

    The design sets {@code (Recent 12 Months)} after the metric heading in a + * smaller body face, so a berth's title is split at its first opening bracket: + * what comes before is the heading, and the bracket and everything after it is + * the parenthetical. A title with no bracket is a heading and nothing else.

    + * + *

    Why every horizontal pair here is a table

    + * + *

    The main column is a row cell and a row cannot nest inside one, so the + * title/date line and the metric strip are tables rather than rows. A cell here + * is only ever given a paragraph or a spacer: a section with composite children + * in a cell reserves its box and draws nothing.

    + */ +final class OrangeOpsMain { + + private OrangeOpsMain() { + } + + /** + * Draws the column. + * + * @param main the main cell + * @param profile the opening prose, or {@code null} + * @param experience the roles held, or {@code null} + * @param metrics the metric strip, or {@code null} + * @param additional the closing lines, or {@code null} + */ + static void compose(SectionBuilder main, ParagraphSection profile, EntriesSection experience, + EntriesSection metrics, EntriesSection additional) { + List blocks = new ArrayList<>(); + if (SectionLookup.hasContent(profile)) { + blocks.add(new OrangeOpsWidgets.Block("Profile", + column -> renderProfile(column, profile), LATO, BODY_SIZE, false)); + } + if (SectionLookup.hasContent(experience)) { + // A bullet list ends on a full sentence, which descends. + blocks.add(new OrangeOpsWidgets.Block("Experience", + column -> renderExperience(column, experience), LATO, BODY_SIZE, true)); + } + if (SectionLookup.hasContent(metrics)) { + blocks.add(new OrangeOpsWidgets.Block("Metrics", + column -> renderMetrics(column, metrics), LATO, METRIC_CAPTION_SIZE, false)); + } + if (SectionLookup.hasContent(additional)) { + blocks.add(new OrangeOpsWidgets.Block("Additional", + column -> renderAdditional(column, additional), LATO, BODY_SIZE, false)); + } + OrangeOpsWidgets.stack(main, blocks, MAIN_JOIN_INK); + } + + /** + * The profile — one paragraph. + * + *

    The design sets it flush on both edges; the alignments available here + * are left, centre and right, so it is set flush left and the line breaks + * are the engine's.

    + */ + private static void renderProfile(SectionBuilder main, ParagraphSection profile) { + main.addSection("Profile", block -> { + block.spacing(0); + heading(block, "Profile", profile.title()); + block.addParagraph(p -> p + .name("ProfileBody") + .text(profile.body()) + .align(TextAlign.LEFT) + .lineSpacing(leading(PROFILE_PITCH, BODY_SIZE)) + .textStyle(style(BODY_FONT, BODY_SIZE, BODY, false)) + .margin((float) PROFILE_RULE_TO_BODY, 0f, 0f, 0f)); + }); + } + + /** + * The roles held — a title/date line, an accent company line, and bullets. + * + *

    The title line is a two-column table with the date anchored top right, + * so the date lands on the column's right edge whatever its length. The + * bullets are a list: unlike the aside's, these markers are the same + * charcoal as their text, so the marker colour is not fighting the + * style.

    + */ + private static void renderExperience(SectionBuilder main, EntriesSection experience) { + main.addSection("Experience", block -> { + block.spacing(0); + heading(block, "Experience", experience.title()); + List entries = experience.entries(); + double dateWidth = MAIN_WIDTH * DATE_COLUMN_RATIO; + for (int i = 0; i < entries.size(); i++) { + CvEntry entry = entries.get(i); + int index = i; + boolean first = i == 0; + block.addTable(table -> { + table.name("JobHead" + index); + table.width(MAIN_WIDTH); + table.margin(new DocumentInsets(first ? EXPERIENCE_RULE_TO_BODY : JOB_GAP, 0, + JOB_TITLE_TO_COMPANY, 0)); + table.columns( + DocumentTableColumn.fixed(MAIN_WIDTH - dateWidth), + DocumentTableColumn.fixed(dateWidth)); + table.defaultCellStyle(cellStyle(DocumentInsets.zero(), + DocumentTableTextAnchor.TOP_LEFT)); + ParagraphBuilder title = new ParagraphBuilder() + .name("JobTitle" + index) + .lineSpacing(0) + .textStyle(style(BODY_FONT, JOB_TITLE_SIZE, INK, true)); + if (entry.link().isBlank()) { + title.inlineText(entry.title(), + style(BODY_FONT, JOB_TITLE_SIZE, INK, true)); + } else { + title.inlineText(entry.title(), + style(BODY_FONT, JOB_TITLE_SIZE, INK, true), + new DocumentLinkOptions(entry.link())); + } + table.rowCells( + DocumentTableCell.node(title.build()), + DocumentTableCell.node(new ParagraphBuilder() + .name("JobDates" + index) + .text(entry.date()) + .align(TextAlign.RIGHT) + .lineSpacing(0) + .textStyle(style(BODY_FONT, JOB_META_SIZE, MUTED, + false)) + .build()) + .withStyle(cellStyle(DocumentInsets.zero(), + DocumentTableTextAnchor.TOP_RIGHT))); + }); + block.addParagraph(p -> p + .name("JobCompany" + index) + .text(companyLine(entry)) + .lineSpacing(0) + .textStyle(style(BODY_FONT, JOB_META_SIZE, ACCENT, false)) + .margin(0f, 0f, (float) COMPANY_TO_BULLETS, 0f)); + block.addList(list -> { + list.name("JobBullets" + index); + list.items(OrangeOpsWidgets.lines(entry.body())); + list.marker(ListMarker.bullet()); + list.textStyle(style(BODY_FONT, BODY_SIZE, BODY, false)); + list.lineSpacing(leading(BULLET_PITCH, BODY_SIZE)); + list.itemSpacing(gap(BULLET_ITEM_PITCH, BULLET_PITCH)); + }); + } + }); + } + + /** The employer, and where the role was held when the entry says so. */ + private static String companyLine(CvEntry entry) { + if (entry.place().isBlank()) { + return entry.subtitle(); + } + return entry.subtitle().isBlank() + ? entry.place() + : entry.subtitle() + " | " + entry.place(); + } + + /** + * The metric strip — a mark, a number and its caption lines, per metric, + * with hairlines between them. + * + *

    One table of {@code 2n-1} columns: the metric columns interleaved with + * hairline-wide columns whose cell style is filled with the rule colour. + * That is how the separators run the block's full height — a table's rules + * are drawn per cell with no per-edge control, so a stroke would box all + * four edges of a metric cell instead.

    + * + *

    A row per line — mark, number, then one row per caption line — rather + * than one composite node per metric, so the metrics sit on a shared + * baseline grid however long their captions are. A metric with fewer + * caption lines than its neighbours leaves its lower rows empty.

    + */ + private static void renderMetrics(SectionBuilder main, EntriesSection metrics) { + main.addSection("Metrics", block -> { + block.spacing(0); + heading(block, "Metrics", metrics.title()); + List entries = metrics.entries(); + List> captions = new ArrayList<>(); + int captionRows = 0; + for (CvEntry entry : entries) { + List lines = OrangeOpsWidgets.lines(entry.body()); + captions.add(lines); + captionRows = Math.max(captionRows, lines.size()); + } + int rows = captionRows; + int separators = entries.size() - 1; + double metricWidth = (MAIN_WIDTH - separators * METRIC_SEPARATOR) / entries.size(); + block.addTable(table -> { + table.name("MetricTable"); + table.width(MAIN_WIDTH); + table.margin(new DocumentInsets(METRIC_RULE_TO_BODY, 0, 0, 0)); + DocumentTableColumn[] columns = new DocumentTableColumn[2 * entries.size() - 1]; + for (int i = 0; i < columns.length; i++) { + columns[i] = (i % 2 == 0) + ? DocumentTableColumn.fixed(metricWidth) + : DocumentTableColumn.fixed(METRIC_SEPARATOR); + } + table.columns(columns); + table.defaultCellStyle(cellStyle(DocumentInsets.zero(), + DocumentTableTextAnchor.CENTER)); + + table.rowCells(metricRow(entries.size(), + i -> markCell(entries.get(i).icon(), i))); + table.rowStyle(0, cellStyle(new DocumentInsets(0, 0, METRIC_ICON_TO_VALUE, 0), + DocumentTableTextAnchor.CENTER)); + table.rowCells(metricRow(entries.size(), i -> centredCell("MetricValue" + i, + entries.get(i).title(), METRIC_VALUE_SIZE, ACCENT, true))); + table.rowStyle(1, cellStyle(new DocumentInsets(0, 0, METRIC_VALUE_TO_CAPTION, 0), + DocumentTableTextAnchor.CENTER)); + for (int row = 0; row < rows; row++) { + int line = row; + table.rowCells(metricRow(entries.size(), i -> { + List lines = captions.get(i); + String text = line < lines.size() ? lines.get(line) : ""; + return centredCell("MetricCaption" + line + "_" + i, text, + METRIC_CAPTION_SIZE, BODY, false); + })); + } + }); + }); + } + + /** One metric row: metric cells interleaved with the hairline cells. */ + private static DocumentTableCell[] metricRow(int metrics, + IntFunction cell) { + DocumentTableCell[] cells = new DocumentTableCell[2 * metrics - 1]; + for (int i = 0; i < cells.length; i++) { + cells[i] = (i % 2 == 0) + ? cell.apply(i / 2) + : DocumentTableCell.node(new SpacerBuilder() + .name("MetricSeparator" + i) + .width(METRIC_SEPARATOR) + .build()) + .withStyle(DocumentTableStyle.builder() + .padding(DocumentInsets.zero()) + .fillColor(RULE) + .textAnchor(DocumentTableTextAnchor.CENTER) + .stroke(NO_STROKE) + .build()); + } + return cells; + } + + private static DocumentTableCell markCell(String token, int index) { + ParagraphBuilder paragraph = new ParagraphBuilder(); + paragraph.name("MetricMark" + index); + paragraph.align(TextAlign.CENTER); + paragraph.lineSpacing(0); + if (token.isBlank()) { + paragraph.textStyle(style(BODY_FONT, METRIC_CAPTION_SIZE, BODY, false)); + } else { + paragraph.textStyle(style(BODY_FONT, + OrangeOpsWidgets.markLineSize(token, METRIC_CAPTION_SIZE), BODY, false)); + OrangeOpsWidgets.mark(paragraph, token); + } + return DocumentTableCell.node(paragraph.build()); + } + + private static DocumentTableCell centredCell(String name, String text, double size, + DocumentColor color, boolean bold) { + return DocumentTableCell.node(new ParagraphBuilder() + .name(name) + .text(text) + .align(TextAlign.CENTER) + .lineSpacing(0) + .textStyle(style(BODY_FONT, size, color, bold)) + .build()); + } + + /** The closing lines — an inline mark, a bold label and its value, per line. */ + private static void renderAdditional(SectionBuilder main, EntriesSection additional) { + main.addSection("Additional", block -> { + // spacing(0), not the item pitch: a main heading is two nodes — the + // label and its accent rule — and a section spacing would open the + // gap between them as well. + double itemGap = gap(ADDITIONAL_PITCH, LATO.lineBox(BODY_SIZE)); + block.spacing(0); + heading(block, "Additional", additional.title()); + List entries = additional.entries(); + for (int i = 0; i < entries.size(); i++) { + CvEntry entry = entries.get(i); + int index = i; + boolean first = i == 0; + block.addParagraph(p -> { + p.name("Additional" + index); + p.lineSpacing(0); + if (entry.icon().isBlank()) { + p.textStyle(style(BODY_FONT, BODY_SIZE, BODY, false)); + } else { + p.textStyle(style(BODY_FONT, + OrangeOpsWidgets.markLineSize(entry.icon(), BODY_SIZE), + BODY, false)); + OrangeOpsWidgets.mark(p, entry.icon()); + p.inlineText(" ", style(BODY_FONT, BODY_SIZE, BODY, false)); + } + p.inlineText(entry.title(), style(BODY_FONT, BODY_SIZE, INK, true)); + if (!entry.body().isBlank()) { + p.inlineText(" ", style(BODY_FONT, BODY_SIZE, BODY, false)); + p.inlineText(entry.body(), style(BODY_FONT, BODY_SIZE, BODY, false)); + } + p.margin((float) (first ? ADDITIONAL_RULE_TO_BODY : itemGap), 0f, 0f, 0f); + }); + } + }); + } + + /** A berth's title, with any parenthetical set as the smaller suffix. */ + private static void heading(SectionBuilder block, String name, String title) { + int bracket = title.indexOf('('); + if (bracket < 0) { + OrangeOpsWidgets.mainHeading(block, name, title, null); + return; + } + OrangeOpsWidgets.mainHeading(block, name, title.substring(0, bracket).stripTrailing(), + title.substring(bracket)); + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/OrangeOpsMasthead.java b/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/OrangeOpsMasthead.java new file mode 100644 index 000000000..c8a11ec73 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/OrangeOpsMasthead.java @@ -0,0 +1,257 @@ +package com.demcha.compose.document.templates.cv.presets; + +import com.demcha.compose.document.dsl.LineBuilder; +import com.demcha.compose.document.dsl.PageFlowBuilder; +import com.demcha.compose.document.dsl.ParagraphBuilder; +import com.demcha.compose.document.dsl.PathBuilder; +import com.demcha.compose.document.dsl.ShapeContainerBuilder; +import com.demcha.compose.document.node.DocumentLinkOptions; +import com.demcha.compose.document.node.DocumentNode; +import com.demcha.compose.document.node.HorizontalAlign; +import com.demcha.compose.document.node.LayerAlign; +import com.demcha.compose.document.node.RowVerticalAlign; +import com.demcha.compose.document.style.DocumentInsets; +import com.demcha.compose.document.style.DocumentRowColumn; +import com.demcha.compose.document.svg.SvgPath; +import com.demcha.compose.document.templates.core.identity.Contact; +import com.demcha.compose.document.templates.core.identity.ContactUri; +import com.demcha.compose.document.templates.core.identity.Link; +import com.demcha.compose.document.templates.cv.data.CvIdentity; +import com.demcha.compose.document.templates.cv.data.CvName; + +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; + +import static com.demcha.compose.document.templates.cv.presets.OrangeOpsStyles.ACCENT; +import static com.demcha.compose.document.templates.cv.presets.OrangeOpsStyles.BAR_TO_CONTACT; +import static com.demcha.compose.document.templates.cv.presets.OrangeOpsStyles.BODY; +import static com.demcha.compose.document.templates.cv.presets.OrangeOpsStyles.BODY_FONT; +import static com.demcha.compose.document.templates.cv.presets.OrangeOpsStyles.CONTACT_SEPARATOR_HEIGHT; +import static com.demcha.compose.document.templates.cv.presets.OrangeOpsStyles.CONTACT_SIZE; +import static com.demcha.compose.document.templates.cv.presets.OrangeOpsStyles.CONTACT_TO_BODY; +import static com.demcha.compose.document.templates.cv.presets.OrangeOpsStyles.DISPLAY_FONT; +import static com.demcha.compose.document.templates.cv.presets.OrangeOpsStyles.INK; +import static com.demcha.compose.document.templates.cv.presets.OrangeOpsStyles.MARGIN_X; +import static com.demcha.compose.document.templates.cv.presets.OrangeOpsStyles.NAME_SIZE; +import static com.demcha.compose.document.templates.cv.presets.OrangeOpsStyles.NAME_TO_BAR; +import static com.demcha.compose.document.templates.cv.presets.OrangeOpsStyles.ON_DARK; +import static com.demcha.compose.document.templates.cv.presets.OrangeOpsStyles.ROLE_BAR_HEIGHT; +import static com.demcha.compose.document.templates.cv.presets.OrangeOpsStyles.ROLE_BAR_SLANT; +import static com.demcha.compose.document.templates.cv.presets.OrangeOpsStyles.ROLE_BAR_WIDTH; +import static com.demcha.compose.document.templates.cv.presets.OrangeOpsStyles.ROLE_SIZE; +import static com.demcha.compose.document.templates.cv.presets.OrangeOpsStyles.ROLE_TEXT_INSET; +import static com.demcha.compose.document.templates.cv.presets.OrangeOpsStyles.RULE; +import static com.demcha.compose.document.templates.cv.presets.OrangeOpsStyles.RULE_THICKNESS; +import static com.demcha.compose.document.templates.cv.presets.OrangeOpsStyles.SLASHES; +import static com.demcha.compose.document.templates.cv.presets.OrangeOpsStyles.SLASH_BLOCK_WIDTH; +import static com.demcha.compose.document.templates.cv.presets.OrangeOpsStyles.SLASH_BLOCK_X; +import static com.demcha.compose.document.templates.cv.presets.OrangeOpsStyles.px; +import static com.demcha.compose.document.templates.cv.presets.OrangeOpsStyles.style; + +/** + * The three bands above the body row: the two-tone name, the dark role bar with + * its accent slashes, and the contact strip. + * + *

    Why the role bar is a layer stack and not a row

    + * + *

    The plate's bounding box and the slash group's bounding box overlap + * horizontally, and a row cannot place two cells that overlap. The plate is a + * shape container so the white role text is its child — the text is inside the + * bar, not laid beside it — and the three slashes are one path node carrying + * three closed sub-paths, for the same overlap reason.

    + * + *

    Both shapes are described in SVG user space through + * {@link SvgPath#parse(String, double, double, double, double)} rather than raw + * path coordinates: a path builder's own coordinates are bottom-left-origin and + * normalised, and a lean read the wrong way up is a silent mirror rather than + * an error.

    + */ +final class OrangeOpsMasthead { + + private OrangeOpsMasthead() { + } + + /** + * Draws the three bands. + * + * @param page the page flow + * @param identity whose CV this is + */ + static void render(PageFlowBuilder page, CvIdentity identity) { + renderName(page, identity.name()); + renderRoleBar(page, identity.jobTitle()); + renderContactStrip(page, identity); + } + + /** + * The name: one paragraph, two inline runs. + * + *

    The colour change between the given and the family name is a run + * boundary rather than a second node, so the halves cannot drift apart or + * wrap independently of each other.

    + */ + private static void renderName(PageFlowBuilder page, CvName name) { + page.addSection("Masthead", block -> { + block.spacing(0); + block.padding(0f, (float) MARGIN_X, 0f, (float) MARGIN_X); + block.addParagraph(p -> p + .name("Name") + .lineSpacing(0) + .textStyle(style(DISPLAY_FONT, NAME_SIZE, INK, true)) + .inlineText(name.first().toUpperCase(Locale.ROOT), + style(DISPLAY_FONT, NAME_SIZE, INK, true)) + .inlineText(" ", style(DISPLAY_FONT, NAME_SIZE, INK, true)) + .inlineText(name.last().toUpperCase(Locale.ROOT), + style(DISPLAY_FONT, NAME_SIZE, ACCENT, true)) + .margin(0f, 0f, (float) NAME_TO_BAR, 0f)); + }); + } + + /** The role bar: a dark trapezoid carrying the role, and three accent slashes. */ + private static void renderRoleBar(PageFlowBuilder page, String discipline) { + page.addLayerStack(stack -> { + stack.name("RoleBar"); + stack.margin(new DocumentInsets(0, 0, BAR_TO_CONTACT, 0)); + stack.position(slashes(), SLASH_BLOCK_X, 0, LayerAlign.TOP_LEFT, 0); + stack.position(plate(discipline), 0, 0, LayerAlign.TOP_LEFT, 1); + }); + } + + /** + * The dark plate. Its right edge leans left going down by the band's shared + * slant, which is the same lean every slash takes. + */ + private static DocumentNode plate(String discipline) { + String d = "M0,0 L" + px(ROLE_BAR_WIDTH) + ",0 L" + + px(ROLE_BAR_WIDTH - ROLE_BAR_SLANT) + "," + px(ROLE_BAR_HEIGHT) + + " L0," + px(ROLE_BAR_HEIGHT) + " Z"; + SvgPath shape = SvgPath.parse(d, 0, 0, px(ROLE_BAR_WIDTH), px(ROLE_BAR_HEIGHT)); + return new ShapeContainerBuilder() + .name("RoleBarPlate") + .path(ROLE_BAR_WIDTH, ROLE_BAR_HEIGHT, shape) + .fillColor(INK) + .position(new ParagraphBuilder() + .name("RoleText") + .text(discipline.toUpperCase(Locale.ROOT)) + .lineSpacing(0) + .textStyle(style(BODY_FONT, ROLE_SIZE, ON_DARK, true)) + .build(), ROLE_TEXT_INSET, 0, LayerAlign.CENTER_LEFT) + .build(); + } + + /** + * The three slashes as one node. + * + *

    Their bounding boxes overlap — the second starts left of where the + * first ends — so they cannot be three siblings with positive gaps between + * them. Three closed sub-paths in one path is what the design shows.

    + */ + private static DocumentNode slashes() { + StringBuilder d = new StringBuilder(); + for (double[] shape : SLASHES) { + if (d.length() > 0) { + d.append(' '); + } + d.append(slash(shape[0], shape[1])); + } + SvgPath shape = SvgPath.parse(d.toString(), 0, 0, + px(SLASH_BLOCK_WIDTH), px(ROLE_BAR_HEIGHT)); + return new PathBuilder() + .name("RoleSlashes") + .size(SLASH_BLOCK_WIDTH, ROLE_BAR_HEIGHT) + .svg(shape) + .fillColor(ACCENT) + .build(); + } + + /** + * One slash, from where its top edge starts and how wide it is. + * + *

    Two numbers, not four: a slash leans by the band's shared slant like + * everything else on it, so its foot is derived rather than measured.

    + */ + private static String slash(double topLeftPx, double widthPx) { + double h = px(ROLE_BAR_HEIGHT); + double top = topLeftPx * OrangeOpsStyles.PX; + double bottom = top - ROLE_BAR_SLANT; + double width = widthPx * OrangeOpsStyles.PX; + return "M" + px(top) + ",0 L" + px(top + width) + ",0 L" + + px(bottom + width) + "," + h + " L" + px(bottom) + "," + h + " Z"; + } + + /** + * The contact strip: content-sized items with hairlines between them. + * + *

    A top-level row. The item cells are {@link DocumentRowColumn#auto()} so + * each is exactly as wide as its content, and the separator cells are + * {@code weight(1)} so whatever width is left over is split evenly between + * them — which is what lands the last item on the right margin without + * measuring any of the strings.

    + * + *

    The three channels are always drawn: a {@code Contact} rejects a blank + * field, so the triple is non-blank by construction. Each link is drawn as + * its own label with the address behind it, so the strip's width does not + * depend on how long an address happens to be.

    + */ + private static void renderContactStrip(PageFlowBuilder page, CvIdentity identity) { + Contact contact = identity.contact(); + List channels = new ArrayList<>(); + channels.add(new Channel(OrangeOpsIcons.PHONE, contact.phone(), ContactUri.tel(contact.phone()))); + channels.add(new Channel(OrangeOpsIcons.EMAIL, contact.email(), + "mailto:" + contact.email())); + channels.add(new Channel(OrangeOpsIcons.LOCATION, contact.address(), null)); + for (Link link : identity.links()) { + channels.add(new Channel(markFor(link), link.label(), link.url())); + } + + page.addRow("ContactStrip", row -> { + row.spacing(0); + row.padding(new DocumentInsets(0, MARGIN_X, CONTACT_TO_BODY, MARGIN_X)); + row.verticalAlign(RowVerticalAlign.CENTER); + DocumentRowColumn[] columns = new DocumentRowColumn[2 * channels.size() - 1]; + for (int i = 0; i < columns.length; i++) { + columns[i] = (i % 2 == 0) ? DocumentRowColumn.auto() : DocumentRowColumn.weight(1.0); + } + row.columns(columns); + for (int i = 0; i < channels.size(); i++) { + int index = i; + if (i > 0) { + row.addSection("ContactSeparator" + index, gap -> gap + .spacing(0) + .addAligned(HorizontalAlign.CENTER, new LineBuilder() + .name("ContactSeparator" + index + "Line") + .vertical(CONTACT_SEPARATOR_HEIGHT) + .thickness(RULE_THICKNESS) + .color(RULE) + .build())); + } + Channel channel = channels.get(i); + row.addParagraph(p -> { + p.name("Contact" + index); + p.lineSpacing(0); + p.textStyle(style(BODY_FONT, CONTACT_SIZE, BODY, false)); + OrangeOpsWidgets.mark(p, channel.token()); + p.inlineText(" ", style(BODY_FONT, CONTACT_SIZE, BODY, false)); + if (channel.href() == null) { + p.inlineText(channel.label(), style(BODY_FONT, CONTACT_SIZE, BODY, false)); + } else { + p.inlineText(channel.label(), style(BODY_FONT, CONTACT_SIZE, BODY, false), + new DocumentLinkOptions(channel.href())); + } + }); + } + }); + } + + /** The mark for the network a link points at, or the globe. */ + private static String markFor(Link link) { + return link.url().toLowerCase(Locale.ROOT).contains("linkedin.") + ? OrangeOpsIcons.LINKEDIN + : OrangeOpsIcons.WEBSITE; + } + + /** One item of the strip: its mark, what it reads, and where it points. */ + private record Channel(String token, String label, String href) { + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/OrangeOpsStyles.java b/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/OrangeOpsStyles.java new file mode 100644 index 000000000..8e466e0d0 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/OrangeOpsStyles.java @@ -0,0 +1,415 @@ +package com.demcha.compose.document.templates.cv.presets; + +import com.demcha.compose.document.api.DocumentPageSize; +import com.demcha.compose.document.style.DocumentColor; +import com.demcha.compose.document.style.DocumentInsets; +import com.demcha.compose.document.style.DocumentStroke; +import com.demcha.compose.document.style.DocumentTextDecoration; +import com.demcha.compose.document.style.DocumentTextStyle; +import com.demcha.compose.document.table.DocumentTableStyle; +import com.demcha.compose.document.table.DocumentTableTextAnchor; +import com.demcha.compose.font.FontName; + +/** + * Design tokens for {@link OrangeOps} — the palette, the type scale, and the + * geometry every part of the sheet measures against. + * + *

    One scale, not forty measurements

    + * + *

    The design was drawn at 1055 px across an A4 page, so {@link #PX} converts + * every length below from that raster to points. Re-measuring the design at a + * different resolution changes this one number rather than each constant that + * came off it.

    + * + *

    Ink gaps and box gaps are different things

    + * + *

    A vertical distance read off a design is between two pieces of + * ink — the foot of one cap band to the top of the next. Every + * vertical distance the engine accepts is between two boxes, and a + * line box reaches above its cap and below its baseline. {@link Face} holds the + * four fractions that separate the two, and {@link #boxGap} is the only place + * the conversion happens.

    + */ +final class OrangeOpsStyles { + + private OrangeOpsStyles() { + } + + /** + * What one line of a face occupies, as fractions of the type size. + * + *

    The numbers are measured from rendered output rather than read out of + * the font tables: the engine derives its line box from the face's ascent + * and descent, which for a condensed display face is close to 1.5 times the + * type size and not the 1.2 a body face gives.

    + * + * @param lineFactor box height, as a multiple of the type size + * @param capTop box top to cap top + * @param capHeight the cap band itself + * @param capBottom baseline to box bottom + */ + record Face(double lineFactor, double capTop, double capHeight, double capBottom) { + + double lineBox(double size) { + return lineFactor * size; + } + + double capTop(double size) { + return capTop * size; + } + + double capBottom(double size) { + return capBottom * size; + } + + /** The type size whose cap band is {@code capHeightPoints} tall. */ + double sizeForCap(double capHeightPoints) { + return capHeightPoints / capHeight; + } + } + + static final DocumentPageSize PAGE = DocumentPageSize.A4; + static final double PAGE_WIDTH = PAGE.width(); + + /** The design's raster is 1055 px wide; every length below came off it. */ + static final double PX = PAGE_WIDTH / 1055.0; + + static final FontName BODY_FONT = FontName.LATO; + + /** + * The display family, named but not carried. + * + *

    Oswald is not one of the families {@code graph-compose-fonts} ships, + * so a caller registers it on the session before composing — see the class + * documentation on {@link OrangeOps}, which republishes this name as its + * own public constant so a caller has something to register against.

    + */ + static final FontName DISPLAY_FONT = FontName.of("Oswald"); + + static final Face LATO = new Face(1.200, 0.2705, 0.7165, 0.2130); + static final Face DISPLAY = new Face(1.482, 0.3670, 0.8460, 0.2690); + + /* Sized so the display face's cap band matches the design's. */ + static final double NAME_SIZE = DISPLAY.sizeForCap(79 * PX); + static final double HEADING_SIZE = DISPLAY.sizeForCap(17 * PX); + static final double ROLE_SIZE = LATO.sizeForCap(14 * PX); + + /* + * The body sizes are fitted to measured string widths, not to cap heights. + * + * A cap height pins the display face, whose job is to be as tall as the + * design's. Running text has a different job: it has to break where the + * design breaks. Sizing the body faces from cap heights instead put every + * one of them about 7% too wide, wrapped an extra line in four places, and + * pushed the body onto a second page. + */ + static final double BODY_SIZE = 8.2; + static final double HEADING_SUFFIX_SIZE = 8.8; + static final double CONTACT_SIZE = 8.2; + + /* + * An achievement's title is display face, not body face: measured against + * the design it wants 13 px of cap and 224 px of width, and in a body face + * those two ask for sizes 1.4 apart — which is what a condensed face looks + * like when it is measured as though it were not one. + */ + static final double ACHIEVEMENT_TITLE_SIZE = DISPLAY.sizeForCap(13 * PX); + static final double ACHIEVEMENT_BODY_SIZE = 8.4; + static final double EDUCATION_TITLE_SIZE = 8.4; + static final double EDUCATION_LINE_SIZE = 8.0; + static final double CERT_TITLE_SIZE = 8.2; + static final double CERT_ISSUER_SIZE = 6.8; + static final double JOB_TITLE_SIZE = 9.3; + static final double JOB_META_SIZE = 8.5; + static final double METRIC_VALUE_SIZE = LATO.sizeForCap(23 * PX); + static final double METRIC_CAPTION_SIZE = 8.6; + + static final double MARGIN_X = 41 * PX; + + /** + * Zero, and measured rather than chosen. + * + *

    The design's name cap starts 30 px down the page. A paragraph's box + * begins {@link Face#capTop} above its cap band, and for the display face at + * {@link #NAME_SIZE} that alone is more than 30 px — so no positive top + * margin puts the cap where the design has it, and zero is as close as the + * page can get.

    + */ + static final double MARGIN_TOP = 0.0; + + /** + * The design's last line of ink sits 34 px above the paper's edge; the box + * that line lives in reaches lower than the ink does, so the margin is that + * measurement less the descender it has to clear. + */ + static final double MARGIN_BOTTOM = 34 * PX - LATO.capBottom(BODY_SIZE); + + static final double CONTENT_WIDTH = PAGE_WIDTH - 2 * MARGIN_X; + static final double ASIDE_WIDTH = 309 * PX; + static final double GUTTER = 63 * PX; + static final double MAIN_WIDTH = CONTENT_WIDTH - ASIDE_WIDTH - GUTTER; + + /** + * The body row spans the whole page, because the page margins are zero and + * each column carries its own padding. The split falls in the middle of the + * gutter, which is what puts the main cell's rule on the divider the design + * shows. + */ + static final double ASIDE_CELL_WEIGHT = (MARGIN_X + ASIDE_WIDTH + GUTTER / 2) / PAGE_WIDTH; + static final double MAIN_CELL_WEIGHT = 1.0 - ASIDE_CELL_WEIGHT; + + static final double ROLE_BAR_HEIGHT = 39 * PX; + static final double ROLE_BAR_WIDTH = 561 * PX; + + /** + * The lean every edge on the role band shares. + * + *

    Tracked row by row down the design, the plate's right edge and all + * three slashes move left by the same 24 px across the 35 rows between the + * band's second row and its second from last. One ratio, four edges — so + * the plate's slant and each slash's foot are derived from it rather than + * measured one at a time. Measuring them separately put four different + * leans on the band and made the slashes visibly diverge from the plate.

    + */ + static final double SLANT_RATIO = 24.0 / 35.0; + static final double ROLE_BAR_SLANT = SLANT_RATIO * ROLE_BAR_HEIGHT; + + /** + * Each slash as {left edge of its top, width}, in design pixels relative to + * {@link #SLASH_BLOCK_X}. The foot is not listed because it is not a + * measurement: it is the top less {@link #ROLE_BAR_SLANT}. + */ + static final double[][] SLASHES = {{26.7, 19}, {53.7, 19}, {80.7, 35}}; + static final double SLASH_BLOCK_X = 545 * PX; + static final double SLASH_BLOCK_WIDTH = 116 * PX; + + /** + * The one gap allowed to be negative. + * + *

    The design leaves 18 px between the name's cap band and the top of the + * role bar, and the display face's box reaches further below its cap band + * than that — so the bar starts inside the name's box, and a clamped + * conversion cannot say so. Clamped, it put the bar 11 px low and, with it, + * everything down the page.

    + */ + static final double NAME_TO_BAR = 18 * PX - DISPLAY.capBottom(NAME_SIZE); + + static final double CONTACT_SEPARATOR_HEIGHT = 20 * PX; + static final double ROLE_TEXT_INSET = MARGIN_X; + + static final double RULE_THICKNESS = 0.9; + static final double ACCENT_RULE_THICKNESS = 3 * PX; + static final double COLUMN_RULE_THICKNESS = 3 * PX; + static final double MAIN_HEADING_RULE_WIDTH = 110 * PX; + + /* + * Neither column spaces its joins the same way twice. The aside goes 26/20 + * px, then 22/15, then 18/13; the main column 28/21, then 24/16, then + * 15/14. Both tighten as their column fills, which is what a designer does + * to balance two columns of different weight, so each join is spaced as the + * design spaces it rather than all of them from the first. Spacing them + * uniformly put the last aside block 24 px low. + * + * Each row is {above, below} in design pixels, top join first. A column + * with more blocks than the design's four reuses the last row, which is the + * tightest. + */ + static final double[][] ASIDE_JOIN_INK = {{26, 20}, {22, 15}, {18, 13}}; + static final double[][] MAIN_JOIN_INK = {{28, 21}, {24, 16}, {15, 14}}; + + static final double SKILL_PITCH = 22 * PX; + static final double ACHIEVEMENT_BODY_PITCH = 18 * PX; + static final double EDUCATION_LINE_PITCH = 21 * PX; + static final double CERT_PITCH = 46 * PX; + static final double PROFILE_PITCH = 20.6 * PX; + static final double BULLET_PITCH = 18 * PX; + static final double BULLET_ITEM_PITCH = 24 * PX; + static final double ADDITIONAL_PITCH = 24 * PX; + + static final double ACHIEVEMENT_CIRCLE = 58 * PX; + static final double EDUCATION_CIRCLE = 52 * PX; + static final double ACHIEVEMENT_ICON_COLUMN = 74 * PX; + + /** + * The achievement cards stop short of the aside's right edge. + * + *

    No body line in any of the design's four cards passes 341 px, and the + * second line of the first card wraps at a word that would have taken it to + * 345 — so the column ends there rather than at the aside's own 350. Given + * the full width the cards fit one more word per line and every one of them + * comes out a line short, which is where the whole of the lower aside's + * drift came from.

    + */ + static final double ACHIEVEMENT_TEXT_INSET = 9 * PX; + static final double EDUCATION_ICON_COLUMN = 69 * PX; + static final double CERT_TEXT_INSET = 16 * PX; + static final double BULLET_SIZE = 5 * PX; + + /** + * The distance from a skill's accent dot to its name. + * + *

    Recovered rather than declared. The dot and the name were a paragraph + * with four literal spaces between them, and four spaces of this face at the + * aside's body size are a fact about the face rather than a distance the + * design asked for. The nearest round value in the sheet's own unit is what + * stands here, and the difference it makes is recorded with the change.

    + */ + static final double SKILL_BULLET_GAP = 15 * PX; + static final double DATE_COLUMN_RATIO = 0.30; + static final double METRIC_SEPARATOR = 0.6; + + /** The design's glyph is a little under half the disc it sits in. */ + static final double BADGE_GLYPH_SHARE = 0.48; + + static final DocumentColor ACCENT = DocumentColor.rgb(0xEF, 0x5B, 0x03); + static final DocumentColor INK = DocumentColor.rgb(0x1E, 0x24, 0x2D); + static final DocumentColor BODY = DocumentColor.rgb(0x33, 0x3A, 0x42); + static final DocumentColor MUTED = DocumentColor.rgb(0x55, 0x59, 0x5F); + static final DocumentColor RULE = DocumentColor.rgb(0xC9, 0xCB, 0xCD); + static final DocumentColor ON_DARK = DocumentColor.rgb(0xFF, 0xFF, 0xFF); + + /** + * A table's rules are drawn per cell from that cell's own stroke, and the + * default style has one. Every table on this page is a layout device rather + * than a ruled grid, so every cell style starts by turning it off. + */ + static final DocumentStroke NO_STROKE = DocumentStroke.of(DocumentColor.rgba(0, 0, 0, 0), 0.0); + + /* Gaps shared by more than one part of the sheet. */ + + static final double BAR_TO_CONTACT = boxGap(18, null, 0, LATO, CONTACT_SIZE); + + /** The design's last contact line descends, so its 29 px is measured from that. */ + static final double CONTACT_TO_BODY = + boxGap(29, LATO, CONTACT_SIZE, true, DISPLAY, HEADING_SIZE); + + static final double HEADING_TO_RULE = boxGap(8, DISPLAY, HEADING_SIZE, null, 0); + + /** + * How far a block's first line sits below its accent rule. + * + *

    The aside's four blocks agree closely enough to share one value — 15, + * 17, 11 and 13 px — but the main column's four do not, and the reason is + * what sits on the first line. Where it is running text the design leaves + * 12 px to the cap; where it is a job title, 16; where it is a row of icons, + * 3, because an icon has no cap band to clear. One shared constant put the + * metric strip and the closing block 8 px low each.

    + */ + static final double RULE_TO_BODY = boxGap(15, null, 0, LATO, BODY_SIZE); + static final double PROFILE_RULE_TO_BODY = boxGap(12, null, 0, LATO, BODY_SIZE); + static final double EXPERIENCE_RULE_TO_BODY = boxGap(16, null, 0, LATO, JOB_TITLE_SIZE); + static final double METRIC_RULE_TO_BODY = boxGap(3, null, 0, null, 0); + static final double ADDITIONAL_RULE_TO_BODY = boxGap(8, null, 0, null, 0); + + static final double ACHIEVEMENT_TITLE_TO_BODY = + boxGap(9, DISPLAY, ACHIEVEMENT_TITLE_SIZE, LATO, ACHIEVEMENT_BODY_SIZE); + + /** A card's body ends on a full sentence, which descends either way. */ + static final double ACHIEVEMENT_GAP = + boxGap(19, LATO, ACHIEVEMENT_BODY_SIZE, true, DISPLAY, ACHIEVEMENT_TITLE_SIZE); + + static final double CERT_TITLE_TO_ISSUER = + boxGap(6, LATO, CERT_TITLE_SIZE, true, LATO, CERT_ISSUER_SIZE); + + /** What is left of a certification's measured pitch once its two lines are in it. */ + static final double CERT_GAP = Math.max(0.0, CERT_PITCH + - LATO.lineBox(CERT_TITLE_SIZE) + - CERT_TITLE_TO_ISSUER + - LATO.lineBox(CERT_ISSUER_SIZE)); + + /* + * 7 px, not the 9 px average: the design's three instances are 7, 9 and 10, + * but only the first job's title ends in a descender and the other two are + * measured from a baseline. Converting the descender instance is the one + * that gives the box gap all three share. + */ + static final double JOB_TITLE_TO_COMPANY = + boxGap(7, LATO, JOB_TITLE_SIZE, true, LATO, JOB_META_SIZE); + static final double COMPANY_TO_BULLETS = + boxGap(13, LATO, JOB_META_SIZE, true, LATO, BODY_SIZE); + static final double JOB_GAP = boxGap(27, LATO, BODY_SIZE, true, LATO, JOB_TITLE_SIZE); + + static final double METRIC_ICON_TO_VALUE = boxGap(11, null, 0, LATO, METRIC_VALUE_SIZE); + static final double METRIC_VALUE_TO_CAPTION = + boxGap(9, LATO, METRIC_VALUE_SIZE, LATO, METRIC_CAPTION_SIZE); + + static DocumentTextStyle style(FontName font, double size, DocumentColor color, boolean bold) { + return DocumentTextStyle.builder() + .fontName(font) + .size(size) + .color(color) + .decoration(bold ? DocumentTextDecoration.BOLD : DocumentTextDecoration.DEFAULT) + .build(); + } + + static DocumentTableStyle cellStyle(DocumentInsets padding, DocumentTableTextAnchor anchor) { + return DocumentTableStyle.builder() + .padding(padding) + .textAnchor(anchor) + .stroke(NO_STROKE) + .build(); + } + + /** + * A gap the design shows between two pieces of ink, as the gap between the + * two boxes that produce it. + * + * @param inkPixels the measured distance, in design pixels + * @param above the face of the line above, or {@code null} for a rule or a shape + * @param sizeAbove its type size + * @param below the face of the line below, or {@code null} for a rule or a shape + * @param sizeBelow its type size + * @return the margin to declare, never negative + */ + static double boxGap(double inkPixels, Face above, double sizeAbove, + Face below, double sizeBelow) { + return boxGap(inkPixels, above, sizeAbove, false, below, sizeBelow); + } + + /** + * The same conversion, told whether the line above ends in a descender. + * + *

    Subtracting the baseline-to-box depth of the line above assumes its ink + * stops at the baseline. A line ending in a p or a g does not: its ink + * already reaches the box, and subtracting that depth a second time takes + * the gap 3 to 5 px too tight. Three such gaps inside one experience entry + * cost 13 px, and three entries put the whole lower main column 42 px + * high.

    + * + * @param inkPixels the measured distance, in design pixels + * @param above the face of the line above, or {@code null} + * @param sizeAbove that line's type size + * @param descenderAbove whether the design's own text on the line above + * descends past its baseline + * @param below the face of the line below, or {@code null} + * @param sizeBelow that line's type size + * @return the margin to declare, never negative + */ + static double boxGap(double inkPixels, Face above, double sizeAbove, boolean descenderAbove, + Face below, double sizeBelow) { + double ink = inkPixels * PX; + double descent = (above == null || descenderAbove) ? 0.0 : above.capBottom(sizeAbove); + double ascent = below == null ? 0.0 : below.capTop(sizeBelow); + return Math.max(0.0, ink - descent - ascent); + } + + /** A measured pitch, less the content it has to clear, is the gap that produces it. */ + static double gap(double pitch, double contentHeight) { + return Math.max(0.0, pitch - contentHeight); + } + + /** + * A measured leading, as the additive value {@code lineSpacing} actually is: + * the engine's line box is the face's line factor times the type size and + * {@code lineSpacing} adds points on top of it, so a CSS-style multiple + * would be silently wrong. + */ + static double leading(double measuredLeading, double size) { + return Math.max(0.0, measuredLeading - LATO.lineBox(size)); + } + + /** Rounds a point value for an SVG user-space coordinate string. */ + static double px(double points) { + return Math.round(points * 100.0) / 100.0; + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/OrangeOpsWidgets.java b/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/OrangeOpsWidgets.java new file mode 100644 index 000000000..561a92f46 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/OrangeOpsWidgets.java @@ -0,0 +1,215 @@ +package com.demcha.compose.document.templates.cv.presets; + +import com.demcha.compose.document.dsl.ParagraphBuilder; +import com.demcha.compose.document.dsl.SectionBuilder; +import com.demcha.compose.document.dsl.SpacerBuilder; +import com.demcha.compose.document.node.DocumentNode; +import com.demcha.compose.document.node.InlineImageAlignment; +import com.demcha.compose.document.style.DocumentColor; +import com.demcha.compose.document.style.DocumentInsets; +import com.demcha.compose.document.table.DocumentTableCell; +import com.demcha.compose.document.table.DocumentTableColumn; +import com.demcha.compose.document.table.DocumentTableStyle; +import com.demcha.compose.document.table.DocumentTableTextAnchor; +import com.demcha.compose.document.templates.cv.presets.OrangeOpsStyles.Face; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; + +import static com.demcha.compose.document.templates.cv.presets.OrangeOpsStyles.ACCENT; +import static com.demcha.compose.document.templates.cv.presets.OrangeOpsStyles.ACCENT_RULE_THICKNESS; +import static com.demcha.compose.document.templates.cv.presets.OrangeOpsStyles.BODY; +import static com.demcha.compose.document.templates.cv.presets.OrangeOpsStyles.BODY_FONT; +import static com.demcha.compose.document.templates.cv.presets.OrangeOpsStyles.DISPLAY; +import static com.demcha.compose.document.templates.cv.presets.OrangeOpsStyles.DISPLAY_FONT; +import static com.demcha.compose.document.templates.cv.presets.OrangeOpsStyles.HEADING_SIZE; +import static com.demcha.compose.document.templates.cv.presets.OrangeOpsStyles.HEADING_SUFFIX_SIZE; +import static com.demcha.compose.document.templates.cv.presets.OrangeOpsStyles.HEADING_TO_RULE; +import static com.demcha.compose.document.templates.cv.presets.OrangeOpsStyles.INK; +import static com.demcha.compose.document.templates.cv.presets.OrangeOpsStyles.LATO; +import static com.demcha.compose.document.templates.cv.presets.OrangeOpsStyles.MAIN_HEADING_RULE_WIDTH; +import static com.demcha.compose.document.templates.cv.presets.OrangeOpsStyles.NO_STROKE; +import static com.demcha.compose.document.templates.cv.presets.OrangeOpsStyles.RULE; +import static com.demcha.compose.document.templates.cv.presets.OrangeOpsStyles.RULE_THICKNESS; +import static com.demcha.compose.document.templates.cv.presets.OrangeOpsStyles.cellStyle; +import static com.demcha.compose.document.templates.cv.presets.OrangeOpsStyles.style; + +/** + * The pieces more than one part of the Orange Ops sheet draws: the two kinds of + * heading, an inline mark, and the badge disc. + * + *

    The two kinds of heading rule

    + * + *

    In the aside the accent rule under a heading is exactly as wide as the + * heading's own text, and no API measures a string — so the pair is a two-row + * table on a single {@link DocumentTableColumn#auto()} column: the column sizes + * itself to the heading, and the second row is a hairline-high spacer in an + * accent-filled cell, which paints across whatever width the column resolved + * to. In the main column the rule is a fixed short length and is a plain line. + * Both come from the design; neither is a guess about the other.

    + */ +final class OrangeOpsWidgets { + + private OrangeOpsWidgets() { + } + + /** Places a packaged mark inline, on the line of the paragraph it opens. */ + static void mark(ParagraphBuilder paragraph, String token) { + paragraph.inlineSvgIcon(OrangeOpsIcons.icon(token), OrangeOpsIcons.size(token), + InlineImageAlignment.CENTER); + } + + /** + * The type size a paragraph needs so an inline mark is not clipped. + * + *

    An inline run is measured into the line box the paragraph's style + * gives it, so a mark taller than its label is drawn clipped to the label + * unless the box is opened up first.

    + */ + static double markLineSize(String token, double labelSize) { + return Math.max(labelSize, OrangeOpsIcons.size(token) / LATO.lineFactor()); + } + + /** + * An aside heading and its text-width accent rule. + * + * @param block the block the heading opens + * @param name the node name, for review and rollback + * @param text the heading + */ + static void asideHeading(SectionBuilder block, String name, String text) { + block.addTable(table -> { + table.name(name + "Heading"); + table.columns(DocumentTableColumn.auto()); + table.defaultCellStyle(cellStyle(DocumentInsets.zero(), + DocumentTableTextAnchor.TOP_LEFT)); + table.rowCells(DocumentTableCell.node(new ParagraphBuilder() + .name(name + "HeadingText") + .text(text) + .lineSpacing(0) + .textStyle(style(DISPLAY_FONT, HEADING_SIZE, INK, true)) + .build())); + table.rowStyle(0, cellStyle(new DocumentInsets(0, 0, HEADING_TO_RULE, 0), + DocumentTableTextAnchor.TOP_LEFT)); + table.rowCells(DocumentTableCell.node(new SpacerBuilder() + .name(name + "HeadingRule") + .height(ACCENT_RULE_THICKNESS) + .build()) + .withStyle(DocumentTableStyle.builder() + .padding(DocumentInsets.zero()) + .fillColor(ACCENT) + .textAnchor(DocumentTableTextAnchor.TOP_LEFT) + .stroke(NO_STROKE) + .build())); + }); + } + + /** + * A main-column heading and its fixed-length accent rule. + * + * @param block the block the heading opens + * @param name the node name, for review and rollback + * @param text the heading + * @param suffix the smaller parenthetical set on the same line, or + * {@code null} when the heading stands alone + */ + static void mainHeading(SectionBuilder block, String name, String text, String suffix) { + block.addParagraph(p -> { + p.name(name + "Heading"); + p.lineSpacing(0); + p.textStyle(style(DISPLAY_FONT, HEADING_SIZE, INK, true)); + p.inlineText(text, style(DISPLAY_FONT, HEADING_SIZE, INK, true)); + if (suffix != null && !suffix.isBlank()) { + p.inlineText(" ", style(BODY_FONT, HEADING_SUFFIX_SIZE, INK, false)); + p.inlineText(suffix, style(BODY_FONT, HEADING_SUFFIX_SIZE, INK, false)); + } + p.margin(0f, 0f, (float) HEADING_TO_RULE, 0f); + }); + block.addLine(line -> line + .name(name + "HeadingRule") + .horizontal(MAIN_HEADING_RULE_WIDTH) + .thickness(ACCENT_RULE_THICKNESS) + .color(ACCENT)); + } + + /** + * One block of a column, and what its last line is set in. + * + *

    The join rule below a block is spaced from the ink above it, and what + * that ink is differs per block — so a block carries the face of its own + * last line rather than the column guessing.

    + * + * @param name the node name of the rule that follows, for review + * @param render draws the block into the column + * @param trailingFace the face of the block's last line + * @param trailingSize that line's type size + * @param trailingDescends whether the design's own last line there descends + */ + record Block(String name, Consumer render, Face trailingFace, + double trailingSize, boolean trailingDescends) { + } + + /** + * Draws a column's blocks with a hairline between each neighbouring pair. + * + *

    A join takes the spacing the design gives the join in that position, + * and a column carrying more blocks than the design's four reuses the last + * row of {@code joinInk}, which is the tightest. A berth nobody filled + * simply is not in {@code blocks}, so it takes its rule with it rather than + * leaving a hairline over a gap.

    + * + * @param column the column to draw into + * @param blocks the blocks that have content, in the order the design has them + * @param joinInk each join's {above, below} spacing in design pixels + */ + static void stack(SectionBuilder column, List blocks, double[][] joinInk) { + for (int i = 0; i < blocks.size(); i++) { + Block block = blocks.get(i); + block.render().accept(column); + if (i == blocks.size() - 1) { + continue; + } + double[] ink = joinInk[Math.min(i, joinInk.length - 1)]; + double above = OrangeOpsStyles.boxGap(ink[0], block.trailingFace(), + block.trailingSize(), block.trailingDescends(), null, 0); + double below = OrangeOpsStyles.boxGap(ink[1], null, 0, DISPLAY, HEADING_SIZE); + column.addLine(line -> line + .name("After" + block.name() + "Rule") + .fill() + .thickness(RULE_THICKNESS) + .color(RULE) + .margin(new DocumentInsets(above, 0, below, 0))); + } + } + + /** A body split into the lines it was written as, blanks dropped. */ + static List lines(String body) { + List out = new ArrayList<>(); + for (String line : body.split(String.valueOf((char) 10))) { + if (!line.isBlank()) { + out.add(line.strip()); + } + } + return out; + } + + /** + * A coloured disc with the mark centred inside it, as one inline run. + * + * @param name the node name, for review and rollback + * @param token the mark to draw inside the disc + * @param diameter the disc's diameter in points + * @param fill the disc colour + * @return a paragraph carrying the badge as its only run + */ + static DocumentNode badge(String name, String token, double diameter, DocumentColor fill) { + return new ParagraphBuilder() + .name(name) + .lineSpacing(0) + .textStyle(style(BODY_FONT, diameter / LATO.lineFactor(), BODY, false)) + .inlineSvgIcon(OrangeOpsIcons.badge(token, fill), diameter, + InlineImageAlignment.CENTER) + .build(); + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/Panel.java b/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/Panel.java index 3f5e44530..e6bbb129a 100644 --- a/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/Panel.java +++ b/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/Panel.java @@ -17,6 +17,8 @@ import com.demcha.compose.document.templates.core.widgets.CardWidget; import com.demcha.compose.font.FontName; +import com.demcha.compose.document.templates.core.identity.ContactUri; + import java.util.List; import java.util.Locale; import java.util.Objects; @@ -244,14 +246,31 @@ private void addHeader(PageFlowBuilder flow, CvIdentity identity, .align(TextAlign.CENTER) .margin(DocumentInsets.zero())); } - String contact = joinPipe(identity.contact().address(), - identity.contact().phone()); - if (!contact.isBlank()) { - card.addParagraph(paragraph -> paragraph - .text(contact) - .textStyle(headerMetaStyle()) - .align(TextAlign.CENTER) - .margin(DocumentInsets.zero())); + String address = identity.contact().address(); + String phone = identity.contact().phone(); + if (!address.isBlank() || !phone.isBlank()) { + // The number is its own run rather than part of one joined string, so + // it can carry a dialling target without the address being swept into + // it. The glyphs and their order are what the joined string gave. + card.addParagraph(paragraph -> { + paragraph.textStyle(headerMetaStyle()) + .align(TextAlign.CENTER) + .margin(DocumentInsets.zero()); + if (!address.isBlank()) { + paragraph.inlineText(address.trim(), headerMetaStyle()); + } + if (!phone.isBlank()) { + if (!address.isBlank()) { + paragraph.inlineText(" | ", headerMetaStyle()); + } + DocumentLinkOptions dial = ContactUri.telLink(phone); + if (dial == null) { + paragraph.inlineText(phone.trim(), headerMetaStyle()); + } else { + paragraph.inlineText(phone.trim(), headerMetaStyle(), dial); + } + } + }); } addLinkRow(card, identity); }); @@ -497,19 +516,5 @@ private DocumentTextStyle moduleTitleStyle() { DocumentTextDecoration.BOLD, ACCENT); } - - private static String joinPipe(String... parts) { - StringBuilder sb = new StringBuilder(); - for (String part : parts) { - if (part == null || part.isBlank()) { - continue; - } - if (sb.length() > 0) { - sb.append(" | "); - } - sb.append(part.trim()); - } - return sb.toString(); - } } } diff --git a/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/ProfessionalSidebar.java b/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/ProfessionalSidebar.java new file mode 100644 index 000000000..15d297a22 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/ProfessionalSidebar.java @@ -0,0 +1,226 @@ +package com.demcha.compose.document.templates.cv.presets; + +import com.demcha.compose.document.api.DocumentSession; +import com.demcha.compose.document.api.PageBackgroundFill; +import com.demcha.compose.document.style.DocumentInsets; +import com.demcha.compose.document.templates.api.DocumentTemplate; +import com.demcha.compose.document.templates.cv.components.SectionLookup; +import com.demcha.compose.document.templates.cv.data.CvDocument; +import com.demcha.compose.document.templates.cv.data.CvSection; +import com.demcha.compose.document.templates.cv.data.EntriesSection; +import com.demcha.compose.document.templates.cv.data.ParagraphSection; +import com.demcha.compose.document.templates.cv.data.SkillsSection; + +import java.util.List; +import java.util.Objects; + +import static com.demcha.compose.document.templates.cv.presets.ProfessionalSidebarStyles.MAIN_WEIGHT; +import static com.demcha.compose.document.templates.cv.presets.ProfessionalSidebarStyles.PAGE_BACKGROUND; +import static com.demcha.compose.document.templates.cv.presets.ProfessionalSidebarStyles.PAGE_HEIGHT; +import static com.demcha.compose.document.templates.cv.presets.ProfessionalSidebarStyles.PAGE_WIDTH; +import static com.demcha.compose.document.templates.cv.presets.ProfessionalSidebarStyles.SIDEBAR_BACKGROUND; +import static com.demcha.compose.document.templates.cv.presets.ProfessionalSidebarStyles.SIDEBAR_WEIGHT; + +/** + * Professional Sidebar — a one-page CV in two columns: a pale sidebar under + * a navy monogram plate carrying the contact channels, the skill meters, the + * education rail and the language ratings, and a white main column carrying + * the name, the profile, the roles held, the projects and the references + * note. + * + *

    The preset owns its page: a 491.6 x 737.28pt sheet with no margin, the + * page fill and the pale sidebar painted as page backgrounds so the column + * reaches the foot of the sheet whatever the sidebar holds.

    + * + *

    One page, and what happens past it

    + * + *

    This sheet holds one page of content. The two columns are a single row, + * and a row is atomic — it cannot be split — so a CV longer than the sheet + * does not flow onto a second page: composing it raises + * {@code AtomicNodeTooLargeException}, naming the node and the height it + * needed. Feed it a CV of about the length the design was drawn around: five + * or six roles with three or four highlights each, alongside the sidebar + * blocks.

    + * + *

    The preset draws no cap of its own, which is the deliberate half of + * that: its siblings {@link MonogramSidebar}, {@link SidebarPortrait} and + * {@link MintEditorial} cap each block and silently drop what does not fit, + * and a CV that quietly loses a job is worse than one that refuses to + * compose. {@link TimelineMinimal} is the preset in this package that splits + * its own columns across pages.

    + * + *

    How a document reaches its berth

    + * + *

    The columns are fixed, so the preset assigns each section a berth by + * title rather than reading {@link + * com.demcha.compose.document.templates.cv.data.Slot}: profile, experience, + * projects and references to the main column; skills, education and + * languages to the sidebar. A section whose title matches no berth is not + * drawn, and a berth with no section takes its leading hairline with it.

    + * + *

    A berth is filled by the first section that names it, so a second + * section naming the same berth is not drawn. Neither is anything the design + * has no place for: a project's {@code subtitle}, an education entry's + * {@code body}, and a {@code SkillGroup}'s {@code category} — this sheet + * heads its projects with a title and dates, its degrees with an institution + * and dates, and sets its skills as one ungrouped list.

    + * + *

    Both the skills and the languages are {@link SkillsSection}s — the same + * shape, drawn differently: a skill's level fills a bar, a language's fills + * the nearest whole number of five dots. A level the document omits draws + * the name alone.

    + * + *

    The sidebar's rows are one line each: a skill name, a language name and + * an education degree are anchored inside a fixed-height band, so text + * longer than the narrow column measures overflows the band instead of + * wrapping inside it. That is the design — the sheet is drawn around short + * labels — and it is the one length constraint a document has to respect.

    + * + *

    The contact channels come off the identity rather than a section: the + * phone, the email and the address take the packaged marks, and each link + * takes the network mark when it names one and the globe otherwise. The + * phone and email rows carry {@code tel:} and {@code mailto:} targets built + * from the value.

    + * + *

    A channel is one paragraph, the mark and the value sharing a line, so a + * value wider than the sidebar's text column wraps under the mark and leaves + * it alone on the first line. The column is about 107pt, which at this type + * size is a little over twenty characters — long addresses want shortening + * rather than a wider column.

    + * + *

    Fonts

    + * + *

    The sheet is set in Barlow Condensed and Lato. The templates artifact + * does not carry either face — register them on the session, or the engine + * substitutes and the measured geometry here no longer matches the type + * sitting in it.

    + * + *

    Usage:

    + *
    {@code
    + * DocumentTemplate template = ProfessionalSidebar.create();
    + * template.compose(session, cv);
    + * }
    + */ +public final class ProfessionalSidebar { + + /** Stable identifier of this preset. */ + public static final String ID = "professional-sidebar"; + + /** Human-readable name of this preset. */ + public static final String DISPLAY_NAME = "Professional Sidebar"; + + /** + * The margin this preset expects: none. It sets the page margin itself, + * because both columns run to the paper edge. + */ + public static final double RECOMMENDED_MARGIN = 0.0; + + private static final List PROFILE_KEYS = + List.of("profile", "summary", "about"); + private static final List EXPERIENCE_KEYS = + List.of("experience", "employment", "work history", "career"); + private static final List PROJECT_KEYS = + List.of("projects", "project", "portfolio"); + private static final List REFERENCES_KEYS = + List.of("references", "reference"); + private static final List SKILL_KEYS = + List.of("skills", "expertise", "competencies"); + private static final List EDUCATION_KEYS = + List.of("education", "qualifications", "certifications"); + private static final List LANGUAGE_KEYS = + List.of("languages", "language"); + + private ProfessionalSidebar() { + } + + /** + * Creates the template. + * + * @return a template composing a {@link CvDocument} onto its own page + */ + public static DocumentTemplate create() { + return new Template(); + } + + private record Template() implements DocumentTemplate { + + @Override + public String id() { + return ID; + } + + @Override + public String displayName() { + return DISPLAY_NAME; + } + + @Override + public void compose(DocumentSession document, CvDocument doc) { + Objects.requireNonNull(document, "document"); + Objects.requireNonNull(doc, "doc"); + List sections = doc.sections(); + + document.pageSize(PAGE_WIDTH, PAGE_HEIGHT) + .margin(DocumentInsets.zero()) + // The sidebar fill is a page background rather than a + // section fill: a section is only as tall as what it + // holds, and the pale column has to reach the foot of + // the sheet whatever the sidebar carries. + .pageBackgrounds(List.of( + PageBackgroundFill.fullPage(PAGE_BACKGROUND), + PageBackgroundFill.leftColumn(SIDEBAR_WEIGHT, + SIDEBAR_BACKGROUND))); + + SkillsSection skills = berth(sections, SkillsSection.class, SKILL_KEYS); + EntriesSection education = berth(sections, EntriesSection.class, EDUCATION_KEYS); + // A title can name both berths — "Skills and Languages" does — + // and one section cannot fill two, so the second berth stays + // empty rather than drawing the same list twice in two hands. + SkillsSection named = berth(sections, SkillsSection.class, LANGUAGE_KEYS); + SkillsSection languages = named == skills ? null : named; + ParagraphSection profile = berth(sections, ParagraphSection.class, PROFILE_KEYS); + EntriesSection experience = berth(sections, EntriesSection.class, EXPERIENCE_KEYS); + EntriesSection projects = berth(sections, EntriesSection.class, PROJECT_KEYS); + ParagraphSection references = + berth(sections, ParagraphSection.class, REFERENCES_KEYS); + + document.pageFlow(page -> page + .name("ProfessionalSidebarCv") + .padding(DocumentInsets.zero()) + // spacing(0): every gap in this design is authored on the + // node that owns it, so a flow gap would add to all of + // them. + .spacing(0) + .addRow("PageGrid", row -> { + row.spacing(0); + row.weights(SIDEBAR_WEIGHT, MAIN_WEIGHT); + row.addSection("Sidebar", aside -> + ProfessionalSidebarAside.compose(aside, doc.identity(), + skills, education, languages)); + row.addSection("Main", main -> + ProfessionalSidebarMain.compose(main, doc.identity(), + profile, experience, projects, references)); + })); + } + + /** + * The first section of the wanted shape whose title names this berth, + * or {@code null} when the document fills no such berth. + */ + private static T berth(List sections, + Class type, + List keys) { + for (CvSection section : sections) { + if (!type.isInstance(section)) { + continue; + } + for (String key : keys) { + if (SectionLookup.titleContains(section.title(), key)) { + return type.cast(section); + } + } + } + return null; + } + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/ProfessionalSidebarAside.java b/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/ProfessionalSidebarAside.java new file mode 100644 index 000000000..c88f8dd4d --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/ProfessionalSidebarAside.java @@ -0,0 +1,477 @@ +package com.demcha.compose.document.templates.cv.presets; + +import com.demcha.compose.document.dsl.EllipseBuilder; +import com.demcha.compose.document.dsl.ParagraphBuilder; +import com.demcha.compose.document.dsl.SectionBuilder; +import com.demcha.compose.document.dsl.ShapeBuilder; +import com.demcha.compose.document.dsl.ShapeContainerBuilder; +import com.demcha.compose.document.dsl.TimelineMarker; +import com.demcha.compose.document.dsl.TimelineRailEnd; +import com.demcha.compose.document.image.DocumentImageData; +import com.demcha.compose.document.node.DocumentLinkOptions; +import com.demcha.compose.document.node.DocumentNode; +import com.demcha.compose.document.node.InlineImageAlignment; +import com.demcha.compose.document.node.LayerAlign; +import com.demcha.compose.document.node.TextAlign; +import com.demcha.compose.document.style.ClipPolicy; +import com.demcha.compose.document.style.DocumentColor; +import com.demcha.compose.document.style.DocumentInsets; +import com.demcha.compose.document.style.DocumentStroke; +import com.demcha.compose.document.style.DocumentTextDecoration; +import com.demcha.compose.document.style.DocumentTextStyle; +import com.demcha.compose.document.templates.core.identity.ContactUri; +import com.demcha.compose.document.templates.core.identity.Link; +import com.demcha.compose.document.templates.cv.components.SectionLookup; +import com.demcha.compose.document.templates.cv.data.CvEntry; +import com.demcha.compose.document.templates.cv.data.CvIdentity; +import com.demcha.compose.document.templates.cv.data.CvSkill; +import com.demcha.compose.document.templates.cv.data.EntriesSection; +import com.demcha.compose.document.templates.cv.data.SkillGroup; +import com.demcha.compose.document.templates.cv.data.SkillsSection; + +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; + +import static com.demcha.compose.document.templates.cv.presets.ProfessionalSidebarStyles.ACCENT_PRIMARY; +import static com.demcha.compose.document.templates.cv.presets.ProfessionalSidebarStyles.BODY_FONT; +import static com.demcha.compose.document.templates.cv.presets.ProfessionalSidebarStyles.BODY_LEADING; +import static com.demcha.compose.document.templates.cv.presets.ProfessionalSidebarStyles.BODY_SIZE; +import static com.demcha.compose.document.templates.cv.presets.ProfessionalSidebarStyles.CONTACT_HEADING_TO_BODY; +import static com.demcha.compose.document.templates.cv.presets.ProfessionalSidebarStyles.CONTACT_ROW_GAP; +import static com.demcha.compose.document.templates.cv.presets.ProfessionalSidebarStyles.CONTACT_TO_SKILLS_ABOVE; +import static com.demcha.compose.document.templates.cv.presets.ProfessionalSidebarStyles.CONTACT_TO_SKILLS_BELOW; +import static com.demcha.compose.document.templates.cv.presets.ProfessionalSidebarStyles.EDUCATION_DEGREE_AIR; +import static com.demcha.compose.document.templates.cv.presets.ProfessionalSidebarStyles.EDUCATION_DEGREE_SIZE; +import static com.demcha.compose.document.templates.cv.presets.ProfessionalSidebarStyles.EDUCATION_ENTRY_GAP; +import static com.demcha.compose.document.templates.cv.presets.ProfessionalSidebarStyles.EDUCATION_HEADING_TO_BODY; +import static com.demcha.compose.document.templates.cv.presets.ProfessionalSidebarStyles.EDUCATION_LINE_GAP; +import static com.demcha.compose.document.templates.cv.presets.ProfessionalSidebarStyles.EDUCATION_MARKER_DIAMETER; +import static com.demcha.compose.document.templates.cv.presets.ProfessionalSidebarStyles.EDUCATION_RAIL_WIDTH; +import static com.demcha.compose.document.templates.cv.presets.ProfessionalSidebarStyles.EDUCATION_RAIL_X; +import static com.demcha.compose.document.templates.cv.presets.ProfessionalSidebarStyles.EDUCATION_TEXT_X; +import static com.demcha.compose.document.templates.cv.presets.ProfessionalSidebarStyles.EDUCATION_TO_LANGUAGES_ABOVE; +import static com.demcha.compose.document.templates.cv.presets.ProfessionalSidebarStyles.EDUCATION_TO_LANGUAGES_BELOW; +import static com.demcha.compose.document.templates.cv.presets.ProfessionalSidebarStyles.ENTRY_HEAD_HEIGHT; +import static com.demcha.compose.document.templates.cv.presets.ProfessionalSidebarStyles.HEADER_PLATE_HEIGHT; +import static com.demcha.compose.document.templates.cv.presets.ProfessionalSidebarStyles.LANGUAGE_DOTS; +import static com.demcha.compose.document.templates.cv.presets.ProfessionalSidebarStyles.LANGUAGE_DOT_DIAMETER; +import static com.demcha.compose.document.templates.cv.presets.ProfessionalSidebarStyles.LANGUAGE_DOT_PITCH; +import static com.demcha.compose.document.templates.cv.presets.ProfessionalSidebarStyles.LANGUAGE_RATING_WIDTH; +import static com.demcha.compose.document.templates.cv.presets.ProfessionalSidebarStyles.LANGUAGE_RATING_X; +import static com.demcha.compose.document.templates.cv.presets.ProfessionalSidebarStyles.LANGUAGE_ROW_GAP; +import static com.demcha.compose.document.templates.cv.presets.ProfessionalSidebarStyles.LANGUAGE_ROW_HEIGHT; +import static com.demcha.compose.document.templates.cv.presets.ProfessionalSidebarStyles.MONOGRAM_DIAMETER; +import static com.demcha.compose.document.templates.cv.presets.ProfessionalSidebarStyles.MONOGRAM_SIZE; +import static com.demcha.compose.document.templates.cv.presets.ProfessionalSidebarStyles.MONOGRAM_STROKE; +import static com.demcha.compose.document.templates.cv.presets.ProfessionalSidebarStyles.MONOGRAM_TRACKING_EM; +import static com.demcha.compose.document.templates.cv.presets.ProfessionalSidebarStyles.RATING_MUTED; +import static com.demcha.compose.document.templates.cv.presets.ProfessionalSidebarStyles.RULE_MUTED; +import static com.demcha.compose.document.templates.cv.presets.ProfessionalSidebarStyles.SIDEBAR_BODY_TOP; +import static com.demcha.compose.document.templates.cv.presets.ProfessionalSidebarStyles.SIDEBAR_INNER_WIDTH; +import static com.demcha.compose.document.templates.cv.presets.ProfessionalSidebarStyles.SIDEBAR_PAD_X; +import static com.demcha.compose.document.templates.cv.presets.ProfessionalSidebarStyles.SIDEBAR_WIDTH; +import static com.demcha.compose.document.templates.cv.presets.ProfessionalSidebarStyles.SKILLS_TO_EDUCATION_ABOVE; +import static com.demcha.compose.document.templates.cv.presets.ProfessionalSidebarStyles.SKILLS_TO_EDUCATION_BELOW; +import static com.demcha.compose.document.templates.cv.presets.ProfessionalSidebarStyles.SKILL_ROW_GAP; +import static com.demcha.compose.document.templates.cv.presets.ProfessionalSidebarStyles.SKILL_ROW_HEIGHT; +import static com.demcha.compose.document.templates.cv.presets.ProfessionalSidebarStyles.SKILL_TRACK_HEIGHT; +import static com.demcha.compose.document.templates.cv.presets.ProfessionalSidebarStyles.SKILL_TRACK_WIDTH; +import static com.demcha.compose.document.templates.cv.presets.ProfessionalSidebarStyles.PLATE_BACKGROUND; +import static com.demcha.compose.document.templates.cv.presets.ProfessionalSidebarStyles.STANDARD_HEADING_TO_BODY; +import static com.demcha.compose.document.templates.cv.presets.ProfessionalSidebarStyles.TEXT_MUTED; +import static com.demcha.compose.document.templates.cv.presets.ProfessionalSidebarStyles.TEXT_PRIMARY; +import static com.demcha.compose.document.templates.cv.presets.ProfessionalSidebarStyles.body; +import static com.demcha.compose.document.templates.cv.presets.ProfessionalSidebarStyles.clamp01; +import static com.demcha.compose.document.templates.cv.presets.ProfessionalSidebarStyles.compact; +import static com.demcha.compose.document.templates.cv.presets.ProfessionalSidebarStyles.style; +import static com.demcha.compose.document.templates.cv.presets.ProfessionalSidebarStyles.tracked; +import static com.demcha.compose.document.templates.cv.presets.ProfessionalSidebarWidgets.paragraph; +import static com.demcha.compose.document.templates.cv.presets.ProfessionalSidebarWidgets.sidebarDivider; +import static com.demcha.compose.document.templates.cv.presets.ProfessionalSidebarWidgets.sidebarHeading; + +/** + * The pale left column: the navy monogram plate, the contact channels, the + * skill meters, the education rail and the language ratings. + * + *

    The column has four berths in a fixed order, each with its own measured + * gap to the one above. A berth with nothing to draw takes its leading + * hairline with it, so a CV that carries no languages does not end on a rule + * with empty space under it.

    + */ +final class ProfessionalSidebarAside { + + /** + * The contact heading. It is not read from the document because the + * channels are not a section — they come off the identity, which carries + * no title of its own. + */ + static final String CONTACT_HEADING = "CONTACT"; + + private ProfessionalSidebarAside() { + } + + static void compose(SectionBuilder section, + CvIdentity identity, + SkillsSection skills, + EntriesSection education, + SkillsSection languages) { + section.spacing(0); + section.add(monogramPlate(identity)); + section.addSection("SidebarBody", body -> { + body.spacing(0); + body.padding(new DocumentInsets(SIDEBAR_BODY_TOP, SIDEBAR_PAD_X, 0, SIDEBAR_PAD_X)); + body.addSection("Contact", contact -> renderContact(contact, identity)); + if (SectionLookup.hasContent(skills)) { + sidebarDivider(body, CONTACT_TO_SKILLS_ABOVE, CONTACT_TO_SKILLS_BELOW); + body.addSection("Skills", host -> renderSkills(host, skills)); + } + if (SectionLookup.hasContent(education)) { + sidebarDivider(body, SKILLS_TO_EDUCATION_ABOVE, SKILLS_TO_EDUCATION_BELOW); + body.addSection("Education", host -> renderEducation(host, education)); + } + if (SectionLookup.hasContent(languages)) { + sidebarDivider(body, EDUCATION_TO_LANGUAGES_ABOVE, EDUCATION_TO_LANGUAGES_BELOW); + body.addSection("Languages", host -> renderLanguages(host, languages)); + } + }); + } + + // -- monogram -------------------------------------------------------- + + /** + * The navy plate that caps the column, with the initials set inside a + * hairline ring. + * + *

    The plate is drawn rather than painted as a page background because + * it is the first thing in the flow: the sidebar content stacks under it, + * and a background would not push anything down.

    + */ + private static DocumentNode monogramPlate(CvIdentity identity) { + DocumentTextStyle initialsStyle = style(BODY_FONT, MONOGRAM_SIZE, + DocumentColor.WHITE, DocumentTextDecoration.DEFAULT); + ParagraphBuilder initials = new ParagraphBuilder() + .name("MonogramInitials") + .textStyle(initialsStyle) + .align(TextAlign.CENTER); + tracked(initials, initials(identity), initialsStyle, MONOGRAM_TRACKING_EM); + + DocumentNode ring = new ShapeContainerBuilder() + .name("MonogramRing") + .circle(MONOGRAM_DIAMETER) + .clipPolicy(ClipPolicy.OVERFLOW_VISIBLE) + .stroke(DocumentStroke.of(DocumentColor.WHITE, MONOGRAM_STROKE)) + .center(initials.margin(DocumentInsets.zero()).build()) + .build(); + + return new ShapeContainerBuilder() + .name("MonogramPlate") + .rectangle(SIDEBAR_WIDTH, HEADER_PLATE_HEIGHT) + .fillColor(PLATE_BACKGROUND) + .center(ring) + .build(); + } + + /** First letter of the given name, first letter of the family name. */ + private static String initials(CvIdentity identity) { + String first = initial(identity.name().first()); + String last = initial(identity.name().last()); + return (first + last).toUpperCase(Locale.ROOT); + } + + private static String initial(String value) { + return value == null || value.isBlank() ? "" : value.trim().substring(0, 1); + } + + // -- contact --------------------------------------------------------- + + /** + * The contact channels, in the order the design sets them: phone, email, + * address, then whatever links the identity carries. + * + *

    The phone and email rows link to {@code tel:} and {@code mailto:} + * targets built from the value, so the channels are dialable and + * writable from the PDF without the document carrying the URI twice.

    + */ + private static void renderContact(SectionBuilder section, CvIdentity identity) { + sidebarHeading(section, CONTACT_HEADING, CONTACT_HEADING_TO_BODY); + List channels = channels(identity); + for (int i = 0; i < channels.size(); i++) { + Channel channel = channels.get(i); + double gapBelow = i + 1 < channels.size() ? CONTACT_ROW_GAP : 0; + renderChannel(section, channel, gapBelow); + } + } + + private static void renderChannel(SectionBuilder section, Channel channel, + double gapBelow) { + DocumentImageData icon = ProfessionalSidebarIcons.image(channel.token()); + double size = ProfessionalSidebarIcons.size(channel.token()); + DocumentLinkOptions link = channel.href() == null + ? null + : new DocumentLinkOptions(channel.href()); + ParagraphBuilder row = new ParagraphBuilder() + .name("Contact_" + compact(channel.token())) + .textStyle(body()) + .inlineImage(icon, size, size, InlineImageAlignment.CENTER, 0, link); + // The gap between the mark and the value is set as spaces rather than + // an indent: the row is one paragraph, so the mark and the text share + // a baseline and wrap together. + if (link == null) { + row.inlineText(" " + channel.value(), body()); + } else { + row.inlineText(" " + channel.value(), body(), link); + } + section.add(row.margin(new DocumentInsets(0, 0, gapBelow, 0)).build()); + } + + private static List channels(CvIdentity identity) { + List channels = new ArrayList<>(); + String phone = identity.contact().phone(); + channels.add(new Channel(ProfessionalSidebarIcons.PHONE, phone, ContactUri.tel(phone))); + String email = identity.contact().email(); + channels.add(new Channel(ProfessionalSidebarIcons.EMAIL, email, "mailto:" + email)); + channels.add(new Channel(ProfessionalSidebarIcons.LOCATION, + identity.contact().address(), null)); + for (Link link : identity.links()) { + channels.add(new Channel(linkToken(link), link.label(), link.url())); + } + return channels; + } + + /** + * The mark for a link. The packaged set names one network; everything + * else is a site, which is what the globe stands for. + */ + private static String linkToken(Link link) { + String haystack = SectionLookup.normalize(link.label() + " " + link.url()); + return haystack.contains("linkedin") + ? ProfessionalSidebarIcons.LINKEDIN + : ProfessionalSidebarIcons.WEBSITE; + } + + private record Channel(String token, String value, String href) { + } + + // -- skills ---------------------------------------------------------- + + /** + * Skill rows: the name at the left of a fixed-height band, the meter at + * the right. A skill the document leaves unlevelled draws its name alone + * rather than an empty track. + */ + private static void renderSkills(SectionBuilder section, SkillsSection skills) { + sidebarHeading(section, skills.title(), STANDARD_HEADING_TO_BODY); + section.addSection("SkillRows", rows -> { + rows.spacing(SKILL_ROW_GAP); + for (CvSkill skill : flatten(skills)) { + DocumentNode label = paragraph("Skill_" + compact(skill.name()), + skill.name(), body(), TextAlign.LEFT); + rows.addContainer(row -> { + row.name("SkillRow_" + compact(skill.name())) + .rectangle(SIDEBAR_INNER_WIDTH, SKILL_ROW_HEIGHT) + .clipPolicy(ClipPolicy.OVERFLOW_VISIBLE) + .centerLeft(label); + if (skill.level().isPresent()) { + row.centerRight(skillMeter(skill.level().getAsDouble())); + } + }); + } + }); + } + + private static DocumentNode skillMeter(double level) { + DocumentNode fill = new ShapeBuilder() + .name("SkillMeterFill") + .size(SKILL_TRACK_WIDTH * clamp01(level), SKILL_TRACK_HEIGHT) + .fillColor(PLATE_BACKGROUND) + .build(); + return new ShapeContainerBuilder() + .name("SkillMeter") + .rectangle(SKILL_TRACK_WIDTH, SKILL_TRACK_HEIGHT) + .fillColor(RULE_MUTED) + .centerLeft(fill) + .build(); + } + + // -- education ------------------------------------------------------- + + /** + * The education rail: a hairline down the left of the block with a dot + * on each entry's first line. + * + *

    The rail runs from the first dot to the foot of the last entry, which is what the + * block is drawn as and what it now asks for. It used to be the section's left accent, + * and the first entry filled its own head band with the sidebar colour, masked the rail's + * protruding edge above its dot and redrew the rail below it, meaning to start the line + * at the first marker. Measured against the render, that never happened: an accent draws + * above the fill and above the mask, so the rail stayed visible for the 2.5pt above the + * first dot and the only thing the construction achieved was drawing that stretch below + * the dot twice. The mask and the redraw are gone; the rail is the timeline's and says + * what it does.

    + */ + private static void renderEducation(SectionBuilder section, EntriesSection education) { + sidebarHeading(section, education.title(), EDUCATION_HEADING_TO_BODY); + SectionBuilder holder = new SectionBuilder(); + holder.name("EducationRailHolder"); + holder.spacing(0); + // The axis column centres the rail on itself, so the timeline starts half a dot left + // of the column the accent drew on and the line lands back on it. + holder.margin(new DocumentInsets(0, 0, 0, + EDUCATION_RAIL_X - EDUCATION_MARKER_DIAMETER / 2.0)); + holder.addTimeline(timeline -> { + timeline.markerOnRail() + // Begin at the first dot, run on to the foot of the entries. That is + // what this block is drawn as, and what the mask-and-redraw it replaced + // was trying to fake — it could not be asked for while the rail's two + // ends were one value. + .rail(rail -> rail + .stroke(DocumentStroke.of(RULE_MUTED, EDUCATION_RAIL_WIDTH)) + .from(TimelineRailEnd.MARKER).to(TimelineRailEnd.ENTRY_BOUND)) + .axisWidth(EDUCATION_MARKER_DIAMETER) + .markerGap(EDUCATION_TEXT_X - EDUCATION_MARKER_DIAMETER / 2.0) + .gutter(0) + .spacing(EDUCATION_ENTRY_GAP); + List entries = education.entries(); + for (int i = 0; i < entries.size(); i++) { + int index = i; + CvEntry entry = entries.get(i); + timeline.entry(educationMarker(index), e -> e.content(body -> { + body.spacing(0); + renderEducationHead(body, entry, index); + body.addParagraph(p -> p + .name("EducationInstitution_" + index) + .text(entry.subtitle()) + .textStyle(style(BODY_FONT, BODY_SIZE, TEXT_MUTED, + DocumentTextDecoration.ITALIC)) + .margin(new DocumentInsets( + EDUCATION_LINE_GAP, 0, EDUCATION_LINE_GAP, 0))); + body.addParagraph(p -> p + .name("EducationDates_" + index) + .text(entry.date()) + .textStyle(body()) + .margin(DocumentInsets.zero())); + })); + } + }); + DocumentNode educationRail = holder.build(); + // The timeline goes in through a layer stack rather than straight into the + // section. A timeline publishes a row, a row cannot nest inside a row cell, + // and this sidebar is a row cell — the stack is what insulates it. Unwinding + // the stacks that turn out not to need it buys no capability, so they stay + // until that is done as its own deliberate cleanup. + section.addLayerStack(stack -> stack + .name("EducationRail") + .layer(educationRail, LayerAlign.TOP_LEFT, 0)); + } + + /** + * The accent dot that marks a degree, in a box as tall as the head band it rides. + * + *

    The dot used to be centred in that band by the container positioning it; a timeline + * top-aligns its marker, so the band height is declared here and the dot carries the half + * band above it. Same box, same centre, and the rail still runs through it.

    + */ + private static TimelineMarker educationMarker(int index) { + double halfBand = (ENTRY_HEAD_HEIGHT - EDUCATION_MARKER_DIAMETER) / 2.0; + return TimelineMarker.custom(EDUCATION_MARKER_DIAMETER, ENTRY_HEAD_HEIGHT, + column -> column.addEllipse(ellipse -> ellipse + .name("EducationMarker_" + index) + .circle(EDUCATION_MARKER_DIAMETER) + .fillColor(ACCENT_PRIMARY) + .margin(new DocumentInsets(halfBand, 0, 0, 0)))); + } + + /** + * The degree title — the entry's own first line, not a band of declared height. + * + *

    It was a container of exactly {@code ENTRY_HEAD_HEIGHT} with the title centred in + * it, which put the title's centre on the dot's, the dot being centred in a box of the + * same height. That works for a title of one line and for no other. "MSc Advanced + * Computer Science and Software Engineering" needs three lines in a sidebar this narrow, + * and a box told to be 9.3pt tall stayed 9.3pt tall; under + * {@link ClipPolicy#OVERFLOW_VISIBLE} the extra lines drew rather than vanished — 16.840pt + * of them, over the institution and the dates below.

    + * + *

    Nothing about a one-line title moves. The band's surplus over one line is now + * {@link ProfessionalSidebarStyles#EDUCATION_DEGREE_AIR} above and below the title, so it + * occupies the same 9.3pt and its centre stays on the dot's; and the width is the same + * number too, since the container declared + * {@code SIDEBAR_INNER_WIDTH - EDUCATION_RAIL_X - EDUCATION_TEXT_X} and the timeline's + * content column resolves to exactly that out of {@code axisWidth} and {@code markerGap}. + * What changes is only that a title needing a second line now gets one.

    + */ + private static void renderEducationHead(SectionBuilder rail, CvEntry entry, int index) { + rail.addParagraph(p -> p + .name("EducationDegree_" + index) + .text(entry.title()) + .textStyle(style(BODY_FONT, EDUCATION_DEGREE_SIZE, TEXT_PRIMARY, + DocumentTextDecoration.BOLD)) + .align(TextAlign.LEFT) + .lineSpacing(BODY_LEADING) + .margin(new DocumentInsets( + EDUCATION_DEGREE_AIR, 0, EDUCATION_DEGREE_AIR, 0))); + } + + // -- languages ------------------------------------------------------- + + /** + * Language rows: the name at the left, a five-dot rating anchored at a + * fixed offset so the ratings line up whatever the names measure. A + * language the document leaves unlevelled draws its name alone. + */ + private static void renderLanguages(SectionBuilder section, SkillsSection languages) { + sidebarHeading(section, languages.title(), STANDARD_HEADING_TO_BODY); + section.addSection("LanguageRows", rows -> { + rows.spacing(LANGUAGE_ROW_GAP); + for (CvSkill language : flatten(languages)) { + DocumentNode label = paragraph("Language_" + compact(language.name()), + language.name(), body(), TextAlign.LEFT); + rows.addContainer(row -> { + row.name("LanguageRow_" + compact(language.name())) + .rectangle(SIDEBAR_INNER_WIDTH, LANGUAGE_ROW_HEIGHT) + .clipPolicy(ClipPolicy.OVERFLOW_VISIBLE) + .centerLeft(label); + if (language.level().isPresent()) { + row.position(ratingDots(language.level().getAsDouble()), + LANGUAGE_RATING_X, 0, LayerAlign.CENTER_LEFT); + } + }); + } + }); + } + + /** + * The rating: five dots, of which the level fills the nearest whole + * number — the model carries a fraction, and this design counts. + */ + private static DocumentNode ratingDots(double level) { + long filled = Math.round(clamp01(level) * LANGUAGE_DOTS); + ShapeContainerBuilder rating = new ShapeContainerBuilder() + .name("LanguageRating") + .rectangle(LANGUAGE_RATING_WIDTH, LANGUAGE_DOT_DIAMETER) + .clipPolicy(ClipPolicy.OVERFLOW_VISIBLE); + for (int i = 0; i < LANGUAGE_DOTS; i++) { + DocumentNode dot = new EllipseBuilder() + .name("LanguageDot_" + i) + .circle(LANGUAGE_DOT_DIAMETER) + .fillColor(i < filled ? PLATE_BACKGROUND : RATING_MUTED) + .build(); + rating.position(dot, i * LANGUAGE_DOT_PITCH, 0, LayerAlign.CENTER_LEFT); + } + return rating.build(); + } + + /** + * The section's skills as one list. This design sets no group headings, + * so a document that groups its skills gets them in the order the groups + * were declared. + */ + private static List flatten(SkillsSection section) { + List out = new ArrayList<>(); + for (SkillGroup group : section.groups()) { + out.addAll(group.entries()); + } + return out; + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/ProfessionalSidebarIcons.java b/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/ProfessionalSidebarIcons.java new file mode 100644 index 000000000..0e35fa334 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/ProfessionalSidebarIcons.java @@ -0,0 +1,83 @@ +package com.demcha.compose.document.templates.cv.presets; + +import com.demcha.compose.document.image.DocumentImageData; + +import java.io.IOException; +import java.io.InputStream; +import java.io.UncheckedIOException; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Classpath loader for the Professional Sidebar CV contact marks. + * + *

    They ship inside the templates artifact under + * {@code templates/cv/professional-sidebar/icons/} as PNG, at the point size + * the design draws each one at — the marks are not one size, because a round + * pin and a wide envelope need different heights to read as the same + * weight.

    + */ +final class ProfessionalSidebarIcons { + + static final String PHONE = "phone"; + static final String EMAIL = "email"; + static final String LOCATION = "location"; + static final String LINKEDIN = "linkedin"; + static final String WEBSITE = "website"; + + private static final String ICON_ROOT = "/templates/cv/professional-sidebar/icons/"; + + private static final Map SIZES = Map.of( + PHONE, 8.8, + EMAIL, 9.2, + LOCATION, 9.6, + LINKEDIN, 9.4, + WEBSITE, 9.6); + + private static final Map CACHE = new ConcurrentHashMap<>(); + + private ProfessionalSidebarIcons() { + } + + /** + * The drawn size of one mark. + * + * @param token one of the constants on this class + * @return the size in points + * @throws IllegalArgumentException when the token names no packaged mark + */ + static double size(String token) { + Double size = SIZES.get(token); + if (size == null) { + throw new IllegalArgumentException( + "Unknown professional sidebar CV icon token: " + token); + } + return size; + } + + /** + * Reads one mark, caching the decoded image per token so a CV that + * repeats a channel reads the file once per JVM. + * + * @param token one of the constants on this class + * @return the image data + */ + static DocumentImageData image(String token) { + return CACHE.computeIfAbsent(token, ProfessionalSidebarIcons::read); + } + + private static DocumentImageData read(String token) { + String resourcePath = ICON_ROOT + token + ".png"; + try (InputStream input = + ProfessionalSidebarIcons.class.getResourceAsStream(resourcePath)) { + if (input == null) { + throw new IllegalStateException( + "Missing professional sidebar CV icon: " + resourcePath); + } + return DocumentImageData.fromBytes(input.readAllBytes()); + } catch (IOException e) { + throw new UncheckedIOException( + "Failed to read professional sidebar CV icon: " + resourcePath, e); + } + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/ProfessionalSidebarMain.java b/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/ProfessionalSidebarMain.java new file mode 100644 index 000000000..7b9e17e66 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/ProfessionalSidebarMain.java @@ -0,0 +1,244 @@ +package com.demcha.compose.document.templates.cv.presets; + +import com.demcha.compose.document.dsl.ParagraphBuilder; +import com.demcha.compose.document.dsl.SectionBuilder; +import com.demcha.compose.document.dsl.ShapeBuilder; +import com.demcha.compose.document.dsl.ShapeContainerBuilder; +import com.demcha.compose.document.node.DocumentNode; +import com.demcha.compose.document.node.TextAlign; +import com.demcha.compose.document.style.DocumentInsets; +import com.demcha.compose.document.style.DocumentTextDecoration; +import com.demcha.compose.document.style.DocumentTextStyle; +import com.demcha.compose.document.templates.core.text.MarkdownInline; +import com.demcha.compose.document.templates.cv.components.SectionLookup; +import com.demcha.compose.document.templates.cv.data.CvEntry; +import com.demcha.compose.document.templates.cv.data.CvIdentity; +import com.demcha.compose.document.templates.cv.data.EntriesSection; +import com.demcha.compose.document.templates.cv.data.ParagraphSection; + +import java.util.ArrayList; +import java.util.List; + +import static com.demcha.compose.document.templates.cv.presets.ProfessionalSidebarStyles.ACCENT_PRIMARY; +import static com.demcha.compose.document.templates.cv.presets.ProfessionalSidebarStyles.BODY_FONT; +import static com.demcha.compose.document.templates.cv.presets.ProfessionalSidebarStyles.BODY_LEADING; +import static com.demcha.compose.document.templates.cv.presets.ProfessionalSidebarStyles.DISPLAY_FONT; +import static com.demcha.compose.document.templates.cv.presets.ProfessionalSidebarStyles.DIVIDER_TO_ENTRY; +import static com.demcha.compose.document.templates.cv.presets.ProfessionalSidebarStyles.DIVIDER_TO_PROJECT; +import static com.demcha.compose.document.templates.cv.presets.ProfessionalSidebarStyles.ENTRY_HEAD_TO_META; +import static com.demcha.compose.document.templates.cv.presets.ProfessionalSidebarStyles.ENTRY_META_TO_HIGHLIGHTS; +import static com.demcha.compose.document.templates.cv.presets.ProfessionalSidebarStyles.ENTRY_TO_DIVIDER; +import static com.demcha.compose.document.templates.cv.presets.ProfessionalSidebarStyles.EXPERIENCE_HEADING_TO_BODY; +import static com.demcha.compose.document.templates.cv.presets.ProfessionalSidebarStyles.EXPERIENCE_TO_PROJECTS; +import static com.demcha.compose.document.templates.cv.presets.ProfessionalSidebarStyles.HIGHLIGHT_ITEM_GAP; +import static com.demcha.compose.document.templates.cv.presets.ProfessionalSidebarStyles.IDENTITY_ACCENT_WIDTH; +import static com.demcha.compose.document.templates.cv.presets.ProfessionalSidebarStyles.IDENTITY_TO_PROFILE; +import static com.demcha.compose.document.templates.cv.presets.ProfessionalSidebarStyles.MAIN_CONTENT_WIDTH; +import static com.demcha.compose.document.templates.cv.presets.ProfessionalSidebarStyles.MAIN_HEADING_TO_BODY; +import static com.demcha.compose.document.templates.cv.presets.ProfessionalSidebarStyles.MAIN_PAD_LEFT; +import static com.demcha.compose.document.templates.cv.presets.ProfessionalSidebarStyles.MAIN_PAD_RIGHT; +import static com.demcha.compose.document.templates.cv.presets.ProfessionalSidebarStyles.MAIN_PAD_TOP; +import static com.demcha.compose.document.templates.cv.presets.ProfessionalSidebarStyles.NAME_SIZE; +import static com.demcha.compose.document.templates.cv.presets.ProfessionalSidebarStyles.NAME_TO_ROLE; +import static com.demcha.compose.document.templates.cv.presets.ProfessionalSidebarStyles.NAME_TRACKING_EM; +import static com.demcha.compose.document.templates.cv.presets.ProfessionalSidebarStyles.PROFESSIONAL_TITLE_SIZE; +import static com.demcha.compose.document.templates.cv.presets.ProfessionalSidebarStyles.PROFILE_TO_EXPERIENCE; +import static com.demcha.compose.document.templates.cv.presets.ProfessionalSidebarStyles.PROJECTS_TO_REFERENCES; +import static com.demcha.compose.document.templates.cv.presets.ProfessionalSidebarStyles.PROJECT_HEADING_TO_BODY; +import static com.demcha.compose.document.templates.cv.presets.ProfessionalSidebarStyles.PROJECT_HEAD_TO_BODY; +import static com.demcha.compose.document.templates.cv.presets.ProfessionalSidebarStyles.PROJECT_TO_DIVIDER; +import static com.demcha.compose.document.templates.cv.presets.ProfessionalSidebarStyles.REFERENCES_HEADING_TO_BODY; +import static com.demcha.compose.document.templates.cv.presets.ProfessionalSidebarStyles.REFERENCES_SIZE; +import static com.demcha.compose.document.templates.cv.presets.ProfessionalSidebarStyles.ROLE_TO_RULE; +import static com.demcha.compose.document.templates.cv.presets.ProfessionalSidebarStyles.ROLE_TRACKING_EM; +import static com.demcha.compose.document.templates.cv.presets.ProfessionalSidebarStyles.RULE_MUTED; +import static com.demcha.compose.document.templates.cv.presets.ProfessionalSidebarStyles.SECTION_ACCENT_HEIGHT; +import static com.demcha.compose.document.templates.cv.presets.ProfessionalSidebarStyles.TEXT_MUTED; +import static com.demcha.compose.document.templates.cv.presets.ProfessionalSidebarStyles.TEXT_PRIMARY; +import static com.demcha.compose.document.templates.cv.presets.ProfessionalSidebarStyles.body; +import static com.demcha.compose.document.templates.cv.presets.ProfessionalSidebarStyles.metaItalic; +import static com.demcha.compose.document.templates.cv.presets.ProfessionalSidebarStyles.style; +import static com.demcha.compose.document.templates.cv.presets.ProfessionalSidebarStyles.tracked; +import static com.demcha.compose.document.templates.cv.presets.ProfessionalSidebarWidgets.mainDivider; +import static com.demcha.compose.document.templates.cv.presets.ProfessionalSidebarWidgets.mainHeading; +import static com.demcha.compose.document.templates.cv.presets.ProfessionalSidebarWidgets.spacer; +import static com.demcha.compose.document.templates.cv.presets.ProfessionalSidebarWidgets.titleDateBand; + +/** + * The white right column: the name and role over the identity rule, then the + * profile, the roles held, the projects and the references note. + */ +final class ProfessionalSidebarMain { + + private ProfessionalSidebarMain() { + } + + static void compose(SectionBuilder section, + CvIdentity identity, + ParagraphSection profile, + EntriesSection experience, + EntriesSection projects, + ParagraphSection references) { + section.spacing(0); + section.padding(new DocumentInsets(MAIN_PAD_TOP, MAIN_PAD_RIGHT, 0, MAIN_PAD_LEFT)); + section.addSection("Identity", host -> renderIdentity(host, identity)); + if (SectionLookup.hasContent(profile)) { + section.addSection("Profile", host -> renderProfile(host, profile)); + } + if (SectionLookup.hasContent(experience)) { + section.addSection("Experience", host -> renderExperience(host, experience)); + } + if (SectionLookup.hasContent(projects)) { + section.addSection("Projects", host -> renderProjects(host, projects)); + } + if (SectionLookup.hasContent(references)) { + section.addSection("References", host -> renderReferences(host, references)); + } + } + + // -- identity -------------------------------------------------------- + + /** + * The name in tracked condensed capitals, the role under it, and the + * rule that closes the block: a full-width hairline whose first stretch + * is the accent. + */ + private static void renderIdentity(SectionBuilder section, CvIdentity identity) { + DocumentTextStyle nameStyle = style(DISPLAY_FONT, NAME_SIZE, TEXT_PRIMARY, + DocumentTextDecoration.BOLD); + ParagraphBuilder name = new ParagraphBuilder() + .name("DisplayName") + .textStyle(nameStyle) + .align(TextAlign.LEFT); + tracked(name, identity.name().full(), nameStyle, NAME_TRACKING_EM); + section.add(name.margin(new DocumentInsets(0, 0, NAME_TO_ROLE, 0)).build()); + + DocumentTextStyle roleStyle = style(BODY_FONT, PROFESSIONAL_TITLE_SIZE, TEXT_MUTED, + DocumentTextDecoration.DEFAULT); + ParagraphBuilder role = new ParagraphBuilder() + .name("ProfessionalTitle") + .textStyle(roleStyle) + .align(TextAlign.LEFT); + tracked(role, identity.jobTitle(), roleStyle, ROLE_TRACKING_EM); + section.add(role.margin(new DocumentInsets(0, 0, ROLE_TO_RULE, 0)).build()); + + section.add(identityRule()); + spacer(section, IDENTITY_TO_PROFILE); + } + + private static DocumentNode identityRule() { + DocumentNode accent = new ShapeBuilder() + .name("IdentityAccentSegment") + .size(IDENTITY_ACCENT_WIDTH, SECTION_ACCENT_HEIGHT) + .fillColor(ACCENT_PRIMARY) + .build(); + return new ShapeContainerBuilder() + .name("IdentityRule") + .rectangle(MAIN_CONTENT_WIDTH, SECTION_ACCENT_HEIGHT) + .fillColor(RULE_MUTED) + .centerLeft(accent) + .build(); + } + + // -- profile --------------------------------------------------------- + + private static void renderProfile(SectionBuilder section, ParagraphSection profile) { + mainHeading(section, profile.title(), MAIN_HEADING_TO_BODY); + section.addParagraph(p -> p + .name("ProfileText") + .text(profile.body()) + .textStyle(body()) + .lineSpacing(BODY_LEADING) + .margin(DocumentInsets.zero())); + spacer(section, PROFILE_TO_EXPERIENCE); + } + + // -- experience ------------------------------------------------------ + + /** + * The roles held, each a title-and-dates band over the employer line and + * a bulleted list of what the role delivered, separated by hairlines. + */ + private static void renderExperience(SectionBuilder section, EntriesSection experience) { + mainHeading(section, experience.title(), EXPERIENCE_HEADING_TO_BODY); + List entries = experience.entries(); + for (int i = 0; i < entries.size(); i++) { + renderRole(section, entries.get(i), i); + if (i + 1 < entries.size()) { + mainDivider(section, ENTRY_TO_DIVIDER, DIVIDER_TO_ENTRY); + } + } + spacer(section, EXPERIENCE_TO_PROJECTS); + } + + private static void renderRole(SectionBuilder section, CvEntry entry, int index) { + titleDateBand(section, "ExperienceHead_" + index, entry.title(), entry.date()); + section.addParagraph(p -> p + .name("ExperienceMeta_" + index) + .text(entry.subtitle()) + .textStyle(metaItalic()) + .margin(new DocumentInsets( + ENTRY_HEAD_TO_META, 0, ENTRY_META_TO_HIGHLIGHTS, 0))); + List highlights = lines(entry.body()); + if (!highlights.isEmpty()) { + section.addList(list -> list + .name("ExperienceHighlights_" + index) + .items(highlights) + .bullet() + .textStyle(body()) + .lineSpacing(BODY_LEADING) + .itemSpacing(HIGHLIGHT_ITEM_GAP) + .margin(DocumentInsets.zero())); + } + } + + // -- projects -------------------------------------------------------- + + private static void renderProjects(SectionBuilder section, EntriesSection projects) { + mainHeading(section, projects.title(), PROJECT_HEADING_TO_BODY); + List entries = projects.entries(); + for (int i = 0; i < entries.size(); i++) { + int index = i; + CvEntry project = entries.get(i); + titleDateBand(section, "ProjectHead_" + index, project.title(), project.date()); + spacer(section, PROJECT_HEAD_TO_BODY); + section.addParagraph(p -> p + .name("ProjectDescription_" + index) + .text(project.body()) + .textStyle(body()) + .lineSpacing(BODY_LEADING) + .margin(DocumentInsets.zero())); + if (i + 1 < entries.size()) { + mainDivider(section, PROJECT_TO_DIVIDER, DIVIDER_TO_PROJECT); + } + } + spacer(section, PROJECTS_TO_REFERENCES); + } + + // -- references ------------------------------------------------------ + + private static void renderReferences(SectionBuilder section, ParagraphSection references) { + mainHeading(section, references.title(), REFERENCES_HEADING_TO_BODY); + section.addParagraph(p -> p + .name("ReferencesNote") + .text(references.body()) + .textStyle(style(BODY_FONT, REFERENCES_SIZE, TEXT_MUTED, + DocumentTextDecoration.DEFAULT)) + .margin(DocumentInsets.zero())); + } + + /** + * One bullet per non-blank line of an entry body — the family's way of + * carrying a list in a field the model types as a single string. + */ + private static List lines(String body) { + List out = new ArrayList<>(); + for (String line : body.split("\\R")) { + String clean = MarkdownInline.plainText(line).trim(); + if (!clean.isBlank()) { + out.add(clean); + } + } + return out; + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/ProfessionalSidebarStyles.java b/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/ProfessionalSidebarStyles.java new file mode 100644 index 000000000..37a76b04d --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/ProfessionalSidebarStyles.java @@ -0,0 +1,229 @@ +package com.demcha.compose.document.templates.cv.presets; + +import com.demcha.compose.document.api.DocumentPageSize; +import com.demcha.compose.document.dsl.ParagraphBuilder; +import com.demcha.compose.document.style.DocumentColor; +import com.demcha.compose.document.style.DocumentTextDecoration; +import com.demcha.compose.document.style.DocumentTextStyle; +import com.demcha.compose.document.templates.core.text.TextStyles; +import com.demcha.compose.font.FontName; + +/** + * The measured geometry, palette and type scale of the Professional Sidebar + * CV. + * + *

    Every value here is read off the design rather than derived, which is + * why so many carry two decimals: the preset reproduces a specific sheet, + * and rounding them to tidier numbers moves type off its baseline.

    + */ +final class ProfessionalSidebarStyles { + + private ProfessionalSidebarStyles() { + } + + // -- page ------------------------------------------------------------ + + /** + * The page: 1024 x 1536 pixels at 150 dpi, which is the frame the design + * was drawn in rather than a paper size. + */ + static final DocumentPageSize PAGE = DocumentPageSize.of(491.60, 737.28); + + static final double PAGE_WIDTH = PAGE.width(); + static final double PAGE_HEIGHT = PAGE.height(); + + static final double SIDEBAR_WEIGHT = 0.291; + static final double MAIN_WEIGHT = 1.0 - SIDEBAR_WEIGHT; + + static final double SIDEBAR_WIDTH = PAGE_WIDTH * SIDEBAR_WEIGHT; + static final double MAIN_WIDTH = PAGE_WIDTH * MAIN_WEIGHT; + + /** The navy plate behind the monogram, as a fraction of page height. */ + static final double HEADER_PLATE_RATIO = 0.134; + static final double HEADER_PLATE_HEIGHT = PAGE_HEIGHT * HEADER_PLATE_RATIO; + + static final double SIDEBAR_PAD_X = 17.8; + static final double SIDEBAR_INNER_WIDTH = SIDEBAR_WIDTH - 2.0 * SIDEBAR_PAD_X; + + static final double MAIN_PAD_LEFT = 27.0; + static final double MAIN_PAD_RIGHT = 20.0; + static final double MAIN_CONTENT_WIDTH = MAIN_WIDTH - MAIN_PAD_LEFT - MAIN_PAD_RIGHT; + + // -- ornaments ------------------------------------------------------- + + static final double MONOGRAM_DIAMETER = 55.2; + static final double MONOGRAM_STROKE = 0.8; + static final double MONOGRAM_SIZE = 16.0; + static final double MONOGRAM_TRACKING_EM = 0.24; + + static final double SECTION_ACCENT_WIDTH = 19.0; + static final double SECTION_ACCENT_HEIGHT = 1.05; + static final double IDENTITY_ACCENT_WIDTH = 27.0; + + static final double RULE_THICKNESS = 0.55; + + static final double SKILL_TRACK_WIDTH = SIDEBAR_INNER_WIDTH * 0.44; + static final double SKILL_TRACK_HEIGHT = 2.8; + static final double SKILL_ROW_HEIGHT = 8.5; + + static final double EDUCATION_RAIL_X = 2.8; + static final double EDUCATION_TEXT_X = 13.0; + static final double EDUCATION_MARKER_DIAMETER = 4.3; + static final double EDUCATION_RAIL_WIDTH = 0.55; + static final double EDUCATION_DEGREE_SIZE = 7.3; + + static final int LANGUAGE_DOTS = 5; + static final double LANGUAGE_RATING_X = SIDEBAR_INNER_WIDTH * 0.49; + static final double LANGUAGE_DOT_DIAMETER = 5.0; + static final double LANGUAGE_DOT_GAP = 5.1; + static final double LANGUAGE_DOT_PITCH = LANGUAGE_DOT_DIAMETER + LANGUAGE_DOT_GAP; + static final double LANGUAGE_RATING_WIDTH = + LANGUAGE_DOTS * LANGUAGE_DOT_DIAMETER + (LANGUAGE_DOTS - 1) * LANGUAGE_DOT_GAP; + static final double LANGUAGE_ROW_HEIGHT = 8.5; + + static final double ENTRY_HEAD_HEIGHT = 9.3; + + /** + * The line box of the body face as a share of its type size. + * + *

    Declared because one thing on this sheet has to reason about the height of a line + * without measuring one: see {@link #EDUCATION_DEGREE_AIR}.

    + */ + private static final double BODY_LINE_FACTOR = 1.200; + + /** + * The air above and below a degree title. + * + *

    Half of what {@link #ENTRY_HEAD_HEIGHT} had over one line of the degree, and + * declared as air rather than left implicit in a band height so that a degree needing + * two lines gets two lines with the same air around them. As a band the title kept the + * air and lost the lines: a box told to be 9.3pt tall stays 9.3pt tall, and the overflow + * drew over the institution below it.

    + */ + static final double EDUCATION_DEGREE_AIR = + Math.max(0.0, (ENTRY_HEAD_HEIGHT - EDUCATION_DEGREE_SIZE * BODY_LINE_FACTOR) / 2.0); + + // -- type ------------------------------------------------------------ + + static final FontName DISPLAY_FONT = FontName.BARLOW_CONDENSED; + static final FontName BODY_FONT = FontName.LATO; + + static final double BODY_SIZE = 7.1; + static final double SIDEBAR_HEADING_SIZE = 8.5; + static final double MAIN_HEADING_SIZE = 8.7; + static final double NAME_SIZE = 36.7; + static final double PROFESSIONAL_TITLE_SIZE = 9.5; + static final double ENTRY_TITLE_SIZE = 7.5; + static final double META_SIZE = 7.2; + static final double REFERENCES_SIZE = 7.4; + static final double BODY_LEADING = 1.18; + + static final double NAME_TRACKING_EM = 0.114; + static final double HEADING_TRACKING_EM = 0.14; + static final double ROLE_TRACKING_EM = 0.18; + + /** + * The advance of a space in the body face, as a fraction of type size. + * {@link #tracked} sizes its spacer runs against this to hit a tracking + * measured in ems. + */ + static final double SPACE_ADVANCE_EM = 0.25; + + // -- vertical rhythm ------------------------------------------------- + + static final double SIDEBAR_BODY_TOP = 22.0; + static final double SIDEBAR_HEADING_TO_RULE = 4.0; + static final double CONTACT_HEADING_TO_BODY = 16.0; + static final double STANDARD_HEADING_TO_BODY = 13.5; + static final double CONTACT_ROW_GAP = 11.8; + static final double SKILL_ROW_GAP = 9.3; + static final double EDUCATION_HEADING_TO_BODY = 10.0; + static final double EDUCATION_LINE_GAP = 2.5; + static final double EDUCATION_ENTRY_GAP = 11.0; + static final double LANGUAGE_ROW_GAP = 7.3; + + static final double CONTACT_TO_SKILLS_ABOVE = 24.0; + static final double CONTACT_TO_SKILLS_BELOW = 18.0; + static final double SKILLS_TO_EDUCATION_ABOVE = 15.0; + static final double SKILLS_TO_EDUCATION_BELOW = 18.0; + static final double EDUCATION_TO_LANGUAGES_ABOVE = 8.0; + static final double EDUCATION_TO_LANGUAGES_BELOW = 16.0; + + static final double MAIN_PAD_TOP = 21.9; + static final double NAME_TO_ROLE = 6.3; + static final double ROLE_TO_RULE = 14.9; + static final double IDENTITY_TO_PROFILE = 20.5; + static final double MAIN_HEADING_TO_RULE = 4.0; + static final double MAIN_HEADING_TO_BODY = 14.0; + static final double EXPERIENCE_HEADING_TO_BODY = 18.3; + static final double PROJECT_HEADING_TO_BODY = 8.0; + static final double REFERENCES_HEADING_TO_BODY = 8.0; + static final double PROFILE_TO_EXPERIENCE = 24.5; + static final double ENTRY_HEAD_TO_META = 2.0; + static final double ENTRY_META_TO_HIGHLIGHTS = 14.3; + static final double HIGHLIGHT_ITEM_GAP = 4.8; + static final double ENTRY_TO_DIVIDER = 14.0; + static final double DIVIDER_TO_ENTRY = 13.0; + static final double EXPERIENCE_TO_PROJECTS = 22.0; + static final double PROJECT_HEAD_TO_BODY = 5.0; + static final double PROJECT_TO_DIVIDER = 6.0; + static final double DIVIDER_TO_PROJECT = 7.0; + static final double PROJECTS_TO_REFERENCES = 18.0; + + // -- palette --------------------------------------------------------- + + static final DocumentColor PAGE_BACKGROUND = DocumentColor.WHITE; + static final DocumentColor SIDEBAR_BACKGROUND = DocumentColor.rgb(247, 248, 250); + static final DocumentColor PLATE_BACKGROUND = DocumentColor.rgb(11, 39, 77); + static final DocumentColor TEXT_PRIMARY = DocumentColor.rgb(7, 17, 30); + static final DocumentColor TEXT_MUTED = DocumentColor.rgb(47, 74, 115); + static final DocumentColor ACCENT_PRIMARY = DocumentColor.rgb(11, 103, 200); + static final DocumentColor RULE_MUTED = DocumentColor.rgb(209, 211, 214); + static final DocumentColor RATING_MUTED = DocumentColor.rgb(194, 196, 199); + + // -- styles ---------------------------------------------------------- + + static DocumentTextStyle body() { + return style(BODY_FONT, BODY_SIZE, TEXT_PRIMARY, DocumentTextDecoration.DEFAULT); + } + + static DocumentTextStyle metaItalic() { + return style(BODY_FONT, META_SIZE, TEXT_MUTED, DocumentTextDecoration.ITALIC); + } + + static DocumentTextStyle style(FontName font, double size, DocumentColor color, + DocumentTextDecoration decoration) { + return TextStyles.of(font, size, decoration, color); + } + + /** + * Writes text letter by letter with a sized space between each pair, + * which is how this design gets its letter-spacing: a text style carries + * no tracking, so the gap is a run of its own whose type size is chosen + * to advance by {@code trackingEm} of the surrounding size. + * + * @param paragraph the paragraph being built + * @param text the text to space out + * @param style the style of the letters + * @param trackingEm the gap between letters, in ems of {@code style} + */ + static void tracked(ParagraphBuilder paragraph, String text, + DocumentTextStyle style, double trackingEm) { + DocumentTextStyle spacer = style.withSize(trackingEm * style.size() / SPACE_ADVANCE_EM); + for (int i = 0; i < text.length(); i++) { + paragraph.inlineText(String.valueOf(text.charAt(i)), style); + if (i + 1 < text.length()) { + paragraph.inlineText(" ", spacer); + } + } + } + + /** Strips a title down to the letters and digits a node name can carry. */ + static String compact(String text) { + return text == null ? "" : text.replaceAll("[^A-Za-z0-9]", ""); + } + + static double clamp01(double value) { + return Math.max(0.0, Math.min(1.0, value)); + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/ProfessionalSidebarWidgets.java b/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/ProfessionalSidebarWidgets.java new file mode 100644 index 000000000..7588b1ec5 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/ProfessionalSidebarWidgets.java @@ -0,0 +1,159 @@ +package com.demcha.compose.document.templates.cv.presets; + +import com.demcha.compose.document.dsl.ParagraphBuilder; +import com.demcha.compose.document.dsl.SectionBuilder; +import com.demcha.compose.document.node.DocumentNode; +import com.demcha.compose.document.node.TextAlign; +import com.demcha.compose.document.style.ClipPolicy; +import com.demcha.compose.document.style.DocumentInsets; +import com.demcha.compose.document.style.DocumentTextDecoration; +import com.demcha.compose.document.style.DocumentTextStyle; + +import static com.demcha.compose.document.templates.cv.presets.ProfessionalSidebarStyles.ACCENT_PRIMARY; +import static com.demcha.compose.document.templates.cv.presets.ProfessionalSidebarStyles.BODY_FONT; +import static com.demcha.compose.document.templates.cv.presets.ProfessionalSidebarStyles.BODY_LEADING; +import static com.demcha.compose.document.templates.cv.presets.ProfessionalSidebarStyles.ENTRY_HEAD_HEIGHT; +import static com.demcha.compose.document.templates.cv.presets.ProfessionalSidebarStyles.ENTRY_TITLE_SIZE; +import static com.demcha.compose.document.templates.cv.presets.ProfessionalSidebarStyles.HEADING_TRACKING_EM; +import static com.demcha.compose.document.templates.cv.presets.ProfessionalSidebarStyles.MAIN_CONTENT_WIDTH; +import static com.demcha.compose.document.templates.cv.presets.ProfessionalSidebarStyles.MAIN_HEADING_SIZE; +import static com.demcha.compose.document.templates.cv.presets.ProfessionalSidebarStyles.MAIN_HEADING_TO_RULE; +import static com.demcha.compose.document.templates.cv.presets.ProfessionalSidebarStyles.META_SIZE; +import static com.demcha.compose.document.templates.cv.presets.ProfessionalSidebarStyles.RULE_MUTED; +import static com.demcha.compose.document.templates.cv.presets.ProfessionalSidebarStyles.RULE_THICKNESS; +import static com.demcha.compose.document.templates.cv.presets.ProfessionalSidebarStyles.SECTION_ACCENT_HEIGHT; +import static com.demcha.compose.document.templates.cv.presets.ProfessionalSidebarStyles.SECTION_ACCENT_WIDTH; +import static com.demcha.compose.document.templates.cv.presets.ProfessionalSidebarStyles.SIDEBAR_HEADING_SIZE; +import static com.demcha.compose.document.templates.cv.presets.ProfessionalSidebarStyles.SIDEBAR_HEADING_TO_RULE; +import static com.demcha.compose.document.templates.cv.presets.ProfessionalSidebarStyles.SIDEBAR_INNER_WIDTH; +import static com.demcha.compose.document.templates.cv.presets.ProfessionalSidebarStyles.TEXT_MUTED; +import static com.demcha.compose.document.templates.cv.presets.ProfessionalSidebarStyles.TEXT_PRIMARY; +import static com.demcha.compose.document.templates.cv.presets.ProfessionalSidebarStyles.compact; +import static com.demcha.compose.document.templates.cv.presets.ProfessionalSidebarStyles.style; +import static com.demcha.compose.document.templates.cv.presets.ProfessionalSidebarStyles.tracked; + +/** + * The parts both columns of the Professional Sidebar CV are built from: the + * heading over its short accent rule, the hairline that separates entries, + * and the band that sets an entry title against its dates. + */ +final class ProfessionalSidebarWidgets { + + private ProfessionalSidebarWidgets() { + } + + /** + * A sidebar heading: tracked capitals over a short accent rule. + * + *

    The heading is drawn as authored — the design sets these in + * capitals, and the preset does not upper-case for the author, so a + * document that titles a section in sentence case gets sentence case.

    + * + * @param section the sidebar column + * @param title the heading text + * @param bodyGap the gap between the rule and the section body + */ + static void sidebarHeading(SectionBuilder section, String title, double bodyGap) { + heading(section, "Sidebar", title, SIDEBAR_HEADING_SIZE, + SIDEBAR_HEADING_TO_RULE, bodyGap); + } + + /** + * A main-column heading, a fraction larger than its sidebar counterpart + * and otherwise identical. + * + * @param section the main column + * @param title the heading text + * @param bodyGap the gap between the rule and the section body + */ + static void mainHeading(SectionBuilder section, String title, double bodyGap) { + heading(section, "Main", title, MAIN_HEADING_SIZE, + MAIN_HEADING_TO_RULE, bodyGap); + } + + private static void heading(SectionBuilder section, String column, String title, + double size, double ruleGap, double bodyGap) { + DocumentTextStyle headingStyle = + style(BODY_FONT, size, TEXT_PRIMARY, DocumentTextDecoration.BOLD); + ParagraphBuilder heading = new ParagraphBuilder() + .name(column + "Heading_" + compact(title)) + .textStyle(headingStyle); + tracked(heading, title, headingStyle, HEADING_TRACKING_EM); + section.add(heading.margin(new DocumentInsets(0, 0, ruleGap, 0)).build()); + section.addLine(line -> line + .name(column + "HeadingRule_" + compact(title)) + .horizontal(SECTION_ACCENT_WIDTH) + .thickness(SECTION_ACCENT_HEIGHT) + .color(ACCENT_PRIMARY) + .margin(new DocumentInsets(0, 0, bodyGap, 0))); + } + + /** The hairline between two sidebar sections. */ + static void sidebarDivider(SectionBuilder section, double gapAbove, double gapBelow) { + divider(section, "SidebarDivider", SIDEBAR_INNER_WIDTH, gapAbove, gapBelow); + } + + /** The hairline between two main-column entries. */ + static void mainDivider(SectionBuilder section, double gapAbove, double gapBelow) { + divider(section, "MainDivider", MAIN_CONTENT_WIDTH, gapAbove, gapBelow); + } + + private static void divider(SectionBuilder section, String name, double width, + double gapAbove, double gapBelow) { + section.addLine(line -> line + .name(name) + .horizontal(width) + .thickness(RULE_THICKNESS) + .color(RULE_MUTED) + .margin(new DocumentInsets(gapAbove, 0, gapBelow, 0))); + } + + /** + * An entry head: the title at the left of a fixed-height band with its + * dates set flush right on the same optical line. + * + *

    It is a container rather than a two-column row because the two ends + * are centred against each other vertically, which a row of sections + * cannot do — a section is as tall as its content and starts at the top + * of its column.

    + * + * @param section the host column + * @param name the node name of the band + * @param title the entry title + * @param dates the entry dates, drawn flush right + */ + static void titleDateBand(SectionBuilder section, String name, + String title, String dates) { + DocumentNode titleNode = paragraph(name + "Title", title, + style(BODY_FONT, ENTRY_TITLE_SIZE, TEXT_PRIMARY, + DocumentTextDecoration.BOLD), + TextAlign.LEFT); + DocumentNode dateNode = paragraph(name + "Dates", dates, + style(BODY_FONT, META_SIZE, TEXT_MUTED, + DocumentTextDecoration.DEFAULT), + TextAlign.RIGHT); + section.addContainer(band -> band + .name(name) + .rectangle(MAIN_CONTENT_WIDTH, ENTRY_HEAD_HEIGHT) + .clipPolicy(ClipPolicy.OVERFLOW_VISIBLE) + .centerLeft(titleNode) + .centerRight(dateNode)); + } + + /** A standalone paragraph node, for anchoring inside a container. */ + static DocumentNode paragraph(String name, String text, + DocumentTextStyle style, TextAlign align) { + return new ParagraphBuilder() + .name(name) + .text(text) + .textStyle(style) + .align(align) + .lineSpacing(BODY_LEADING) + .margin(DocumentInsets.zero()) + .build(); + } + + static void spacer(SectionBuilder section, double height) { + section.addSpacer(spacer -> spacer.height(height)); + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/SerifHeadline.java b/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/SerifHeadline.java new file mode 100644 index 000000000..b4c37d4d3 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/SerifHeadline.java @@ -0,0 +1,261 @@ +package com.demcha.compose.document.templates.cv.presets; + +import com.demcha.compose.document.api.DocumentSession; +import com.demcha.compose.document.node.RowVerticalAlign; +import com.demcha.compose.document.style.DocumentInsets; +import com.demcha.compose.document.templates.api.DocumentTemplate; +import com.demcha.compose.document.templates.cv.components.SectionLookup; +import com.demcha.compose.document.templates.cv.data.CvDocument; +import com.demcha.compose.document.templates.cv.data.CvSection; +import com.demcha.compose.document.templates.cv.data.EntriesSection; +import com.demcha.compose.document.templates.cv.data.ParagraphSection; +import com.demcha.compose.document.templates.cv.data.RowsSection; +import com.demcha.compose.document.templates.cv.data.SkillsSection; + +import java.util.List; +import java.util.Objects; + +import static com.demcha.compose.document.templates.cv.presets.SerifHeadlineStyles.ASIDE_CELL_WEIGHT; +import static com.demcha.compose.document.templates.cv.presets.SerifHeadlineStyles.BODY_BAND_TO_CERTIFICATIONS; +import static com.demcha.compose.document.templates.cv.presets.SerifHeadlineStyles.MAIN_CELL_WEIGHT; +import static com.demcha.compose.document.templates.cv.presets.SerifHeadlineStyles.MARGIN; +import static com.demcha.compose.document.templates.cv.presets.SerifHeadlineStyles.PAGE; + +/** + * Serif Headline — a one-page A4 CV under a Volkhov masthead: the name in + * the display serif over its role and a short gold rule, the contact + * channels stacked opposite, then a two-column body — the roles held on a + * timeline rail and the projects as marked cards on the left, the degrees + * and the skill meters across a hairline divider on the right — closing with + * full-width bands of certifications and achievements. + * + *

    Geometry

    + * + *

    This sheet is not a set of round numbers. It was drawn on a 1024-pixel + * grid, so the preset carries that grid's numbers and scales them onto A4 — + * heights by an extra factor, because the drawing is proportionally taller + * than the page. Vertical gaps are stated as the white the drawing shows + * between letters, with the blank a line box carries above and below its own + * type subtracted; setting the pixel numbers directly would push every block + * a few points too far apart.

    + * + *

    One measure is deliberately not the drawing's: a band column keeps a + * gutter at its right edge, so its text stops short of the hairline instead + * of running into it. In the published sheet a line that happens to fill its + * column touches the rule.

    + * + *

    One page, and what happens past it

    + * + *

    This sheet holds one page of content. The body is a single row, and a + * row is atomic — it cannot be split — so a CV longer than the sheet does + * not flow onto a second page: composing it raises + * {@code AtomicNodeTooLargeException}, naming the node and the height it + * needed. It draws no cap of its own, because a CV that quietly loses a job + * is worse than one that refuses to compose. {@link TimelineMinimal} is the + * preset in this package that splits its own columns across pages.

    + * + *

    How a document reaches its berth

    + * + *

    The columns are fixed, so the preset assigns each section a berth by + * title rather than reading {@link + * com.demcha.compose.document.templates.cv.data.Slot}. It has eight:

    + * + *
      + *
    • the summary, a {@link ParagraphSection} set under the masthead;
    • + *
    • experience and projects, {@link EntriesSection}s in the wide + * column;
    • + *
    • education, an {@link EntriesSection}, and skills, a + * {@link SkillsSection}, in the narrow one;
    • + *
    • soft skills, a {@link ParagraphSection} of one line each, under the + * skills;
    • + *
    • certifications, a {@link RowsSection} of qualification and issuer, + * and achievements, an {@link EntriesSection}, as the closing + * bands.
    • + *
    + * + *

    A section whose title matches no berth is not drawn, a berth with no + * section takes its heading with it, and a berth is filled by the first + * section that names it. The certifications' {@code RowStyle} is not read — + * the rows are drawn to this design rather than to a family style — and the + * skills heading covers the soft skills too, so a document with only soft + * skills is headed "Skills".

    + * + *

    Marks

    + * + *

    Each project and each achievement is opened by the mark its entry names + * in {@code CvEntry.icon()}. The vocabulary is this preset's own — + * {@code cart}, {@code api}, {@code trophy}, {@code chart}, {@code rocket} — + * and an unknown token is reported as a data error naming the set. An entry + * with no token is drawn without a mark. The certification medal is chrome + * rather than data: every plate takes the same one.

    + * + *

    The marks come in two colours, which is worth knowing before choosing + * one: {@code cart} and {@code api} are drawn in the design's navy ink, and + * {@code trophy}, {@code chart} and {@code rocket} in its gold. Nothing + * stops a document mixing them, but a band that does reads as a mistake + * rather than a choice.

    + * + *

    Every title this preset draws — a role, a project, a degree, an + * achievement — becomes a link when its entry carries {@code CvEntry.link()}. + * A link is an annotation rather than ink, so a linked title and a plain one + * are the same sheet.

    + * + *

    The employer's city and the campus come from {@code CvEntry.place()}, + * because this design sets them beside the employer and the years in their + * own colour rather than inside them.

    + * + *

    The phone, the email and each link are reachable from the PDF, with the + * {@code tel:} and {@code mailto:} targets built from the values. The set + * packages two network marks: a link naming GitHub takes that one, anything + * else takes LinkedIn's.

    + * + *

    Fonts

    + * + *

    The name is set in Volkhov and everything else in Lato. The templates + * artifact carries neither — register them on the session, or the engine + * substitutes and the measured geometry here no longer matches the type + * sitting in it.

    + * + *

    Usage:

    + *
    {@code
    + * DocumentTemplate template = SerifHeadline.create();
    + * template.compose(session, cv);
    + * }
    + */ +public final class SerifHeadline { + + /** Stable identifier of this preset. */ + public static final String ID = "serif-headline"; + + /** Human-readable name of this preset. */ + public static final String DISPLAY_NAME = "Serif Headline"; + + /** + * The margin this preset expects: none of the caller's. It sets the page + * margin itself, because the grid the sheet is drawn on defines it. + */ + public static final double RECOMMENDED_MARGIN = 0.0; + + private static final List SUMMARY_KEYS = + List.of("summary", "profile", "about"); + private static final List EXPERIENCE_KEYS = + List.of("experience", "employment", "work history", "career"); + private static final List PROJECT_KEYS = + List.of("projects", "project", "portfolio"); + private static final List EDUCATION_KEYS = + List.of("education", "qualifications"); + private static final List SOFT_SKILL_KEYS = + List.of("soft skills", "strengths"); + private static final List SKILL_KEYS = + List.of("skills", "expertise", "competencies"); + private static final List CERTIFICATION_KEYS = + List.of("certifications", "certification", "licences", "licenses"); + private static final List ACHIEVEMENT_KEYS = + List.of("achievements", "achievement", "awards"); + + private SerifHeadline() { + } + + /** + * Creates the template. + * + * @return a template composing a {@link CvDocument} onto its own page + */ + public static DocumentTemplate create() { + return new Template(); + } + + private record Template() implements DocumentTemplate { + + @Override + public String id() { + return ID; + } + + @Override + public String displayName() { + return DISPLAY_NAME; + } + + @Override + public void compose(DocumentSession document, CvDocument doc) { + Objects.requireNonNull(document, "document"); + Objects.requireNonNull(doc, "doc"); + List sections = doc.sections(); + + document.pageSize(PAGE).margin(DocumentInsets.of(MARGIN)); + + ParagraphSection summary = berth(sections, ParagraphSection.class, SUMMARY_KEYS); + EntriesSection experience = berth(sections, EntriesSection.class, EXPERIENCE_KEYS); + EntriesSection projects = berth(sections, EntriesSection.class, PROJECT_KEYS); + EntriesSection education = berth(sections, EntriesSection.class, EDUCATION_KEYS); + // Soft skills first: "Soft Skills" names the skills berth too, and + // the block that has a shape of its own should win it. + ParagraphSection softSkills = + berth(sections, ParagraphSection.class, SOFT_SKILL_KEYS); + SkillsSection skills = berth(sections, SkillsSection.class, SKILL_KEYS); + RowsSection certifications = + berth(sections, RowsSection.class, CERTIFICATION_KEYS); + EntriesSection achievements = + berth(sections, EntriesSection.class, ACHIEVEMENT_KEYS); + + boolean hasBody = SectionLookup.hasContent(experience) + || SectionLookup.hasContent(projects) + || SectionLookup.hasContent(education) + || SectionLookup.hasContent(skills) + || SectionLookup.hasContent(softSkills); + boolean hasCertifications = SectionLookup.hasContent(certifications); + boolean hasAchievements = SectionLookup.hasContent(achievements); + + document.pageFlow(page -> { + page.name("SerifHeadlineCv").spacing(0).padding(DocumentInsets.zero()); + SerifHeadlineMasthead.compose(page, doc.identity(), summary); + if (hasBody) { + page.addRow("BodyColumns", row -> { + row.spacing(0); + row.gap(0); + row.weights(MAIN_CELL_WEIGHT, ASIDE_CELL_WEIGHT); + // The row splits on the gutter's midline, so the + // divider the right column hangs off lands in the + // middle of the gutter rather than on its edge. + row.verticalAlign(RowVerticalAlign.TOP); + row.margin(new DocumentInsets( + 0, 0, BODY_BAND_TO_CERTIFICATIONS, 0)); + row.addSection("MainColumn", section -> + SerifHeadlineMain.compose(section, experience, projects)); + row.addSection("AsideColumn", section -> + SerifHeadlineAside.compose(section, education, skills, + softSkills)); + }); + } + if (hasCertifications) { + SerifHeadlineClosing.renderCertifications(page, certifications, + hasAchievements); + } + if (hasAchievements) { + SerifHeadlineClosing.renderAchievements(page, achievements); + } + }); + } + + /** + * The first section of the wanted shape whose title names this berth, + * or {@code null} when the document fills no such berth. + */ + private static T berth(List sections, + Class type, + List keys) { + for (CvSection section : sections) { + if (!type.isInstance(section)) { + continue; + } + for (String key : keys) { + if (SectionLookup.titleContains(section.title(), key)) { + return type.cast(section); + } + } + } + return null; + } + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/SerifHeadlineAside.java b/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/SerifHeadlineAside.java new file mode 100644 index 000000000..a5dddadf7 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/SerifHeadlineAside.java @@ -0,0 +1,248 @@ +package com.demcha.compose.document.templates.cv.presets; + +import com.demcha.compose.document.dsl.ParagraphBuilder; +import com.demcha.compose.document.dsl.SectionBuilder; +import com.demcha.compose.document.dsl.ShapeBuilder; +import com.demcha.compose.document.dsl.ShapeContainerBuilder; +import com.demcha.compose.document.node.DocumentNode; +import com.demcha.compose.document.node.LayerAlign; +import com.demcha.compose.document.style.ClipPolicy; +import com.demcha.compose.document.style.DocumentInsets; +import com.demcha.compose.document.style.DocumentTextDecoration; +import com.demcha.compose.document.style.DocumentTextStyle; +import com.demcha.compose.document.templates.cv.components.SectionLookup; +import com.demcha.compose.document.templates.cv.data.CvEntry; +import com.demcha.compose.document.templates.cv.data.CvSkill; +import com.demcha.compose.document.templates.cv.data.EntriesSection; +import com.demcha.compose.document.templates.cv.data.ParagraphSection; +import com.demcha.compose.document.templates.cv.data.SkillGroup; +import com.demcha.compose.document.templates.cv.data.SkillsSection; + +import java.util.List; +import java.util.Locale; + +import static com.demcha.compose.document.templates.cv.presets.SerifHeadlineStyles.ACCENT; +import static com.demcha.compose.document.templates.cv.presets.SerifHeadlineStyles.ASIDE_WIDTH; +import static com.demcha.compose.document.templates.cv.presets.SerifHeadlineStyles.CAPTION_TO_ROWS; +import static com.demcha.compose.document.templates.cv.presets.SerifHeadlineStyles.CAPTION_TO_SOFT_SKILLS; +import static com.demcha.compose.document.templates.cv.presets.SerifHeadlineStyles.DEGREE_TO_INSTITUTION; +import static com.demcha.compose.document.templates.cv.presets.SerifHeadlineStyles.DIVIDER; +import static com.demcha.compose.document.templates.cv.presets.SerifHeadlineStyles.EDUCATION_TO_SKILLS; +import static com.demcha.compose.document.templates.cv.presets.SerifHeadlineStyles.GROUP_GAP; +import static com.demcha.compose.document.templates.cv.presets.SerifHeadlineStyles.HAIRLINE_THICKNESS; +import static com.demcha.compose.document.templates.cv.presets.SerifHeadlineStyles.HALF_GUTTER; +import static com.demcha.compose.document.templates.cv.presets.SerifHeadlineStyles.HEADING_TO_CAPTION; +import static com.demcha.compose.document.templates.cv.presets.SerifHeadlineStyles.HEADING_TO_EDUCATION; +import static com.demcha.compose.document.templates.cv.presets.SerifHeadlineStyles.INK; +import static com.demcha.compose.document.templates.cv.presets.SerifHeadlineStyles.INSTITUTION_TO_YEARS; +import static com.demcha.compose.document.templates.cv.presets.SerifHeadlineStyles.ITEM_TITLE_SIZE; +import static com.demcha.compose.document.templates.cv.presets.SerifHeadlineStyles.RULE; +import static com.demcha.compose.document.templates.cv.presets.SerifHeadlineStyles.SKILL_ROW_GAP; +import static com.demcha.compose.document.templates.cv.presets.SerifHeadlineStyles.SKILL_ROW_HEIGHT; +import static com.demcha.compose.document.templates.cv.presets.SerifHeadlineStyles.SMALL_SIZE; +import static com.demcha.compose.document.templates.cv.presets.SerifHeadlineStyles.SOFT_SKILL_LEADING; +import static com.demcha.compose.document.templates.cv.presets.SerifHeadlineStyles.STUB_RULE; +import static com.demcha.compose.document.templates.cv.presets.SerifHeadlineStyles.STUB_RULE_THICKNESS; +import static com.demcha.compose.document.templates.cv.presets.SerifHeadlineStyles.STUB_RULE_WIDTH; +import static com.demcha.compose.document.templates.cv.presets.SerifHeadlineStyles.STUB_TO_DEGREE; +import static com.demcha.compose.document.templates.cv.presets.SerifHeadlineStyles.TIGHT_LEADING; +import static com.demcha.compose.document.templates.cv.presets.SerifHeadlineStyles.TRACK; +import static com.demcha.compose.document.templates.cv.presets.SerifHeadlineStyles.TRACK_THICKNESS; +import static com.demcha.compose.document.templates.cv.presets.SerifHeadlineStyles.TRACK_WIDTH; +import static com.demcha.compose.document.templates.cv.presets.SerifHeadlineStyles.YEARS_TO_STUB; +import static com.demcha.compose.document.templates.cv.presets.SerifHeadlineStyles.body; +import static com.demcha.compose.document.templates.cv.presets.SerifHeadlineStyles.compact; +import static com.demcha.compose.document.templates.cv.presets.SerifHeadlineStyles.small; +import static com.demcha.compose.document.templates.cv.presets.SerifHeadlineStyles.style; +import static com.demcha.compose.document.templates.cv.presets.SerifHeadlineStyles.tracked; +import static com.demcha.compose.document.templates.cv.presets.SerifHeadlineWidgets.sectionHeading; + +/** + * The narrow right column, hung off the hairline that divides the grid: the + * degrees, then the skills as captioned groups of meters, closing with the + * soft skills as plain lines. + */ +final class SerifHeadlineAside { + + private SerifHeadlineAside() { + } + + static void compose(SectionBuilder section, + EntriesSection education, + SkillsSection skills, + ParagraphSection softSkills) { + // The divider is this column's left accent, so it runs exactly as + // deep as the column's content and no further. + section.spacing(0) + .padding(new DocumentInsets(0, 0, 0, HALF_GUTTER)) + .accentLeft(DIVIDER, HAIRLINE_THICKNESS); + boolean hasEducation = SectionLookup.hasContent(education); + if (hasEducation) { + sectionHeading(section, education.title(), ASIDE_WIDTH, false, 0.0, HALF_GUTTER); + renderEducation(section, education); + } + if (SectionLookup.hasContent(skills) || SectionLookup.hasContent(softSkills)) { + sectionHeading(section, skillsHeading(skills), ASIDE_WIDTH, false, + hasEducation ? EDUCATION_TO_SKILLS : 0.0, HALF_GUTTER); + renderSkills(section, skills, softSkills); + } + } + + /** The heading over both blocks, taken from the skills section when there is one. */ + private static String skillsHeading(SkillsSection skills) { + return SectionLookup.hasContent(skills) ? skills.title() : "Skills"; + } + + // -- education ------------------------------------------------------- + + /** + * Each degree as three lines — the qualification, the institution in + * accent, and the years beside the place — with a short stub rule + * between neighbours. + */ + private static void renderEducation(SectionBuilder section, EntriesSection education) { + List entries = education.entries(); + for (int i = 0; i < entries.size(); i++) { + CvEntry entry = entries.get(i); + boolean first = i == 0; + section.addParagraph(p -> { + p.name("Degree_" + compact(entry.title())) + .textStyle(style(ITEM_TITLE_SIZE, INK, DocumentTextDecoration.BOLD)) + .lineSpacing(TIGHT_LEADING); + SerifHeadlineText.title(p, entry, + style(ITEM_TITLE_SIZE, INK, DocumentTextDecoration.BOLD)); + p.margin(new DocumentInsets( + first ? HEADING_TO_EDUCATION : 0, 0, DEGREE_TO_INSTITUTION, 0)); + }); + section.addParagraph(p -> p + .name("Institution_" + compact(entry.title())) + .text(entry.subtitle()) + .textStyle(style(SMALL_SIZE, ACCENT, DocumentTextDecoration.DEFAULT)) + .lineSpacing(TIGHT_LEADING) + .margin(new DocumentInsets(0, 0, INSTITUTION_TO_YEARS, 0))); + section.addParagraph(p -> { + p.name("Years_" + compact(entry.title())) + .textStyle(small()) + .inlineText(entry.date(), small()); + if (!entry.place().isBlank()) { + p.inlineText(" | ", + style(SMALL_SIZE, RULE, DocumentTextDecoration.DEFAULT)) + .inlineText(entry.place(), small()); + } + p.lineSpacing(TIGHT_LEADING).margin(DocumentInsets.zero()); + }); + if (i < entries.size() - 1) { + section.addLine(line -> line + .name("EducationStub_" + compact(entry.title())) + .horizontal(STUB_RULE_WIDTH) + .thickness(STUB_RULE_THICKNESS) + .color(STUB_RULE) + .margin(new DocumentInsets(YEARS_TO_STUB, 0, STUB_TO_DEGREE, 0))); + } + } + } + + // -- skills ---------------------------------------------------------- + + /** + * Each skill group under its own tracked caption, then the soft skills + * under theirs — the one block on this sheet that is prose rather than + * meters, so it takes the section's title as its caption. + */ + private static void renderSkills(SectionBuilder section, SkillsSection skills, + ParagraphSection softSkills) { + boolean firstCaption = true; + if (SectionLookup.hasContent(skills)) { + List groups = skills.groups(); + for (int i = 0; i < groups.size(); i++) { + SkillGroup group = groups.get(i); + caption(section, group.category(), + i == 0 ? HEADING_TO_CAPTION : GROUP_GAP); + section.addSection("SkillGroup_" + compact(group.category()), rows -> { + rows.spacing(SKILL_ROW_GAP).padding(DocumentInsets.zero()); + rows.margin(new DocumentInsets(CAPTION_TO_ROWS, 0, 0, 0)); + for (CvSkill skill : group.entries()) { + renderSkillRow(rows, skill); + } + }); + } + firstCaption = groups.isEmpty(); + } + if (SectionLookup.hasContent(softSkills)) { + caption(section, softSkills.title(), firstCaption ? HEADING_TO_CAPTION : GROUP_GAP); + renderSoftSkills(section, softSkills); + } + } + + private static void caption(SectionBuilder section, String text, double gapAbove) { + DocumentTextStyle captionStyle = style(SMALL_SIZE, INK, DocumentTextDecoration.BOLD); + section.addParagraph(p -> { + p.name("SkillCaption_" + compact(text)); + p.textStyle(captionStyle); + tracked(p, text.toUpperCase(Locale.ROOT), captionStyle); + p.lineSpacing(TIGHT_LEADING); + p.margin(new DocumentInsets(gapAbove, 0, 0, 0)); + }); + } + + /** + * A skill row: the name at the left of a band, the meter flush right. A + * skill the document leaves unlevelled draws its name alone rather than + * an empty track. + */ + private static void renderSkillRow(SectionBuilder rows, CvSkill skill) { + DocumentNode label = new ParagraphBuilder() + .name("SkillLabel_" + compact(skill.name())) + .text(skill.name()) + .textStyle(small()) + .lineSpacing(TIGHT_LEADING) + .margin(DocumentInsets.zero()) + .build(); + rows.addContainer(band -> { + band.name("SkillRow_" + compact(skill.name())) + .rectangle(ASIDE_WIDTH, SKILL_ROW_HEIGHT) + .clipPolicy(ClipPolicy.OVERFLOW_VISIBLE) + .padding(DocumentInsets.zero()) + .position(label, 0, 0, LayerAlign.CENTER_LEFT); + if (skill.level().isPresent()) { + band.position(meter(skill), 0, 0, LayerAlign.CENTER_RIGHT); + } + }); + } + + private static DocumentNode meter(CvSkill skill) { + double level = Math.max(0.0, Math.min(1.0, skill.level().getAsDouble())); + // A shape, not a shape container: a container must own at least one + // layer, and the fill is a bare rounded rectangle with nothing in it. + DocumentNode fill = new ShapeBuilder() + .name("SkillFill_" + compact(skill.name())) + .size(TRACK_WIDTH * level, TRACK_THICKNESS) + .cornerRadius(TRACK_THICKNESS / 2.0) + .fillColor(INK) + .margin(DocumentInsets.zero()) + .build(); + return new ShapeContainerBuilder() + .name("SkillTrack_" + compact(skill.name())) + .roundedRect(TRACK_WIDTH, TRACK_THICKNESS, TRACK_THICKNESS / 2.0) + .clipPolicy(ClipPolicy.OVERFLOW_VISIBLE) + .fillColor(TRACK) + .padding(DocumentInsets.zero()) + .position(fill, 0, 0, LayerAlign.CENTER_LEFT) + .build(); + } + + /** One line per line of the block's body, set as plain prose. */ + private static void renderSoftSkills(SectionBuilder section, ParagraphSection softSkills) { + List lines = SerifHeadlineText.lines(softSkills.body()); + for (int i = 0; i < lines.size(); i++) { + String line = lines.get(i); + boolean first = i == 0; + section.addParagraph(p -> p + .name("SoftSkill_" + compact(line)) + .text(line) + .textStyle(body()) + .lineSpacing(SOFT_SKILL_LEADING) + .margin(new DocumentInsets(first ? CAPTION_TO_SOFT_SKILLS : 0, 0, 0, 0))); + } + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/SerifHeadlineClosing.java b/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/SerifHeadlineClosing.java new file mode 100644 index 000000000..5a7e5f024 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/SerifHeadlineClosing.java @@ -0,0 +1,147 @@ +package com.demcha.compose.document.templates.cv.presets; + +import com.demcha.compose.document.dsl.ImageBuilder; +import com.demcha.compose.document.dsl.PageFlowBuilder; +import com.demcha.compose.document.dsl.SectionBuilder; +import com.demcha.compose.document.node.DocumentNode; +import com.demcha.compose.document.style.DocumentInsets; +import com.demcha.compose.document.style.DocumentTextDecoration; +import com.demcha.compose.document.templates.cv.data.CvEntry; +import com.demcha.compose.document.templates.cv.data.CvRow; +import com.demcha.compose.document.templates.cv.data.EntriesSection; +import com.demcha.compose.document.templates.cv.data.RowsSection; + +import java.util.List; + +import static com.demcha.compose.document.templates.cv.presets.SerifHeadlineStyles.ACHIEVEMENT_BODY_LEADING; +import static com.demcha.compose.document.templates.cv.presets.SerifHeadlineStyles.ACHIEVEMENT_GLYPH_BOX; +import static com.demcha.compose.document.templates.cv.presets.SerifHeadlineStyles.ACHIEVEMENT_GLYPH_HANG; +import static com.demcha.compose.document.templates.cv.presets.SerifHeadlineStyles.ACHIEVEMENT_GLYPH_TO_TEXT; +import static com.demcha.compose.document.templates.cv.presets.SerifHeadlineStyles.ACHIEVEMENT_SEPARATOR_HEIGHT; +import static com.demcha.compose.document.templates.cv.presets.SerifHeadlineStyles.ACHIEVEMENT_TEXT_DROP; +import static com.demcha.compose.document.templates.cv.presets.SerifHeadlineStyles.ACHIEVEMENT_TITLE_TO_BODY; +import static com.demcha.compose.document.templates.cv.presets.SerifHeadlineStyles.CERTIFICATIONS_TO_ACHIEVEMENTS; +import static com.demcha.compose.document.templates.cv.presets.SerifHeadlineStyles.CERT_GLYPH_TO_TEXT; +import static com.demcha.compose.document.templates.cv.presets.SerifHeadlineStyles.CERT_LINE_LEADING; +import static com.demcha.compose.document.templates.cv.presets.SerifHeadlineStyles.CERT_PLATE_DIAMETER; +import static com.demcha.compose.document.templates.cv.presets.SerifHeadlineStyles.CERT_SEPARATOR_HEIGHT; +import static com.demcha.compose.document.templates.cv.presets.SerifHeadlineStyles.CONTENT_WIDTH; +import static com.demcha.compose.document.templates.cv.presets.SerifHeadlineStyles.GLYPH_CLEARANCE; +import static com.demcha.compose.document.templates.cv.presets.SerifHeadlineStyles.HEADING_TO_ACHIEVEMENTS; +import static com.demcha.compose.document.templates.cv.presets.SerifHeadlineStyles.HEADING_TO_CERTIFICATIONS; +import static com.demcha.compose.document.templates.cv.presets.SerifHeadlineStyles.INK; +import static com.demcha.compose.document.templates.cv.presets.SerifHeadlineStyles.ITEM_TITLE_SIZE; +import static com.demcha.compose.document.templates.cv.presets.SerifHeadlineStyles.MARGIN; +import static com.demcha.compose.document.templates.cv.presets.SerifHeadlineStyles.PLATE_HANG; +import static com.demcha.compose.document.templates.cv.presets.SerifHeadlineStyles.TIGHT_LEADING; +import static com.demcha.compose.document.templates.cv.presets.SerifHeadlineStyles.compact; +import static com.demcha.compose.document.templates.cv.presets.SerifHeadlineStyles.small; +import static com.demcha.compose.document.templates.cv.presets.SerifHeadlineStyles.style; +import static com.demcha.compose.document.templates.cv.presets.SerifHeadlineWidgets.BandColumn; +import static com.demcha.compose.document.templates.cv.presets.SerifHeadlineWidgets.columnBand; +import static com.demcha.compose.document.templates.cv.presets.SerifHeadlineWidgets.plate; +import static com.demcha.compose.document.templates.cv.presets.SerifHeadlineWidgets.sectionHeading; + +/** + * The two full-width bands that close the sheet: the certifications behind + * their medals, and the achievements behind their own marks. + */ +final class SerifHeadlineClosing { + + private SerifHeadlineClosing() { + } + + /** + * The certifications: a plate, then the qualification over who issued it. + * + *

    Every plate takes the same medal. It is the one mark on this sheet + * that is chrome rather than data — a certification is a certification — + * so the rows are {@link CvRow}s and carry no icon token.

    + */ + static void renderCertifications(PageFlowBuilder page, RowsSection certifications, + boolean achievementsFollow) { + List rows = certifications.rows(); + page.addSection("Certifications", section -> { + section.spacing(0).padding(DocumentInsets.zero()) + .margin(new DocumentInsets(0, 0, + achievementsFollow ? CERTIFICATIONS_TO_ACHIEVEMENTS : 0, 0)); + sectionHeading(section, certifications.title(), CONTENT_WIDTH, true, 0.0, MARGIN); + columnBand(section, "Certifications", CONTENT_WIDTH, rows.size(), + HEADING_TO_CERTIFICATIONS, CERT_SEPARATOR_HEIGHT, + index -> certificationColumn(rows.get(index), index)); + }); + } + + private static BandColumn certificationColumn(CvRow certification, int index) { + double lead = index == 0 ? PLATE_HANG : GLYPH_CLEARANCE; + SectionBuilder column = new SectionBuilder(); + column.name("Certification_" + compact(certification.label())) + .spacing(0) + .padding(new DocumentInsets(0, 0, 0, lead + CERT_GLYPH_TO_TEXT)); + column.addParagraph(p -> p + .name("CertificationTitle_" + compact(certification.label())) + .text(certification.label()) + .textStyle(small()) + .lineSpacing(CERT_LINE_LEADING) + .margin(DocumentInsets.zero())); + column.addParagraph(p -> p + .name("CertificationIssuer_" + compact(certification.label())) + .text(certification.body()) + .textStyle(small()) + .lineSpacing(CERT_LINE_LEADING) + .margin(DocumentInsets.zero())); + return new BandColumn(column, + plate("CertificationPlate_" + compact(certification.label()), + SerifHeadlineIcons.MEDAL, CERT_PLATE_DIAMETER), + lead); + } + + /** + * The achievements: the entry's own mark, drawn bare rather than on a + * plate, beside the title and what it was for. + */ + static void renderAchievements(PageFlowBuilder page, EntriesSection achievements) { + List entries = achievements.entries(); + page.addSection("Achievements", section -> { + section.spacing(0).padding(DocumentInsets.zero()); + sectionHeading(section, achievements.title(), CONTENT_WIDTH, true, 0.0, MARGIN); + columnBand(section, "Achievements", CONTENT_WIDTH, entries.size(), + HEADING_TO_ACHIEVEMENTS, ACHIEVEMENT_SEPARATOR_HEIGHT, + index -> achievementColumn(entries.get(index), index)); + }); + } + + private static BandColumn achievementColumn(CvEntry achievement, int index) { + double lead = index == 0 ? ACHIEVEMENT_GLYPH_HANG : GLYPH_CLEARANCE; + SectionBuilder column = new SectionBuilder(); + column.name("Achievement_" + compact(achievement.title())) + .spacing(0) + // The text drops a little, because the mark is taller than the + // title it stands beside and the two centre on each other. + .padding(new DocumentInsets( + ACHIEVEMENT_TEXT_DROP, 0, 0, lead + ACHIEVEMENT_GLYPH_TO_TEXT)); + column.addParagraph(p -> { + p.name("AchievementTitle_" + compact(achievement.title())) + .textStyle(style(ITEM_TITLE_SIZE, INK, DocumentTextDecoration.BOLD)) + .lineSpacing(TIGHT_LEADING); + SerifHeadlineText.title(p, achievement, + style(ITEM_TITLE_SIZE, INK, DocumentTextDecoration.BOLD)); + p.margin(new DocumentInsets(0, 0, ACHIEVEMENT_TITLE_TO_BODY, 0)); + }); + column.addParagraph(p -> p + .name("AchievementBody_" + compact(achievement.title())) + .text(achievement.body()) + .textStyle(small()) + .lineSpacing(ACHIEVEMENT_BODY_LEADING) + .margin(DocumentInsets.zero())); + DocumentNode mark = achievement.icon().isBlank() + ? null + : new ImageBuilder() + .name("AchievementGlyph_" + compact(achievement.title())) + .source(SerifHeadlineIcons.image(achievement.icon())) + .fitToBounds(ACHIEVEMENT_GLYPH_BOX, ACHIEVEMENT_GLYPH_BOX) + .margin(DocumentInsets.zero()) + .build(); + return new BandColumn(column, mark, lead); + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/SerifHeadlineIcons.java b/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/SerifHeadlineIcons.java new file mode 100644 index 000000000..7f05038a9 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/SerifHeadlineIcons.java @@ -0,0 +1,113 @@ +package com.demcha.compose.document.templates.cv.presets; + +import com.demcha.compose.document.image.DocumentImageData; + +import java.io.IOException; +import java.io.InputStream; +import java.io.UncheckedIOException; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Classpath loader for the Serif Headline CV marks — the five contact + * glyphs, the two project marks, the certification medal and the three + * achievement marks. + * + *

    They ship inside the templates artifact under + * {@code templates/cv/serif-headline/icons/} as PNG, each at the point size + * the design draws it at: a mark set inside a plate is drawn smaller than + * one standing on its own, and a contact glyph smaller again.

    + * + *

    The project and achievement marks are the vocabulary a document names + * through {@code CvEntry.icon()}. It is scoped to this preset — another + * preset packages its own set — so {@link #ENTRY_TOKENS} is the list a + * document can choose from here.

    + */ +final class SerifHeadlineIcons { + + static final String PHONE = "phone"; + static final String EMAIL = "email"; + static final String LOCATION = "location"; + static final String LINKEDIN = "linkedin"; + static final String GITHUB = "github"; + + /** The mark the certification plates always take; not data-driven. */ + static final String MEDAL = "medal"; + + private static final String ICON_ROOT = "/templates/cv/serif-headline/icons/"; + + private static final Map SIZES = Map.ofEntries( + Map.entry(PHONE, 8.7), + Map.entry(EMAIL, 8.7), + Map.entry(LOCATION, 9.6), + Map.entry(LINKEDIN, 8.7), + Map.entry(GITHUB, 9.0), + Map.entry("cart", 15.1), + Map.entry("api", 15.1), + Map.entry(MEDAL, 16.3), + Map.entry("trophy", 22.7), + Map.entry("chart", 22.7), + Map.entry("rocket", 22.7)); + + /** + * The tokens a document may name on an entry: the two project marks and + * the three achievement marks. + */ + static final Set ENTRY_TOKENS = Set.of("cart", "api", "trophy", "chart", "rocket"); + + private static final Map CACHE = new ConcurrentHashMap<>(); + + private SerifHeadlineIcons() { + } + + /** + * The drawn size of one mark. + * + * @param token one of the packaged tokens + * @return the size in points + * @throws IllegalArgumentException when the token names no packaged mark, + * listing the ones a document may choose — an icon token is data, + * so a wrong one is a data error + */ + static double size(String token) { + Double size = SIZES.get(token); + if (size == null) { + throw new IllegalArgumentException( + "Unknown serif headline CV icon token '" + token + + "'. This preset packages " + ENTRY_TOKENS.stream().sorted().toList() + + " for an entry mark."); + } + return size; + } + + /** + * Reads one mark, caching the decoded image per token so a sheet that + * repeats a mark reads the file once per JVM. + * + * @param token one of the packaged tokens + * @return the image data + */ + static DocumentImageData image(String token) { + // Validated here too, not only in size(): the marks drawn bare are + // scaled to their box rather than to their own size, so without this + // an unknown token would surface as a missing resource instead of as + // the data error it is. + size(token); + return CACHE.computeIfAbsent(token, SerifHeadlineIcons::read); + } + + private static DocumentImageData read(String token) { + String resourcePath = ICON_ROOT + token + ".png"; + try (InputStream input = SerifHeadlineIcons.class.getResourceAsStream(resourcePath)) { + if (input == null) { + throw new IllegalStateException( + "Missing serif headline CV icon: " + resourcePath); + } + return DocumentImageData.fromBytes(input.readAllBytes()); + } catch (IOException e) { + throw new UncheckedIOException( + "Failed to read serif headline CV icon: " + resourcePath, e); + } + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/SerifHeadlineMain.java b/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/SerifHeadlineMain.java new file mode 100644 index 000000000..cc32a700a --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/SerifHeadlineMain.java @@ -0,0 +1,289 @@ +package com.demcha.compose.document.templates.cv.presets; + +import com.demcha.compose.document.dsl.ParagraphBuilder; +import com.demcha.compose.document.dsl.SectionBuilder; +import com.demcha.compose.document.dsl.TimelineMarker; +import com.demcha.compose.document.dsl.TimelineRailEnd; +import com.demcha.compose.document.node.DocumentNode; +import com.demcha.compose.document.node.LayerAlign; +import com.demcha.compose.document.node.TextAlign; +import com.demcha.compose.document.style.ClipPolicy; +import com.demcha.compose.document.style.DocumentInsets; +import com.demcha.compose.document.style.DocumentStroke; +import com.demcha.compose.document.style.DocumentTextDecoration; +import com.demcha.compose.document.templates.cv.components.SectionLookup; +import com.demcha.compose.document.templates.cv.data.CvEntry; +import com.demcha.compose.document.templates.cv.data.EntriesSection; + +import java.util.List; + +import static com.demcha.compose.document.templates.cv.presets.SerifHeadlineStyles.ACCENT; +import static com.demcha.compose.document.templates.cv.presets.SerifHeadlineStyles.BULLET_ITEM_GAP; +import static com.demcha.compose.document.templates.cv.presets.SerifHeadlineStyles.BULLET_LEADING; +import static com.demcha.compose.document.templates.cv.presets.SerifHeadlineStyles.BULLETS_TO_SEPARATOR; +import static com.demcha.compose.document.templates.cv.presets.SerifHeadlineStyles.BULLET_MARKER; +import static com.demcha.compose.document.templates.cv.presets.SerifHeadlineStyles.BULLET_MARKER_GAP; +import static com.demcha.compose.document.templates.cv.presets.SerifHeadlineStyles.CARD_BODY_LEADING; +import static com.demcha.compose.document.templates.cv.presets.SerifHeadlineStyles.CARD_TECH_TO_BODY; +import static com.demcha.compose.document.templates.cv.presets.SerifHeadlineStyles.CARD_TITLE_TO_TECH; +import static com.demcha.compose.document.templates.cv.presets.SerifHeadlineStyles.DATE_OVERFLOW; +import static com.demcha.compose.document.templates.cv.presets.SerifHeadlineStyles.DIVIDER; +import static com.demcha.compose.document.templates.cv.presets.SerifHeadlineStyles.EMPLOYER_TO_BULLETS; +import static com.demcha.compose.document.templates.cv.presets.SerifHeadlineStyles.ENTRY_HEAD_BAND_HEIGHT; +import static com.demcha.compose.document.templates.cv.presets.SerifHeadlineStyles.ENTRY_TEXT_INSET; +import static com.demcha.compose.document.templates.cv.presets.SerifHeadlineStyles.ENTRY_WIDTH; +import static com.demcha.compose.document.templates.cv.presets.SerifHeadlineStyles.EXPERIENCE_TO_PROJECTS; +import static com.demcha.compose.document.templates.cv.presets.SerifHeadlineStyles.GLYPH_CLEARANCE; +import static com.demcha.compose.document.templates.cv.presets.SerifHeadlineStyles.HAIRLINE_THICKNESS; +import static com.demcha.compose.document.templates.cv.presets.SerifHeadlineStyles.HALF_GUTTER; +import static com.demcha.compose.document.templates.cv.presets.SerifHeadlineStyles.HEADING_TO_CARDS; +import static com.demcha.compose.document.templates.cv.presets.SerifHeadlineStyles.HEADING_TO_ENTRY; +import static com.demcha.compose.document.templates.cv.presets.SerifHeadlineStyles.INK; +import static com.demcha.compose.document.templates.cv.presets.SerifHeadlineStyles.ITEM_TITLE_SIZE; +import static com.demcha.compose.document.templates.cv.presets.SerifHeadlineStyles.JOB_TITLE_SIZE; +import static com.demcha.compose.document.templates.cv.presets.SerifHeadlineStyles.MAIN_WIDTH; +import static com.demcha.compose.document.templates.cv.presets.SerifHeadlineStyles.MARGIN; +import static com.demcha.compose.document.templates.cv.presets.SerifHeadlineStyles.MARKER_DIAMETER; +import static com.demcha.compose.document.templates.cv.presets.SerifHeadlineStyles.PLATE_HANG; +import static com.demcha.compose.document.templates.cv.presets.SerifHeadlineStyles.PROJECT_GLYPH_TO_TEXT; +import static com.demcha.compose.document.templates.cv.presets.SerifHeadlineStyles.PROJECT_PLATE_DIAMETER; +import static com.demcha.compose.document.templates.cv.presets.SerifHeadlineStyles.PROJECT_SEPARATOR_HEIGHT; +import static com.demcha.compose.document.templates.cv.presets.SerifHeadlineStyles.RAIL_MARGIN_LEFT; +import static com.demcha.compose.document.templates.cv.presets.SerifHeadlineStyles.RAIL_THICKNESS; +import static com.demcha.compose.document.templates.cv.presets.SerifHeadlineStyles.RULE; +import static com.demcha.compose.document.templates.cv.presets.SerifHeadlineStyles.SEPARATOR_TO_ENTRY; +import static com.demcha.compose.document.templates.cv.presets.SerifHeadlineStyles.SMALL_SIZE; +import static com.demcha.compose.document.templates.cv.presets.SerifHeadlineStyles.TIGHT_LEADING; +import static com.demcha.compose.document.templates.cv.presets.SerifHeadlineStyles.TITLE_OVERFLOW; +import static com.demcha.compose.document.templates.cv.presets.SerifHeadlineStyles.TITLE_TO_EMPLOYER; +import static com.demcha.compose.document.templates.cv.presets.SerifHeadlineStyles.body; +import static com.demcha.compose.document.templates.cv.presets.SerifHeadlineStyles.compact; +import static com.demcha.compose.document.templates.cv.presets.SerifHeadlineStyles.small; +import static com.demcha.compose.document.templates.cv.presets.SerifHeadlineStyles.style; +import static com.demcha.compose.document.templates.cv.presets.SerifHeadlineWidgets.BandColumn; +import static com.demcha.compose.document.templates.cv.presets.SerifHeadlineWidgets.columnBand; +import static com.demcha.compose.document.templates.cv.presets.SerifHeadlineWidgets.plate; +import static com.demcha.compose.document.templates.cv.presets.SerifHeadlineWidgets.sectionHeading; + +/** + * The wide left column: the roles held on a timeline rail, and the projects + * as a band of marked cards beneath them. + */ +final class SerifHeadlineMain { + + private SerifHeadlineMain() { + } + + static void compose(SectionBuilder section, + EntriesSection experience, + EntriesSection projects) { + section.spacing(0).padding(new DocumentInsets(0, HALF_GUTTER, 0, 0)); + if (SectionLookup.hasContent(experience)) { + sectionHeading(section, experience.title(), MAIN_WIDTH, false, 0.0, MARGIN); + renderExperience(section, experience); + } + if (SectionLookup.hasContent(projects)) { + sectionHeading(section, projects.title(), MAIN_WIDTH, true, + EXPERIENCE_TO_PROJECTS, MARGIN); + renderProjects(section, projects); + } + } + + // -- experience ------------------------------------------------------ + + /** + * The roles held, strung on a vertical rail with a filled marker at each + * one. + * + *

    The rail stops at the last marker, which is what {@code MARKER_TO_MARKER} means. It + * used to be the section's left accent, and an accent runs the full height of what its + * section holds — so the last role's body was composed in a second section outside the + * rail to keep the line from running on to the foot of the block. The extent says it + * directly now, and that sibling section is gone.

    + * + *

    Wrapped in a layer because a row nested in a row cell is refused and this sheet's + * main column is one.

    + */ + private static void renderExperience(SectionBuilder section, EntriesSection experience) { + List entries = experience.entries(); + int last = entries.size() - 1; + SectionBuilder holder = new SectionBuilder(); + holder.name("ExperienceRailHolder"); + holder.spacing(0).padding(DocumentInsets.zero()); + // The axis column centres the rail on itself, so the timeline starts half a marker + // further left than the accent did and the line lands back where it was. + holder.margin(new DocumentInsets(HEADING_TO_ENTRY + TITLE_OVERFLOW, 0, 0, + RAIL_MARGIN_LEFT - MARKER_DIAMETER / 2.0)); + holder.addTimeline(timeline -> { + timeline.markerOnRail() + .rail(rail -> rail + .stroke(DocumentStroke.of(DIVIDER, RAIL_THICKNESS)) + .from(TimelineRailEnd.MARKER).to(TimelineRailEnd.MARKER)) + .axisWidth(MARKER_DIAMETER) + .markerGap(ENTRY_TEXT_INSET - MARKER_DIAMETER / 2.0) + .gutter(0) + // The rhythm between roles is the separator's own margins, as before. + .spacing(0); + for (int i = 0; i < entries.size(); i++) { + CvEntry entry = entries.get(i); + boolean separated = i < last; + timeline.entry(entryMarker(entry), e -> e.content(body -> { + body.spacing(0); + renderEntryHead(body, entry); + renderEntryBody(body, entry); + if (separated) { + renderEntrySeparator(body, entry); + } + })); + } + }); + DocumentNode node = holder.build(); + // The timeline goes in through a layer stack rather than straight into the + // section. A timeline publishes a row, a row cannot nest inside a row cell, + // and this section can sit in one — the stack is what insulates it. + // Unwinding the stacks that turn out not to need it buys no capability, so + // they stay until that is done as its own deliberate cleanup. + section.addLayerStack(stack -> stack + .name("ExperienceRail") + .layer(node, LayerAlign.TOP_LEFT, 0)); + } + + /** The filled disc that marks a role, in a box as tall as the head band it rides. */ + private static TimelineMarker entryMarker(CvEntry entry) { + return TimelineMarker.custom(MARKER_DIAMETER, ENTRY_HEAD_BAND_HEIGHT, + column -> column.addEllipse(ellipse -> ellipse + .name("Marker_" + compact(entry.title())) + .circle(MARKER_DIAMETER) + .fillColor(INK) + .margin(DocumentInsets.zero()))); + } + + /** + * The marker, the position and the dates, all on the marker's axis: the + * band is as tall as the marker, and the two pieces of type are pulled up + * by half their own overhang to centre on it. + */ + private static void renderEntryHead(SectionBuilder rail, CvEntry entry) { + ParagraphBuilder titleText = new ParagraphBuilder() + .name("JobTitle_" + compact(entry.title())) + .textStyle(style(JOB_TITLE_SIZE, INK, DocumentTextDecoration.BOLD)) + .lineSpacing(TIGHT_LEADING); + SerifHeadlineText.title(titleText, entry, + style(JOB_TITLE_SIZE, INK, DocumentTextDecoration.BOLD)); + DocumentNode title = titleText.margin(DocumentInsets.zero()).build(); + DocumentNode dates = new ParagraphBuilder() + .name("JobDates_" + compact(entry.title())) + .text(entry.date()) + .textStyle(body()) + .align(TextAlign.RIGHT) + .lineSpacing(TIGHT_LEADING) + .margin(DocumentInsets.zero()) + .build(); + // The marker has left the band for the timeline's axis column, so the band starts at + // the content column and the title no longer walks in past the rail. + rail.addContainer(head -> head + .name("EntryHead_" + compact(entry.title())) + .rectangle(ENTRY_WIDTH - ENTRY_TEXT_INSET, ENTRY_HEAD_BAND_HEIGHT) + .clipPolicy(ClipPolicy.OVERFLOW_VISIBLE) + .padding(DocumentInsets.zero()) + .position(title, 0, -TITLE_OVERFLOW, LayerAlign.CENTER_LEFT) + .position(dates, 0, -DATE_OVERFLOW, LayerAlign.CENTER_RIGHT)); + } + + /** + * The employer in accent and the location in body, held apart by a pale + * pipe — three runs in one line, which is why the location is its own + * field rather than part of the subtitle. + */ + private static void renderEntryBody(SectionBuilder rail, CvEntry entry) { + rail.addParagraph(p -> { + p.name("Employer_" + compact(entry.title())) + .textStyle(small()) + .inlineText(entry.subtitle(), + style(SMALL_SIZE, ACCENT, DocumentTextDecoration.DEFAULT)); + if (!entry.place().isBlank()) { + p.inlineText(" | ", style(SMALL_SIZE, RULE, DocumentTextDecoration.DEFAULT)) + .inlineText(entry.place(), small()); + } + p.lineSpacing(TIGHT_LEADING) + .margin(new DocumentInsets( + TITLE_TO_EMPLOYER + TITLE_OVERFLOW, 0, + EMPLOYER_TO_BULLETS, 0)); + }); + List highlights = SerifHeadlineText.lines(entry.body()); + if (!highlights.isEmpty()) { + // The gap after the dot is a measurement, so a highlight that wraps + // resumes on its own text instead of the marker column. + rail.addList(list -> list + .name("Highlights_" + compact(entry.title())) + .marker(BULLET_MARKER) + .markerGap(BULLET_MARKER_GAP) + .hangingIndent(true) + .items(highlights) + .textStyle(body()) + .lineSpacing(BULLET_LEADING) + .itemSpacing(BULLET_ITEM_GAP) + .margin(DocumentInsets.zero())); + } + } + + private static void renderEntrySeparator(SectionBuilder rail, CvEntry entry) { + rail.addLine(line -> line + .name("EntrySeparator_" + compact(entry.title())) + .horizontal(ENTRY_WIDTH - ENTRY_TEXT_INSET) + .thickness(HAIRLINE_THICKNESS) + .color(RULE) + .margin(new DocumentInsets( + BULLETS_TO_SEPARATOR, 0, + SEPARATOR_TO_ENTRY + TITLE_OVERFLOW, 0))); + } + + // -- projects -------------------------------------------------------- + + private static void renderProjects(SectionBuilder section, EntriesSection projects) { + List entries = projects.entries(); + columnBand(section, "Projects", MAIN_WIDTH, entries.size(), + HEADING_TO_CARDS, PROJECT_SEPARATOR_HEIGHT, + index -> projectColumn(entries.get(index), index)); + } + + /** + * One project card: the plate with the entry's mark, then the title, the + * stack it was built on, and what it does. + * + *

    The first plate hangs a little left of the band so it lines up with + * the marks above it; the rest clear their column edge instead.

    + */ + private static BandColumn projectColumn(CvEntry project, int index) { + double lead = index == 0 ? PLATE_HANG : GLYPH_CLEARANCE; + SectionBuilder column = new SectionBuilder(); + column.name("ProjectCard_" + compact(project.title())) + .spacing(0) + .padding(new DocumentInsets(0, 0, 0, lead + PROJECT_GLYPH_TO_TEXT)); + column.addParagraph(p -> { + p.name("ProjectTitle_" + compact(project.title())) + .textStyle(style(ITEM_TITLE_SIZE, INK, DocumentTextDecoration.BOLD)) + .lineSpacing(TIGHT_LEADING); + SerifHeadlineText.title(p, project, + style(ITEM_TITLE_SIZE, INK, DocumentTextDecoration.BOLD)); + p.margin(new DocumentInsets(0, 0, CARD_TITLE_TO_TECH, 0)); + }); + column.addParagraph(p -> p + .name("ProjectStack_" + compact(project.title())) + .text(project.subtitle()) + .textStyle(style(SMALL_SIZE, ACCENT, DocumentTextDecoration.ITALIC)) + .lineSpacing(TIGHT_LEADING) + .margin(new DocumentInsets(0, 0, CARD_TECH_TO_BODY, 0))); + column.addParagraph(p -> p + .name("ProjectBody_" + compact(project.title())) + .text(project.body()) + .textStyle(small()) + .lineSpacing(CARD_BODY_LEADING) + .margin(DocumentInsets.zero())); + DocumentNode mark = project.icon().isBlank() + ? null + : plate("ProjectPlate_" + compact(project.title()), + project.icon(), PROJECT_PLATE_DIAMETER); + return new BandColumn(column, mark, lead); + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/SerifHeadlineMasthead.java b/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/SerifHeadlineMasthead.java new file mode 100644 index 000000000..309f4971d --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/SerifHeadlineMasthead.java @@ -0,0 +1,221 @@ +package com.demcha.compose.document.templates.cv.presets; + +import com.demcha.compose.document.dsl.ImageBuilder; +import com.demcha.compose.document.dsl.PageFlowBuilder; +import com.demcha.compose.document.dsl.ParagraphBuilder; +import com.demcha.compose.document.dsl.SectionBuilder; +import com.demcha.compose.document.node.DocumentLinkOptions; +import com.demcha.compose.document.node.DocumentNode; +import com.demcha.compose.document.node.LayerAlign; +import com.demcha.compose.document.node.RowVerticalAlign; +import com.demcha.compose.document.node.TextAlign; +import com.demcha.compose.document.style.ClipPolicy; +import com.demcha.compose.document.style.DocumentInsets; +import com.demcha.compose.document.style.DocumentTextDecoration; +import com.demcha.compose.document.style.DocumentTextStyle; +import com.demcha.compose.document.templates.core.identity.ContactUri; +import com.demcha.compose.document.templates.core.identity.Link; +import com.demcha.compose.document.templates.core.text.TextStyles; +import com.demcha.compose.document.templates.cv.components.SectionLookup; +import com.demcha.compose.document.templates.cv.data.CvIdentity; +import com.demcha.compose.document.templates.cv.data.ParagraphSection; + +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; + +import static com.demcha.compose.document.templates.cv.presets.SerifHeadlineStyles.BODY_LEADING; +import static com.demcha.compose.document.templates.cv.presets.SerifHeadlineStyles.CONTACT_GLYPH_BOX; +import static com.demcha.compose.document.templates.cv.presets.SerifHeadlineStyles.CONTACT_ROW_GAP; +import static com.demcha.compose.document.templates.cv.presets.SerifHeadlineStyles.CONTACT_ROW_HEIGHT; +import static com.demcha.compose.document.templates.cv.presets.SerifHeadlineStyles.CONTACT_TEXT_INSET; +import static com.demcha.compose.document.templates.cv.presets.SerifHeadlineStyles.CONTACT_WIDTH; +import static com.demcha.compose.document.templates.cv.presets.SerifHeadlineStyles.CONTENT_WIDTH; +import static com.demcha.compose.document.templates.cv.presets.SerifHeadlineStyles.DISPLAY_FONT; +import static com.demcha.compose.document.templates.cv.presets.SerifHeadlineStyles.DISPLAY_LEADING; +import static com.demcha.compose.document.templates.cv.presets.SerifHeadlineStyles.GOLD; +import static com.demcha.compose.document.templates.cv.presets.SerifHeadlineStyles.GOLD_RULE_THICKNESS; +import static com.demcha.compose.document.templates.cv.presets.SerifHeadlineStyles.GOLD_RULE_WIDTH; +import static com.demcha.compose.document.templates.cv.presets.SerifHeadlineStyles.IDENTITY_WEIGHT; +import static com.demcha.compose.document.templates.cv.presets.SerifHeadlineStyles.INK; +import static com.demcha.compose.document.templates.cv.presets.SerifHeadlineStyles.MASTHEAD_TO_SUMMARY; +import static com.demcha.compose.document.templates.cv.presets.SerifHeadlineStyles.NAME_SIZE; +import static com.demcha.compose.document.templates.cv.presets.SerifHeadlineStyles.NAME_TO_ROLE; +import static com.demcha.compose.document.templates.cv.presets.SerifHeadlineStyles.ROLE_SIZE; +import static com.demcha.compose.document.templates.cv.presets.SerifHeadlineStyles.ROLE_TO_GOLD_RULE; +import static com.demcha.compose.document.templates.cv.presets.SerifHeadlineStyles.RULE; +import static com.demcha.compose.document.templates.cv.presets.SerifHeadlineStyles.RULE_THICKNESS; +import static com.demcha.compose.document.templates.cv.presets.SerifHeadlineStyles.RULE_TO_BODY; +import static com.demcha.compose.document.templates.cv.presets.SerifHeadlineStyles.SUMMARY_LEADING; +import static com.demcha.compose.document.templates.cv.presets.SerifHeadlineStyles.SUMMARY_RIGHT_INSET; +import static com.demcha.compose.document.templates.cv.presets.SerifHeadlineStyles.SUMMARY_TO_RULE; +import static com.demcha.compose.document.templates.cv.presets.SerifHeadlineStyles.TIGHT_LEADING; +import static com.demcha.compose.document.templates.cv.presets.SerifHeadlineStyles.body; +import static com.demcha.compose.document.templates.cv.presets.SerifHeadlineStyles.style; +import static com.demcha.compose.document.templates.cv.presets.SerifHeadlineStyles.tracked; + +/** + * The top of the sheet: the serif name with its role and gold rule, the + * contact channels stacked at the right, the summary beneath, and the rule + * that closes the block. + */ +final class SerifHeadlineMasthead { + + private SerifHeadlineMasthead() { + } + + static void compose(PageFlowBuilder page, CvIdentity identity, ParagraphSection summary) { + page.addRow("Masthead", row -> { + row.spacing(0); + row.gap(0); + row.weights(IDENTITY_WEIGHT, 1.0 - IDENTITY_WEIGHT); + row.verticalAlign(RowVerticalAlign.TOP); + row.margin(new DocumentInsets(0, 0, MASTHEAD_TO_SUMMARY, 0)); + row.addSection("Identity", section -> renderIdentity(section, identity)); + row.addSection("Contact", section -> renderContact(section, identity)); + }); + if (SectionLookup.hasContent(summary)) { + renderSummary(page, summary); + } + page.addLine(line -> line + .name("MastheadRule") + .horizontal(CONTENT_WIDTH) + .thickness(RULE_THICKNESS) + .color(RULE) + .margin(new DocumentInsets(0, 0, RULE_TO_BODY, 0))); + } + + // -- identity -------------------------------------------------------- + + /** + * The name in the display serif, the role in tracked capitals, and the + * short gold rule that closes them. + */ + private static void renderIdentity(SectionBuilder section, CvIdentity identity) { + section.spacing(0).padding(DocumentInsets.zero()); + + DocumentTextStyle nameStyle = TextStyles.of(DISPLAY_FONT, NAME_SIZE, + DocumentTextDecoration.DEFAULT, INK); + section.addParagraph(p -> p + .name("Name") + .text(identity.name().full().toUpperCase(Locale.ROOT)) + .textStyle(nameStyle) + .align(TextAlign.LEFT) + .lineSpacing(DISPLAY_LEADING) + .margin(new DocumentInsets(0, 0, NAME_TO_ROLE, 0))); + + DocumentTextStyle roleStyle = style(ROLE_SIZE, INK, DocumentTextDecoration.DEFAULT); + section.addParagraph(p -> { + p.name("Role"); + p.textStyle(roleStyle); + tracked(p, identity.jobTitle().toUpperCase(Locale.ROOT), roleStyle); + p.align(TextAlign.LEFT); + p.lineSpacing(TIGHT_LEADING); + p.margin(new DocumentInsets(0, 0, ROLE_TO_GOLD_RULE, 0)); + }); + + section.addLine(line -> line + .name("GoldRule") + .horizontal(GOLD_RULE_WIDTH) + .thickness(GOLD_RULE_THICKNESS) + .color(GOLD) + .margin(DocumentInsets.zero())); + } + + // -- contact --------------------------------------------------------- + + /** + * The contact channels, in the order the design sets them: phone, email, + * address, then whatever links the identity carries. + * + *

    Each row is a band with the mark anchored at the left and the value + * at a fixed inset, so the values line up whatever their marks measure — + * and, unlike a single paragraph, a long value wraps under itself rather + * than under the mark.

    + * + *

    The phone and the email carry {@code tel:} and {@code mailto:} + * targets built from the values, and each link its own url.

    + */ + private static void renderContact(SectionBuilder section, CvIdentity identity) { + section.spacing(CONTACT_ROW_GAP).padding(DocumentInsets.zero()); + for (Channel channel : channels(identity)) { + renderChannel(section, channel); + } + } + + private static void renderChannel(SectionBuilder section, Channel channel) { + DocumentNode glyph = new ImageBuilder() + .name("ContactGlyph_" + channel.token()) + .source(SerifHeadlineIcons.image(channel.token())) + .fitToBounds(CONTACT_GLYPH_BOX, CONTACT_GLYPH_BOX) + .margin(DocumentInsets.zero()) + .build(); + + DocumentLinkOptions link = channel.href() == null + ? null + : new DocumentLinkOptions(channel.href()); + ParagraphBuilder valueText = new ParagraphBuilder() + .name("ContactValue_" + channel.token()) + .textStyle(body()) + .lineSpacing(BODY_LEADING); + if (link == null) { + valueText.inlineText(channel.value(), body()); + } else { + valueText.inlineText(channel.value(), body(), link); + } + DocumentNode value = valueText.margin(DocumentInsets.zero()).build(); + + section.addContainer(band -> band + .name("ContactRow_" + channel.token()) + .rectangle(CONTACT_WIDTH, CONTACT_ROW_HEIGHT) + .clipPolicy(ClipPolicy.OVERFLOW_VISIBLE) + .padding(DocumentInsets.zero()) + .position(glyph, 0, 0, LayerAlign.CENTER_LEFT) + .position(value, CONTACT_TEXT_INSET, 0, LayerAlign.CENTER_LEFT)); + } + + private static List channels(CvIdentity identity) { + List channels = new ArrayList<>(); + String phone = identity.contact().phone(); + channels.add(new Channel(SerifHeadlineIcons.PHONE, phone, ContactUri.tel(phone))); + String email = identity.contact().email(); + channels.add(new Channel(SerifHeadlineIcons.EMAIL, email, "mailto:" + email)); + channels.add(new Channel(SerifHeadlineIcons.LOCATION, + identity.contact().address(), null)); + for (Link link : identity.links()) { + channels.add(new Channel(linkToken(link), link.label(), link.url())); + } + return channels; + } + + /** + * The mark for a link. This design packages the two networks it shows; + * anything else takes the LinkedIn mark, because there is no globe in + * the set to fall back to. + */ + private static String linkToken(Link link) { + String haystack = SectionLookup.normalize(link.label() + " " + link.url()); + return haystack.contains("github") + ? SerifHeadlineIcons.GITHUB + : SerifHeadlineIcons.LINKEDIN; + } + + private record Channel(String token, String value, String href) { + } + + // -- summary --------------------------------------------------------- + + /** + * The opening paragraph. It stops at the column divider rather than + * running the width of the page, and this design gives it no heading, so + * the section's title is not drawn. + */ + private static void renderSummary(PageFlowBuilder page, ParagraphSection summary) { + page.addParagraph(p -> p + .name("Summary") + .text(summary.body()) + .textStyle(body()) + .lineSpacing(SUMMARY_LEADING) + .margin(new DocumentInsets(0, SUMMARY_RIGHT_INSET, SUMMARY_TO_RULE, 0))); + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/SerifHeadlineStyles.java b/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/SerifHeadlineStyles.java new file mode 100644 index 000000000..1d06ab064 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/SerifHeadlineStyles.java @@ -0,0 +1,452 @@ +package com.demcha.compose.document.templates.cv.presets; + +import com.demcha.compose.document.api.DocumentPageSize; +import com.demcha.compose.document.dsl.ParagraphBuilder; +import com.demcha.compose.document.style.DocumentColor; +import com.demcha.compose.document.style.DocumentInsets; +import com.demcha.compose.document.style.DocumentTextDecoration; +import com.demcha.compose.document.style.DocumentTextStyle; +import com.demcha.compose.document.templates.core.text.TextStyles; +import com.demcha.compose.font.FontName; + +/** + * The measured geometry, palette and type scale of the Serif Headline CV. + * + *

    Unlike the other ported sheets, almost nothing here is a literal. The + * design was drawn on a 1024 x 1536 pixel grid, so every distance is that + * grid's number put through {@link #h} or {@link #v}, and every vertical gap + * is stated as the ink distance the drawing shows — the white + * between a descender and the next cap — with {@link #gap} subtracting the + * blank a line box carries above and below its letters. Copying the pixel + * numbers straight in would have set every block a few points too far apart, + * because a line box is taller than the letters in it.

    + * + *

    The vertical scale carries a compression factor of its own: the drawing + * is proportionally taller than A4, so heights are scaled by + * {@link #V_COMPRESS} on top of the width scale to bring the sheet back onto + * the page.

    + */ +final class SerifHeadlineStyles { + + private SerifHeadlineStyles() { + } + + // -- the reference grid ---------------------------------------------- + + static final DocumentPageSize PAGE = DocumentPageSize.A4; + + /** The width of the grid the design was drawn on, in pixels. */ + static final double REFERENCE_WIDTH_PX = 1024.0; + + static final double H_SCALE = PAGE.width() / REFERENCE_WIDTH_PX; + + /** How much shorter the page is than the drawing, proportionally. */ + static final double V_COMPRESS = 0.8450; + static final double V_SCALE = H_SCALE * V_COMPRESS; + + /** A horizontal distance from the drawing, in points. */ + static double h(double px) { + return px * H_SCALE; + } + + /** A vertical distance from the drawing, in points. */ + static double v(double px) { + return px * V_SCALE; + } + + // -- type metrics ---------------------------------------------------- + + /** Cap height as a fraction of type size, for the text face. */ + static final double CAP_EM = 0.72; + static final double ASCENDER_EM = 0.80; + static final double DESCENDER_EM = 0.21; + + /** Cap height as a fraction of type size, for the display face. */ + static final double DISPLAY_CAP_EM = 0.766; + + /** How much narrower the small face runs than the body face. */ + static final double ADVANCE_MATCH = 0.95; + + /** + * The blank a line box leaves under its letters: the descender space, + * which no glyph in a line of capitals or of ordinary lowercase reaches. + */ + static double blankBelow(double size) { + return size * DESCENDER_EM; + } + + /** + * The blank a line box leaves above its letters: the leading it adds, + * plus the ascender space above the cap line. + */ + static double blankAbove(double size, double leading) { + return size * ((leading - 1.0) + (ASCENDER_EM - CAP_EM)); + } + + /** + * A vertical gap authored as the ink distance in the drawing, less the + * blank the two line boxes on either side already contribute. + * + * @param inkPx the white space the drawing shows, in grid pixels + * @param blankAboveTheGap blank the box above already leaves below itself + * @param blankBelowTheGap blank the box below already leaves above itself + * @return the margin to author, never negative + */ + static double gap(double inkPx, double blankAboveTheGap, double blankBelowTheGap) { + return Math.max(0.0, v(inkPx) - blankAboveTheGap - blankBelowTheGap); + } + + // -- the column grid ------------------------------------------------- + + static final double MARGIN = h(54.5); + static final double CONTENT_WIDTH = PAGE.width() - 2.0 * MARGIN; + + static final double MAIN_WEIGHT = 0.6448; + static final double GUTTER_WEIGHT = 0.0721; + static final double ASIDE_WEIGHT = 1.0 - MAIN_WEIGHT - GUTTER_WEIGHT; + + /** The row splits on the gutter's midline, where the divider runs. */ + static final double MAIN_CELL_WEIGHT = MAIN_WEIGHT + GUTTER_WEIGHT / 2.0; + static final double ASIDE_CELL_WEIGHT = 1.0 - MAIN_CELL_WEIGHT; + + static final double MAIN_WIDTH = CONTENT_WIDTH * MAIN_WEIGHT; + static final double GUTTER = CONTENT_WIDTH * GUTTER_WEIGHT; + static final double ASIDE_WIDTH = CONTENT_WIDTH * ASIDE_WEIGHT; + static final double HALF_GUTTER = GUTTER / 2.0; + + static final double IDENTITY_WEIGHT = 0.7803; + static final double CONTACT_WEIGHT = 1.0 - IDENTITY_WEIGHT; + static final double CONTACT_WIDTH = CONTENT_WIDTH * CONTACT_WEIGHT; + + /** The summary stops at the divider rather than running the page width. */ + static final double SUMMARY_WIDTH = CONTENT_WIDTH * (MAIN_WEIGHT + GUTTER_WEIGHT); + static final double SUMMARY_RIGHT_INSET = CONTENT_WIDTH - SUMMARY_WIDTH; + + // -- type ------------------------------------------------------------ + + static final FontName DISPLAY_FONT = FontName.VOLKHOV; + static final FontName TEXT_FONT = FontName.LATO; + + static final double BODY_SIZE = h(10.0) / CAP_EM; + static final double SMALL_SIZE = h(9.0) / CAP_EM * ADVANCE_MATCH; + static final double HEADING_SIZE = h(12.0) / CAP_EM; + static final double JOB_TITLE_SIZE = HEADING_SIZE; + static final double ITEM_TITLE_SIZE = BODY_SIZE; + static final double ROLE_SIZE = h(16.0) / CAP_EM; + static final double NAME_SIZE = h(44.0) / DISPLAY_CAP_EM; + + static final double BODY_LEADING = 1.34; + static final double SUMMARY_LEADING = 1.37; + static final double TIGHT_LEADING = 1.30; + static final double DISPLAY_LEADING = 1.0; + + static final double TRACKING_EM = 0.17; + static final double SPACE_ADVANCE_EM = 0.25; + + /** The marker: the dot, with the gap after it authored as {@link #BULLET_MARKER_GAP}. */ + static final String BULLET_MARKER = "•"; + + /** + * The gap between the dot and its text — the body face's own space advance. + * + *

    2.067pt at {@link #BODY_SIZE}, solved against the frozen render rather + * than assumed. The marker used to be written {@code "• "}, but a list + * marker's authored trailing whitespace is stripped and a single space + * re-appended, so the page has only ever shown one space of gap; setting + * that one space as a real measurement puts the text back where it was and + * brings the wrapped lines it governs onto it, instead of 1.518pt past.

    + * + *

    Not {@link #SPACE_ADVANCE_EM}: that 0.25 em is a rounding kept for the + * tracking spacer, and it is 0.049pt short of this face's real advance.

    + */ + static final double BULLET_MARKER_GAP = 0.256 * BODY_SIZE; + + static final double BLANK_BODY = blankBelow(BODY_SIZE); + static final double BLANK_SMALL = blankBelow(SMALL_SIZE); + static final double BLANK_TITLE = blankBelow(ITEM_TITLE_SIZE); + static final double BLANK_CAPS_BELOW_HEADING = blankBelow(HEADING_SIZE); + static final double BLANK_CAPS_ABOVE_HEADING = blankAbove(HEADING_SIZE, TIGHT_LEADING); + + // -- palette --------------------------------------------------------- + + static final DocumentColor INK = DocumentColor.rgb(26, 38, 62); + static final DocumentColor ACCENT = DocumentColor.rgb(61, 81, 147); + static final DocumentColor BODY = DocumentColor.rgb(72, 76, 89); + static final DocumentColor GOLD = DocumentColor.rgb(165, 117, 42); + static final DocumentColor RULE = DocumentColor.rgb(229, 230, 233); + static final DocumentColor DIVIDER = DocumentColor.rgb(222, 224, 228); + static final DocumentColor TRACK = DocumentColor.rgb(224, 226, 231); + static final DocumentColor PLATE = DocumentColor.rgb(243, 244, 246); + static final DocumentColor STUB_RULE = DocumentColor.rgb(181, 185, 196); + + // -- masthead -------------------------------------------------------- + + static final double NAME_TO_ROLE = gap(25, + blankBelow(NAME_SIZE), blankAbove(ROLE_SIZE, TIGHT_LEADING)); + static final double ROLE_TO_GOLD_RULE = gap(23, blankBelow(ROLE_SIZE), 0.0); + static final double GOLD_RULE_WIDTH = h(79); + static final double GOLD_RULE_THICKNESS = h(3); + + static final double CONTACT_ROW_HEIGHT = BODY_SIZE * BODY_LEADING; + static final double CONTACT_ROW_GAP = Math.max(0.0, v(29.25) - CONTACT_ROW_HEIGHT); + static final double CONTACT_TEXT_INSET = h(32); + static final double CONTACT_GLYPH_BOX = h(16); + + static final double MASTHEAD_TO_SUMMARY = + gap(31, 0.0, blankAbove(BODY_SIZE, SUMMARY_LEADING)); + static final double SUMMARY_TO_RULE = gap(30, BLANK_BODY, 0.0); + static final double RULE_TO_BODY = gap(35, 0.0, BLANK_CAPS_ABOVE_HEADING); + static final double RULE_THICKNESS = h(1.5); + static final double HAIRLINE_THICKNESS = h(1.2); + + // -- section headings ------------------------------------------------ + + static final double DASH_CLEARANCE = h(25); + static final double DASH_WIDTH = h(22); + static final double DASH_THICKNESS = h(3); + static final double DASH_TO_TITLE = h(16); + static final double TAIL_GAP = h(19); + + /** + * Where the heading dash starts, measured from the left edge of whatever + * surface it is drawn on — the page for a full-width band, the column for + * one inside the grid. + */ + static double dashOffset(double leftSurfaceDistance) { + return DASH_CLEARANCE - leftSurfaceDistance; + } + + /** Where the heading's letters start, on the same measure. */ + static double titleOffset(double leftSurfaceDistance) { + return dashOffset(leftSurfaceDistance) + DASH_WIDTH + DASH_TO_TITLE; + } + + static final double SECTION_TEXT_INSET = titleOffset(MARGIN); + + // -- experience ------------------------------------------------------ + + static final double RAIL_THICKNESS = h(1.2); + static final double RAIL_MARGIN_LEFT = SECTION_TEXT_INSET - RAIL_THICKNESS / 2.0; + static final double ENTRY_WIDTH = MAIN_WIDTH - RAIL_MARGIN_LEFT; + static final double ENTRY_TEXT_INSET = h(104 - 54) - RAIL_MARGIN_LEFT; + static final double MARKER_DIAMETER = h(11); + static final double ENTRY_HEAD_BAND_HEIGHT = MARKER_DIAMETER; + + /** How far the job title rises above the marker band it is set on. */ + static final double TITLE_OVERFLOW = + (JOB_TITLE_SIZE * TIGHT_LEADING - ENTRY_HEAD_BAND_HEIGHT) / 2.0; + + /** The same, for the dates set at the other end of that band. */ + static final double DATE_OVERFLOW = + (BODY_SIZE * TIGHT_LEADING - ENTRY_HEAD_BAND_HEIGHT) / 2.0; + + static final double HEADING_TO_ENTRY = gap(36, BLANK_CAPS_BELOW_HEADING, 0.0); + static final double TITLE_TO_EMPLOYER = gap(17, 0.0, BLANK_SMALL); + static final double EMPLOYER_TO_BULLETS = gap(23, BLANK_SMALL, BLANK_BODY); + static final double BULLET_LEADING = v(20) / BODY_SIZE; + static final double BULLET_ITEM_GAP = + Math.max(0.0, v(25) - BODY_SIZE * BULLET_LEADING); + static final double BULLETS_TO_SEPARATOR = gap(35, BLANK_BODY, 0.0); + static final double SEPARATOR_TO_ENTRY = gap(32, 0.0, 0.0); + + // -- projects -------------------------------------------------------- + + static final double EXPERIENCE_TO_PROJECTS = + gap(59, BLANK_BODY, BLANK_CAPS_ABOVE_HEADING); + static final double HEADING_TO_CARDS = gap(33, BLANK_CAPS_BELOW_HEADING, 0.0); + static final double PROJECT_PLATE_DIAMETER = h(47); + static final double PROJECT_GLYPH_TO_TEXT = h(61); + + /** The plate hangs a little left of its column, into the gutter. */ + static final double PLATE_HANG = h(-4); + static final double GLYPH_CLEARANCE = h(10); + + /** + * The space a band column leaves at its right edge, so its text stops + * short of the hairline rather than running into it. + * + *

    This is the one measure on the sheet that is not the published + * design's: there, a column's text runs to the separator, and a line + * that happens to fill the column touches it. The gutter matches + * {@link #GLYPH_CLEARANCE}, the space the next column's mark already + * keeps on the other side, so the rule sits centred in white.

    + */ + static final double COLUMN_TAIL_GUTTER = GLYPH_CLEARANCE; + static final double CARD_TITLE_TO_TECH = gap(15, BLANK_TITLE, BLANK_SMALL); + static final double CARD_TECH_TO_BODY = gap(12, BLANK_SMALL, BLANK_SMALL); + static final double CARD_BODY_LEADING = v(18.5) / SMALL_SIZE; + static final double PROJECT_SEPARATOR_HEIGHT = v(98); + + static final double BODY_BAND_TO_CERTIFICATIONS = + gap(22, BLANK_BODY, BLANK_CAPS_ABOVE_HEADING); + + // -- education ------------------------------------------------------- + + static final double HEADING_TO_EDUCATION = gap(34, BLANK_CAPS_BELOW_HEADING, 0.0); + static final double DEGREE_TO_INSTITUTION = gap(14, BLANK_TITLE, BLANK_SMALL); + static final double INSTITUTION_TO_YEARS = gap(14, BLANK_SMALL, BLANK_SMALL); + static final double YEARS_TO_STUB = gap(20, BLANK_SMALL, 0.0); + static final double STUB_TO_DEGREE = gap(20, 0.0, 0.0); + static final double STUB_RULE_WIDTH = h(27); + static final double STUB_RULE_THICKNESS = h(2); + + // -- skills ---------------------------------------------------------- + + static final double EDUCATION_TO_SKILLS = + gap(45, BLANK_SMALL, BLANK_CAPS_ABOVE_HEADING); + static final double HEADING_TO_CAPTION = gap(32, BLANK_CAPS_BELOW_HEADING, + blankAbove(SMALL_SIZE, TIGHT_LEADING)); + static final double CAPTION_TO_ROWS = gap(17, blankBelow(SMALL_SIZE), 0.0); + static final double SKILL_ROW_HEIGHT = SMALL_SIZE * TIGHT_LEADING; + static final double SKILL_ROW_GAP = Math.max(0.0, v(21.5) - SKILL_ROW_HEIGHT); + static final double GROUP_GAP = + gap(31, 0.0, blankAbove(SMALL_SIZE, TIGHT_LEADING)); + static final double TRACK_WIDTH = h(116); + static final double TRACK_THICKNESS = h(3); + static final double CAPTION_TO_SOFT_SKILLS = + gap(14, blankBelow(SMALL_SIZE), BLANK_BODY); + static final double SOFT_SKILL_LEADING = v(19) / BODY_SIZE; + + // -- certifications and achievements --------------------------------- + + static final double HEADING_TO_CERTIFICATIONS = + gap(33, BLANK_CAPS_BELOW_HEADING, 0.0); + static final double CERT_PLATE_DIAMETER = h(48); + static final double CERT_GLYPH_TO_TEXT = h(58); + static final double CERT_LINE_LEADING = v(19) / SMALL_SIZE; + static final double CERT_SEPARATOR_HEIGHT = v(46); + static final double CERTIFICATIONS_TO_ACHIEVEMENTS = + gap(50, BLANK_SMALL, BLANK_CAPS_ABOVE_HEADING); + + static final double HEADING_TO_ACHIEVEMENTS = + gap(39, BLANK_CAPS_BELOW_HEADING, 0.0); + static final double ACHIEVEMENT_GLYPH_BOX = h(40); + static final double ACHIEVEMENT_GLYPH_TO_TEXT = h(59); + static final double ACHIEVEMENT_GLYPH_HANG = h(-2); + static final double ACHIEVEMENT_TEXT_DROP = v(8); + static final double ACHIEVEMENT_TITLE_TO_BODY = gap(16, BLANK_TITLE, BLANK_SMALL); + static final double ACHIEVEMENT_BODY_LEADING = v(18) / SMALL_SIZE; + static final double ACHIEVEMENT_SEPARATOR_HEIGHT = v(62); + + // -- tracked capitals ------------------------------------------------ + + static final double CAP_ADVANCE_NARROW = 0.30; + static final double CAP_ADVANCE_WIDE = 0.85; + static final double CAP_ADVANCE_EM = 0.62; + + /** Headroom on the estimate, so a tail never runs under its heading. */ + static final double TRACKED_WIDTH_SAFETY = 1.03; + + /** + * Writes text letter by letter with a sized space between each pair, + * which is how this design gets its letter-spacing: a text style carries + * no tracking, so the gap is a run of its own whose type size is chosen + * to advance by {@link #TRACKING_EM} of the surrounding size. + * + * @param paragraph the paragraph being built + * @param text the text to space out + * @param style the style of the letters + */ + static void tracked(ParagraphBuilder paragraph, String text, DocumentTextStyle style) { + DocumentTextStyle spacer = style.withSize(TRACKING_EM * style.size() / SPACE_ADVANCE_EM); + for (int i = 0; i < text.length(); i++) { + paragraph.inlineText(String.valueOf(text.charAt(i)), style); + if (i < text.length() - 1) { + paragraph.inlineText(" ", spacer); + } + } + } + + /** + * How wide a run of tracked capitals will be, estimated from per-letter + * advances rather than measured. + * + *

    The heading rule that trails a title has to stop before the letters + * start, and its width is authored — the engine offers no measurement at + * compose time. Three advance classes are enough for capitals, and the + * result is padded by {@link #TRACKED_WIDTH_SAFETY} so the estimate errs + * towards a shorter rule.

    + * + * @param text the capitals to measure + * @param size the type size + * @return the estimated width in points + */ + static double trackedWidth(String text, double size) { + if (text.isEmpty()) { + return 0.0; + } + double glyphs = 0.0; + for (int i = 0; i < text.length(); i++) { + glyphs += capAdvanceEm(text.charAt(i)); + } + return (glyphs * size + (text.length() - 1) * size * TRACKING_EM) + * TRACKED_WIDTH_SAFETY; + } + + private static double capAdvanceEm(char glyph) { + return switch (glyph) { + case 'I', 'J' -> CAP_ADVANCE_NARROW; + case 'M', 'W' -> CAP_ADVANCE_WIDE; + default -> CAP_ADVANCE_EM; + }; + } + + // -- bands ----------------------------------------------------------- + + /** + * The margins that place one column of an N-column band, as insets on a + * layer of the band's stack. + * + * @param bandWidth the band's full width + * @param columnWidth one column's share of it + * @param index which column, from zero + * @param dx an extra offset for a mark that hangs left + * @param naturalWidth the child's own width when it has one, else zero + * @return the insets to set on that layer + */ + static DocumentInsets columnInsets(double bandWidth, double columnWidth, + int index, double dx, double naturalWidth) { + return columnInsets(bandWidth, columnWidth, index, dx, naturalWidth, 0.0); + } + + /** + * The same, with a gutter kept at the column's right edge. + * + * @param bandWidth the band's full width + * @param columnWidth one column's share of it + * @param index which column, from zero + * @param dx an extra offset for a mark that hangs left + * @param naturalWidth the child's own width when it has one, else zero + * @param tailGutter space to leave at the right edge + * @return the insets to set on that layer + */ + static DocumentInsets columnInsets(double bandWidth, double columnWidth, + int index, double dx, double naturalWidth, + double tailGutter) { + double left = index * columnWidth + dx; + double right = naturalWidth > 0.0 + ? Math.max(0.0, bandWidth - left - naturalWidth) + : Math.max(0.0, bandWidth - left - (columnWidth - dx) + tailGutter); + return new DocumentInsets(0, right, 0, left); + } + + // -- styles ---------------------------------------------------------- + + static DocumentTextStyle body() { + return style(BODY_SIZE, BODY, DocumentTextDecoration.DEFAULT); + } + + static DocumentTextStyle small() { + return style(SMALL_SIZE, BODY, DocumentTextDecoration.DEFAULT); + } + + static DocumentTextStyle style(double size, DocumentColor color, + DocumentTextDecoration decoration) { + return TextStyles.of(TEXT_FONT, size, decoration, color); + } + + /** Strips a title down to the letters and digits a node name can carry. */ + static String compact(String text) { + return text == null ? "" : text.replaceAll("[^A-Za-z0-9]", ""); + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/SerifHeadlineText.java b/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/SerifHeadlineText.java new file mode 100644 index 000000000..a30914a0c --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/SerifHeadlineText.java @@ -0,0 +1,55 @@ +package com.demcha.compose.document.templates.cv.presets; + +import com.demcha.compose.document.dsl.ParagraphBuilder; +import com.demcha.compose.document.node.DocumentLinkOptions; +import com.demcha.compose.document.style.DocumentTextStyle; +import com.demcha.compose.document.templates.core.text.MarkdownInline; +import com.demcha.compose.document.templates.cv.data.CvEntry; + +import java.util.ArrayList; +import java.util.List; + +/** Turning model fields into the runs and lines this sheet draws. */ +final class SerifHeadlineText { + + private SerifHeadlineText() { + } + + /** + * One item per non-blank line — the family's way of carrying a list in a + * field the model types as a single string. + * + * @param body the field's text + * @return the lines, in order + */ + static List lines(String body) { + List out = new ArrayList<>(); + for (String line : body.split("\\R")) { + String clean = MarkdownInline.plainText(line).trim(); + if (!clean.isBlank()) { + out.add(clean); + } + } + return out; + } + + /** + * Writes an entry's title into a paragraph, as a link when the entry + * carries one. + * + *

    The link is the same ink either way — an annotation draws nothing + * and lays out nothing — so a linked title and a plain one are the same + * sheet.

    + * + * @param paragraph the paragraph being built + * @param entry the entry whose title and link to write + * @param style the style of the title + */ + static void title(ParagraphBuilder paragraph, CvEntry entry, DocumentTextStyle style) { + if (entry.link().isBlank()) { + paragraph.inlineText(entry.title(), style); + } else { + paragraph.inlineText(entry.title(), style, new DocumentLinkOptions(entry.link())); + } + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/SerifHeadlineWidgets.java b/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/SerifHeadlineWidgets.java new file mode 100644 index 000000000..8dd32fb0e --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/SerifHeadlineWidgets.java @@ -0,0 +1,236 @@ +package com.demcha.compose.document.templates.cv.presets; + +import com.demcha.compose.document.dsl.ImageBuilder; +import com.demcha.compose.document.dsl.LineBuilder; +import com.demcha.compose.document.dsl.ParagraphBuilder; +import com.demcha.compose.document.dsl.SectionBuilder; +import com.demcha.compose.document.dsl.ShapeContainerBuilder; +import com.demcha.compose.document.node.DocumentNode; +import com.demcha.compose.document.node.LayerAlign; +import com.demcha.compose.document.style.ClipPolicy; +import com.demcha.compose.document.style.DocumentInsets; +import com.demcha.compose.document.style.DocumentTextDecoration; +import com.demcha.compose.document.style.DocumentTextStyle; + +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; + +import static com.demcha.compose.document.templates.cv.presets.SerifHeadlineStyles.DASH_THICKNESS; +import static com.demcha.compose.document.templates.cv.presets.SerifHeadlineStyles.DASH_WIDTH; +import static com.demcha.compose.document.templates.cv.presets.SerifHeadlineStyles.HAIRLINE_THICKNESS; +import static com.demcha.compose.document.templates.cv.presets.SerifHeadlineStyles.HEADING_SIZE; +import static com.demcha.compose.document.templates.cv.presets.SerifHeadlineStyles.INK; +import static com.demcha.compose.document.templates.cv.presets.SerifHeadlineStyles.PLATE; +import static com.demcha.compose.document.templates.cv.presets.SerifHeadlineStyles.RULE; +import static com.demcha.compose.document.templates.cv.presets.SerifHeadlineStyles.TAIL_GAP; +import static com.demcha.compose.document.templates.cv.presets.SerifHeadlineStyles.TIGHT_LEADING; +import static com.demcha.compose.document.templates.cv.presets.SerifHeadlineStyles.COLUMN_TAIL_GUTTER; +import static com.demcha.compose.document.templates.cv.presets.SerifHeadlineStyles.columnInsets; +import static com.demcha.compose.document.templates.cv.presets.SerifHeadlineStyles.compact; +import static com.demcha.compose.document.templates.cv.presets.SerifHeadlineStyles.dashOffset; +import static com.demcha.compose.document.templates.cv.presets.SerifHeadlineStyles.style; +import static com.demcha.compose.document.templates.cv.presets.SerifHeadlineStyles.titleOffset; +import static com.demcha.compose.document.templates.cv.presets.SerifHeadlineStyles.trackedWidth; +import static com.demcha.compose.document.templates.cv.presets.SerifHeadlineStyles.tracked; + +/** + * The three things this sheet is built from: the section heading — a short + * dash, tracked capitals, and on the full-width bands a rule trailing to the + * margin — the round plate a mark sits in, and the N-column band the + * projects, certifications and achievements are laid out as. + */ +final class SerifHeadlineWidgets { + + private SerifHeadlineWidgets() { + } + + /** + * A section heading. + * + *

    The dash and the words are anchored in a band rather than stacked, + * because the design measures both from the page edge and not from the + * column they sit in: a heading inside the grid and one across the page + * put their letters on the same vertical, which is what + * {@code leftSurfaceDistance} corrects for.

    + * + * @param section the host column + * @param title the heading text, drawn in capitals + * @param width the band's width + * @param tail true to trail a rule from the words to the + * right edge + * @param gapAbove the gap above the band + * @param leftSurfaceDistance how far this band's left edge already is + * from the page edge + */ + static void sectionHeading(SectionBuilder section, String title, double width, + boolean tail, double gapAbove, double leftSurfaceDistance) { + String caps = title.toUpperCase(Locale.ROOT); + double dashAt = dashOffset(leftSurfaceDistance); + double titleAt = titleOffset(leftSurfaceDistance); + + DocumentTextStyle headingStyle = style(HEADING_SIZE, INK, DocumentTextDecoration.DEFAULT); + DocumentNode dash = new LineBuilder() + .name("HeadingDash_" + compact(title)) + .horizontal(DASH_WIDTH) + .thickness(DASH_THICKNESS) + .color(INK) + .margin(DocumentInsets.zero()) + .build(); + ParagraphBuilder heading = new ParagraphBuilder() + .name("SectionTitle_" + compact(title)) + .textStyle(headingStyle) + .lineSpacing(TIGHT_LEADING); + tracked(heading, caps, headingStyle); + DocumentNode headingNode = heading.margin(DocumentInsets.zero()).build(); + + // The rule is authored, so its width comes from an estimate of what + // the letters will measure — see trackedWidth. + double tailWidth = width - titleAt - trackedWidth(caps, HEADING_SIZE) - TAIL_GAP; + DocumentNode tailNode = tail && tailWidth > 0 + ? new LineBuilder() + .name("HeadingTail_" + compact(title)) + .horizontal(tailWidth) + .thickness(HAIRLINE_THICKNESS) + .color(RULE) + .margin(DocumentInsets.zero()) + .build() + : null; + + section.addContainer(band -> { + band.name("SectionHeading_" + compact(title)) + .rectangle(width, HEADING_SIZE * TIGHT_LEADING) + .clipPolicy(ClipPolicy.OVERFLOW_VISIBLE) + .padding(DocumentInsets.zero()) + .margin(new DocumentInsets(gapAbove, 0, 0, 0)) + .position(dash, dashAt, 0, LayerAlign.CENTER_LEFT) + .position(headingNode, titleAt, 0, LayerAlign.CENTER_LEFT); + if (tailNode != null) { + band.position(tailNode, 0, 0, LayerAlign.CENTER_RIGHT); + } + }); + } + + /** + * A mark inside a pale round plate, clipped to the circle. + * + * @param plateName node name of the plate + * @param iconToken the packaged mark + * @param plateDiameter the plate's diameter + * @return the plate node + */ + static DocumentNode plate(String plateName, String iconToken, double plateDiameter) { + return new ShapeContainerBuilder() + .name(plateName) + .circle(plateDiameter) + .clipPolicy(ClipPolicy.CLIP_PATH) + .fillColor(PLATE) + .padding(DocumentInsets.zero()) + .center(new ImageBuilder() + .name(plateName + "Glyph") + .source(SerifHeadlineIcons.image(iconToken)) + .fitToBounds(SerifHeadlineIcons.size(iconToken), + SerifHeadlineIcons.size(iconToken)) + .margin(DocumentInsets.zero()) + .build()) + .build(); + } + + /** One column of a band: its text, the mark beside it, and the mark's offset. */ + record BandColumn(SectionBuilder text, DocumentNode glyph, double glyphDx) { + } + + /** Builds the column at a given index. */ + @FunctionalInterface + interface ColumnFactory { + + /** + * @param index which column, from zero + * @return that column + */ + BandColumn column(int index); + } + + /** + * A band of equal columns, each with a mark and a hairline between + * neighbours. + * + *

    It is a layer stack rather than a row because the marks hang outside + * their columns and the separators sit exactly on the column edges — + * both of which a row's slots would clip or push. Every child is placed + * by its own left and right insets instead, computed by + * {@link SerifHeadlineStyles#columnInsets}.

    + * + *

    The three passes are the drawing order: text first, then the marks + * over it, then the separators.

    + * + * @param section the host column + * @param name node-name stem for the band and its parts + * @param bandWidth the band's width + * @param columns how many columns + * @param gapAbove the gap above the band + * @param separatorHeight how tall the hairlines between columns are + * @param factory builds each column + */ + static void columnBand(SectionBuilder section, String name, double bandWidth, + int columns, double gapAbove, double separatorHeight, + ColumnFactory factory) { + double columnWidth = bandWidth / columns; + List parts = new ArrayList<>(columns); + for (int i = 0; i < columns; i++) { + parts.add(factory.column(i)); + } + section.addLayerStack(stack -> { + stack.name(name + "Band") + .padding(DocumentInsets.zero()) + .margin(new DocumentInsets(gapAbove, 0, 0, 0)); + for (int i = 0; i < columns; i++) { + // The text keeps a gutter at its right edge; the marks and + // the separators do not, since they are placed on the column + // boundary rather than filling to it. + stack.layer(parts.get(i).text() + .margin(columnInsets(bandWidth, columnWidth, i, 0.0, 0.0, + COLUMN_TAIL_GUTTER)) + .build(), LayerAlign.TOP_LEFT); + } + for (int i = 0; i < columns; i++) { + BandColumn part = parts.get(i); + if (part.glyph() == null) { + continue; + } + stack.layer(sleeve(name + "Glyph_" + i, part.glyph(), + columnInsets(bandWidth, columnWidth, i, part.glyphDx(), 0.0)), + LayerAlign.TOP_LEFT); + } + for (int i = 1; i < columns; i++) { + stack.layer(sleeve(name + "Separator_" + i, + separatorLine(name, i, separatorHeight), + columnInsets(bandWidth, columnWidth, i, 0.0, + HAIRLINE_THICKNESS)), + LayerAlign.TOP_LEFT); + } + }); + } + + /** + * A layer carries its position as margins, and a node built elsewhere + * already has its own; wrapping it in a section gives the insets + * somewhere to live without touching the node. + */ + private static DocumentNode sleeve(String name, DocumentNode child, DocumentInsets margin) { + SectionBuilder sleeve = new SectionBuilder(); + sleeve.name(name).spacing(0).padding(DocumentInsets.zero()).margin(margin); + sleeve.add(child); + return sleeve.build(); + } + + private static DocumentNode separatorLine(String name, int index, double height) { + return new LineBuilder() + .name(name + "SeparatorLine_" + index) + .vertical(height) + .thickness(HAIRLINE_THICKNESS) + .color(RULE) + .margin(DocumentInsets.zero()) + .build(); + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/SidebarPortrait.java b/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/SidebarPortrait.java index 6f09fdabf..8f893f99f 100644 --- a/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/SidebarPortrait.java +++ b/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/SidebarPortrait.java @@ -23,6 +23,8 @@ import com.demcha.compose.document.templates.core.theme.BrandTheme; import com.demcha.compose.document.templates.core.identity.SvgGlyph; +import com.demcha.compose.document.templates.core.identity.ContactUri; + import java.io.IOException; import java.io.InputStream; import java.io.UncheckedIOException; @@ -930,7 +932,8 @@ private static List contactItems(CvIdentity identity) { return List.of(); } List items = new ArrayList<>(); - addContactItem(items, "phone.svg", identity.contact().phone(), null); + addContactItem(items, "phone.svg", identity.contact().phone(), + ContactUri.telLink(identity.contact().phone())); String email = identity.contact().email(); if (!email.isBlank()) { addContactItem(items, "email.svg", email, diff --git a/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/SlateOrange.java b/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/SlateOrange.java new file mode 100644 index 000000000..92ee5af76 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/SlateOrange.java @@ -0,0 +1,245 @@ +package com.demcha.compose.document.templates.cv.presets; + +import com.demcha.compose.document.api.DocumentSession; +import com.demcha.compose.document.style.DocumentInsets; +import com.demcha.compose.document.templates.api.DocumentTemplate; +import com.demcha.compose.document.templates.cv.components.SectionLookup; +import com.demcha.compose.document.templates.cv.data.CvDocument; +import com.demcha.compose.document.templates.cv.data.CvSection; +import com.demcha.compose.document.templates.cv.data.EntriesSection; +import com.demcha.compose.document.templates.cv.data.ParagraphSection; +import com.demcha.compose.document.templates.cv.data.SkillsSection; + +import java.util.List; +import java.util.Objects; + +import static com.demcha.compose.document.templates.cv.presets.SlateOrangeStyles.MAIN_CELL_WEIGHT; +import static com.demcha.compose.document.templates.cv.presets.SlateOrangeStyles.PAGE; +import static com.demcha.compose.document.templates.cv.presets.SlateOrangeStyles.SIDEBAR_CELL_WEIGHT; + +/** + * Slate Orange — a one-page CV built as a full-bleed slate band over a + * two-column body: an orange monogram tile beside the name, the role line and + * the specialism strip, with the contact lines across an orange hairline; + * then a narrow column of marked competencies, trophy-marked achievements, + * rated languages and closing facts, beside a wide column carrying the + * profile, the roles held on a rail, and a credentials footer. + * + *

    Why the fills are page backgrounds

    + * + *

    The slate band, the orange tile, the hairline between the two body + * columns and the fainter one between the credential columns are page + * backgrounds rather than section fills or line nodes. A fill on a section is + * bounded by its content and would stop short of the paper edge; a page + * background takes its geometry from the canvas and reaches all four edges + * however much content happens to sit on it.

    + * + *

    Why every horizontal pair inside a column is a table

    + * + *

    A row cannot nest inside a row cell, and both body columns are cells of + * the body row — so every pair inside one that a row would otherwise carry is + * a table with fixed column widths: mark and label, language and rating, role + * and dates, the two credential columns. Where a column needs several stacked + * lines inside one cell, that cell holds a single-column table of its own, + * because a cell holds one node.

    + * + *

    The experience rail is the left border of each entry rather than a + * drawn line, so it stretches to whatever height the entry turns out to have + * instead of being a length computed against today's text.

    + * + *

    One page, and what happens past it

    + * + *

    The experience block is held together, and the body's two columns are a + * single row — a row is atomic, so a CV taller than the sheet raises + * {@code AtomicNodeTooLargeException}, naming the node and the height it + * needed. The preset draws no cap of its own, because a CV that quietly loses + * a role is worse than one that refuses to compose. {@link TimelineMinimal} + * is the preset in this package that splits its own columns across pages.

    + * + *

    How a document reaches its berth

    + * + *

    The bands are fixed, so the preset assigns each section a berth by title + * rather than reading {@link + * com.demcha.compose.document.templates.cv.data.Slot}. It has seven:

    + * + *
      + *
    • the specialisms, a {@link ParagraphSection} of one specialism per + * line, drawn as the strip under the role. Only its body is drawn: the + * title is how the document names the berth;
    • + *
    • competencies, achievements and the closing facts, + * {@link EntriesSection}s, and languages, a {@link SkillsSection} — the + * narrow column;
    • + *
    • the profile, a {@link ParagraphSection}, and experience, an + * {@link EntriesSection} whose bodies are one highlight per line — the + * wide column;
    • + *
    • education and certifications, {@link EntriesSection}s — the + * credentials footer.
    • + *
    + * + *

    A section whose title matches no berth is not drawn, and a berth with no + * section takes its heading and rule with it.

    + * + *

    The marks, the ratings and the links

    + * + *

    A competency, an achievement and a closing fact each take the mark its + * entry names in {@code CvEntry.icon()}, from this preset's own vocabulary, + * and an unknown token is reported as a data error naming the set. An entry + * with no token is drawn without a mark — except an achievement, which falls + * back to the trophy the design gives them all.

    + * + *

    A language draws both channels of its skill: the discs from + * {@code CvSkill.level()} and the words beside them from + * {@code CvSkill.note()}. A skill with no level draws no discs, and one with + * no note leaves that column empty rather than inventing a wording.

    + * + *

    The phone, the email and each link are reachable from the PDF, with the + * {@code tel:} and {@code mailto:} targets built from the values — so a + * number written without a country code gets a national dial target, which is + * what the document said. A link is + * drawn as its own label with the address behind it, so the contact column's + * width does not depend on how long a profile happens to be called. A role + * and a degree become links when their entry carries + * {@code CvEntry.link()}.

    + * + *

    Fonts

    + * + *

    The sheet is set in Fira Sans Condensed, with Asap Condensed for the + * name and the headings. The templates artifact carries neither — register + * them on the session, or the engine substitutes and the measured geometry + * here no longer matches the type sitting in it. Both faces are condensed on + * purpose: the design's measure is narrow enough that a normal-width face + * turns single-line bullets into two.

    + * + *

    Usage:

    + *
    {@code
    + * DocumentTemplate template = SlateOrange.create();
    + * template.compose(session, cv);
    + * }
    + * + * @since 2.4.0 + */ +public final class SlateOrange { + + /** Stable identifier of this preset. */ + public static final String ID = "slate-orange"; + + /** Human-readable name of this preset. */ + public static final String DISPLAY_NAME = "Slate Orange"; + + private static final List SPECIALISM_KEYS = + List.of("specialisms", "specialities", "specialties", "focus"); + private static final List COMPETENCY_KEYS = + List.of("competencies", "competency", "skills", "expertise"); + private static final List ACHIEVEMENT_KEYS = + List.of("achievements", "achievement", "awards", "highlights"); + private static final List LANGUAGE_KEYS = + List.of("languages", "language"); + private static final List FACT_KEYS = + List.of("additional", "details", "other"); + private static final List PROFILE_KEYS = + List.of("profile", "summary", "about"); + private static final List EXPERIENCE_KEYS = + List.of("experience", "employment", "work history", "career"); + private static final List EDUCATION_KEYS = + List.of("education", "qualifications"); + private static final List CERTIFICATION_KEYS = + List.of("certifications", "certification", "training", "licences", "licenses"); + + private SlateOrange() { + } + + /** + * Creates the template. + * + * @return a template composing a {@link CvDocument} + */ + public static DocumentTemplate create() { + return new Template(); + } + + private record Template() implements DocumentTemplate { + + @Override + public String id() { + return ID; + } + + @Override + public String displayName() { + return DISPLAY_NAME; + } + + @Override + public void compose(DocumentSession document, CvDocument doc) { + Objects.requireNonNull(document, "document"); + Objects.requireNonNull(doc, "doc"); + List sections = doc.sections(); + + // The specialisms are claimed before the profile: their own keys + // are narrow, but the profile's are broad enough to take a strip + // titled loosely. + ParagraphSection specialisms = + berth(sections, ParagraphSection.class, SPECIALISM_KEYS); + ParagraphSection profile = + berthExcept(sections, ParagraphSection.class, PROFILE_KEYS, specialisms); + EntriesSection competencies = + berth(sections, EntriesSection.class, COMPETENCY_KEYS); + EntriesSection achievements = + berthExcept(sections, EntriesSection.class, ACHIEVEMENT_KEYS, competencies); + SkillsSection languages = berth(sections, SkillsSection.class, LANGUAGE_KEYS); + EntriesSection facts = berth(sections, EntriesSection.class, FACT_KEYS); + EntriesSection experience = berth(sections, EntriesSection.class, EXPERIENCE_KEYS); + EntriesSection education = berth(sections, EntriesSection.class, EDUCATION_KEYS); + EntriesSection certifications = + berth(sections, EntriesSection.class, CERTIFICATION_KEYS); + + // The sheet is a fixed composition drawn to the paper edge, so the + // preset sets the page and takes the margin off: every column pads + // itself, and a session margin would move the fills off the fills. + document.pageSize(PAGE).margin(DocumentInsets.zero()); + SlateOrangeMasthead.renderChrome(document); + + document.pageFlow(page -> { + page.name("SlateOrangeCv"); + page.spacing(0); + SlateOrangeMasthead.render(page, doc.identity(), specialisms); + page.addRow("Body", row -> { + row.spacing(0); + row.weights(SIDEBAR_CELL_WEIGHT, MAIN_CELL_WEIGHT); + row.addSection("Sidebar", side -> SlateOrangeAside.compose(side, + competencies, achievements, languages, facts)); + row.addSection("MainColumn", main -> SlateOrangeMain.compose(main, + profile, experience, education, certifications)); + }); + }); + } + + /** + * The first section of the wanted shape whose title names this berth, + * or {@code null} when the document fills no such berth. + */ + private static T berth(List sections, + Class type, + List keys) { + return berthExcept(sections, type, keys, null); + } + + /** The same, skipping a section another berth has already claimed. */ + private static T berthExcept(List sections, + Class type, + List keys, + CvSection taken) { + for (CvSection section : sections) { + if (!type.isInstance(section) || section == taken) { + continue; + } + for (String key : keys) { + if (SectionLookup.titleContains(section.title(), key)) { + return type.cast(section); + } + } + } + return null; + } + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/SlateOrangeAside.java b/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/SlateOrangeAside.java new file mode 100644 index 000000000..22606cb8e --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/SlateOrangeAside.java @@ -0,0 +1,267 @@ +package com.demcha.compose.document.templates.cv.presets; + +import com.demcha.compose.document.dsl.ParagraphBuilder; +import com.demcha.compose.document.dsl.SectionBuilder; +import com.demcha.compose.document.node.DocumentNode; +import com.demcha.compose.document.style.DocumentInsets; +import com.demcha.compose.document.style.DocumentTextStyle; +import com.demcha.compose.document.table.DocumentTableCell; +import com.demcha.compose.document.table.DocumentTableColumn; +import com.demcha.compose.document.table.DocumentTableTextAnchor; +import com.demcha.compose.document.templates.cv.data.CvEntry; +import com.demcha.compose.document.templates.cv.data.CvSkill; +import com.demcha.compose.document.templates.cv.data.EntriesSection; +import com.demcha.compose.document.templates.cv.data.SkillGroup; +import com.demcha.compose.document.templates.cv.data.SkillsSection; + +import java.util.ArrayList; +import java.util.List; + +import static com.demcha.compose.document.templates.cv.presets.SlateOrangeStyles.ACCENT; +import static com.demcha.compose.document.templates.cv.presets.SlateOrangeStyles.ACHIEVEMENT_BODY_LEADING; +import static com.demcha.compose.document.templates.cv.presets.SlateOrangeStyles.ACHIEVEMENT_BODY_SIZE; +import static com.demcha.compose.document.templates.cv.presets.SlateOrangeStyles.ACHIEVEMENT_GAP; +import static com.demcha.compose.document.templates.cv.presets.SlateOrangeStyles.ACHIEVEMENT_TITLE_SIZE; +import static com.demcha.compose.document.templates.cv.presets.SlateOrangeStyles.ACHIEVEMENTS_TOP_GAP; +import static com.demcha.compose.document.templates.cv.presets.SlateOrangeStyles.ADDITIONAL_ICON_COLUMN; +import static com.demcha.compose.document.templates.cv.presets.SlateOrangeStyles.ADDITIONAL_PITCH; +import static com.demcha.compose.document.templates.cv.presets.SlateOrangeStyles.ADDITIONAL_TOP_GAP; +import static com.demcha.compose.document.templates.cv.presets.SlateOrangeStyles.BODY_FONT; +import static com.demcha.compose.document.templates.cv.presets.SlateOrangeStyles.BODY_TOP; +import static com.demcha.compose.document.templates.cv.presets.SlateOrangeStyles.COMPETENCY_PITCH; +import static com.demcha.compose.document.templates.cv.presets.SlateOrangeStyles.HALF_GAP; +import static com.demcha.compose.document.templates.cv.presets.SlateOrangeStyles.ICON_COLUMN; +import static com.demcha.compose.document.templates.cv.presets.SlateOrangeStyles.INK; +import static com.demcha.compose.document.templates.cv.presets.SlateOrangeStyles.LANGUAGE_LEVEL_COLUMN; +import static com.demcha.compose.document.templates.cv.presets.SlateOrangeStyles.LANGUAGE_NAME_COLUMN; +import static com.demcha.compose.document.templates.cv.presets.SlateOrangeStyles.LANGUAGE_PITCH; +import static com.demcha.compose.document.templates.cv.presets.SlateOrangeStyles.LANGUAGE_RATING_COLUMN; +import static com.demcha.compose.document.templates.cv.presets.SlateOrangeStyles.LANGUAGE_SIZE; +import static com.demcha.compose.document.templates.cv.presets.SlateOrangeStyles.LANGUAGES_TOP_GAP; +import static com.demcha.compose.document.templates.cv.presets.SlateOrangeStyles.LEVEL_SIZE; +import static com.demcha.compose.document.templates.cv.presets.SlateOrangeStyles.LINE_FACTOR; +import static com.demcha.compose.document.templates.cv.presets.SlateOrangeStyles.MUTED; +import static com.demcha.compose.document.templates.cv.presets.SlateOrangeStyles.PAGE_MARGIN; +import static com.demcha.compose.document.templates.cv.presets.SlateOrangeStyles.RATING_DOT_DIAMETER; +import static com.demcha.compose.document.templates.cv.presets.SlateOrangeStyles.RATING_EMPTY; +import static com.demcha.compose.document.templates.cv.presets.SlateOrangeStyles.RULE_TO_SIDEBAR_BODY; +import static com.demcha.compose.document.templates.cv.presets.SlateOrangeStyles.SIDEBAR_TABLE_WIDTH; +import static com.demcha.compose.document.templates.cv.presets.SlateOrangeStyles.cellStyle; +import static com.demcha.compose.document.templates.cv.presets.SlateOrangeStyles.gap; +import static com.demcha.compose.document.templates.cv.presets.SlateOrangeStyles.leading; +import static com.demcha.compose.document.templates.cv.presets.SlateOrangeStyles.style; +import static com.demcha.compose.document.templates.cv.presets.SlateOrangeWidgets.heading; +import static com.demcha.compose.document.templates.cv.presets.SlateOrangeWidgets.iconCell; +import static com.demcha.compose.document.templates.cv.presets.SlateOrangeWidgets.iconTable; +import static com.demcha.compose.document.templates.cv.presets.SlateOrangeWidgets.spacer; +import static com.demcha.compose.document.templates.cv.presets.SlateOrangeWidgets.text; + +/** + * The narrow column: the marked competencies, the achievements, the rated + * languages and the closing facts. + */ +final class SlateOrangeAside { + + /** The mark every achievement takes when its entry names none. */ + private static final String DEFAULT_ACHIEVEMENT_MARK = SlateOrangeIcons.ACHIEVEMENT; + + /** How many discs a rating is drawn out of. */ + private static final int RATING_SCALE = 5; + + private SlateOrangeAside() { + } + + static void compose(SectionBuilder side, EntriesSection competencies, + EntriesSection achievements, SkillsSection languages, + EntriesSection facts) { + side.spacing(0); + side.padding((float) BODY_TOP, (float) HALF_GAP, (float) PAGE_MARGIN, + (float) PAGE_MARGIN); + if (hasEntries(competencies)) { + renderCompetencies(side, competencies); + } + if (hasEntries(achievements)) { + renderAchievements(side, achievements); + } + if (hasSkills(languages)) { + renderLanguages(side, languages); + } + if (hasEntries(facts)) { + renderFacts(side, facts); + } + } + + /** The competencies — one mark-and-label row each, on a fixed mark column. */ + private static void renderCompetencies(SectionBuilder side, EntriesSection section) { + side.addSection("Competencies", block -> { + block.spacing(0); + heading(block, "Competencies", section.title()); + iconTable(block, "CompetencyTable", "Competency", section.entries(), + COMPETENCY_PITCH, ICON_COLUMN); + }); + } + + /** The closing facts — the same mark column at a wider row pitch. */ + private static void renderFacts(SectionBuilder side, EntriesSection section) { + side.addSection("Additional", block -> { + block.spacing(0); + block.margin((float) ADDITIONAL_TOP_GAP, 0f, 0f, 0f); + heading(block, "Additional", section.title()); + iconTable(block, "AdditionalTable", "Additional", section.entries(), + ADDITIONAL_PITCH, ADDITIONAL_ICON_COLUMN); + }); + } + + // -- achievements ------------------------------------------------------ + + /** + * The achievements — a trophy in its own column beside a bold title over + * its body. + * + *

    Two table rows per achievement rather than one: the title and the + * body are separate lines that must both start at the label column, and a + * cell holds one node. The body's row leaves the mark column empty, which + * is what makes the body hang on the title's left edge rather than wrap + * back under the trophy.

    + */ + private static void renderAchievements(SectionBuilder side, EntriesSection section) { + side.addSection("Achievements", block -> { + block.spacing(0); + block.margin((float) ACHIEVEMENTS_TOP_GAP, 0f, 0f, 0f); + heading(block, "Achievements", section.title()); + List entries = section.entries(); + block.addTable(table -> { + table.name("AchievementTable"); + table.margin(new DocumentInsets(RULE_TO_SIDEBAR_BODY, 0, 0, 0)); + table.width(SIDEBAR_TABLE_WIDTH); + table.columns( + DocumentTableColumn.fixed(ICON_COLUMN), + DocumentTableColumn.fixed(SIDEBAR_TABLE_WIDTH - ICON_COLUMN)); + table.defaultCellStyle(cellStyle(DocumentInsets.zero(), + DocumentTableTextAnchor.TOP_LEFT)); + for (int index = 0; index < entries.size(); index++) { + CvEntry entry = entries.get(index); + boolean last = index == entries.size() - 1; + String mark = entry.icon().isBlank() + ? DEFAULT_ACHIEVEMENT_MARK + : entry.icon(); + table.rowCells( + DocumentTableCell.node(iconCell("AchievementIcon_" + index, mark, + ACHIEVEMENT_TITLE_SIZE)), + DocumentTableCell.node(text("AchievementTitle_" + index, + entry.title(), + style(BODY_FONT, ACHIEVEMENT_TITLE_SIZE, INK, true)))); + table.rowCells( + DocumentTableCell.node(spacer("AchievementGutter_" + index)), + DocumentTableCell.node(new ParagraphBuilder() + .name("AchievementBody_" + index) + .text(entry.body()) + .lineSpacing(leading(ACHIEVEMENT_BODY_LEADING, + ACHIEVEMENT_BODY_SIZE)) + .textStyle(style(BODY_FONT, ACHIEVEMENT_BODY_SIZE, INK, false)) + .build())); + if (!last) { + // The gap belongs to the row, not to the paragraph in + // it: a margin on a node inside a table cell is the + // cell's business, and the cell ignores it. + table.rowStyle(2 * index + 1, cellStyle( + new DocumentInsets(0, 0, ACHIEVEMENT_GAP, 0), + DocumentTableTextAnchor.TOP_LEFT)); + } + } + }); + }); + } + + // -- languages --------------------------------------------------------- + + /** + * The languages — the name, the level as the document words it, and the + * rating as five discs, on one line. + * + *

    This is the block that wants both channels of a rated skill: the + * discs come from {@code CvSkill.level()} and the words beside them from + * {@code CvSkill.note()}. A skill with no level draws no discs, and one + * with no note leaves that column empty rather than inventing a + * wording.

    + */ + private static void renderLanguages(SectionBuilder side, SkillsSection languages) { + List entries = new ArrayList<>(); + for (SkillGroup group : languages.groups()) { + entries.addAll(group.entries()); + } + side.addSection("Languages", block -> { + block.spacing(0); + block.margin((float) LANGUAGES_TOP_GAP, 0f, 0f, 0f); + heading(block, "Languages", languages.title()); + block.addTable(table -> { + table.name("LanguageTable"); + table.margin(new DocumentInsets(RULE_TO_SIDEBAR_BODY, 0, 0, 0)); + table.width(SIDEBAR_TABLE_WIDTH); + table.columns( + DocumentTableColumn.fixed(LANGUAGE_NAME_COLUMN), + DocumentTableColumn.fixed(LANGUAGE_LEVEL_COLUMN), + DocumentTableColumn.fixed(LANGUAGE_RATING_COLUMN)); + table.defaultCellStyle(cellStyle( + new DocumentInsets(0, 0, + gap(LANGUAGE_PITCH, LANGUAGE_SIZE * LINE_FACTOR), 0), + DocumentTableTextAnchor.CENTER_LEFT)); + for (int index = 0; index < entries.size(); index++) { + CvSkill skill = entries.get(index); + table.rowCells( + DocumentTableCell.node(text("LanguageName_" + index, skill.name(), + style(BODY_FONT, LANGUAGE_SIZE, INK, false))), + DocumentTableCell.node(skill.note().isBlank() + ? spacer("LanguageLevel_" + index) + : text("LanguageLevel_" + index, skill.note(), + style(BODY_FONT, LEVEL_SIZE, MUTED, false))), + DocumentTableCell.node(rating("LanguageRating_" + index, skill))); + } + table.rowStyle(entries.size() - 1, + cellStyle(DocumentInsets.zero(), DocumentTableTextAnchor.CENTER_LEFT)); + }); + }); + } + + /** + * One language's rating, as inline shape runs rather than a bullet glyph, + * so it cannot fall victim to whatever a fallback font does or does not + * ship. + * + *

    The paragraph is styled although it carries no words: a paragraph + * takes its line box from its type whether or not a glyph uses it, and the + * default box would set the whole row's pitch.

    + */ + private static DocumentNode rating(String name, CvSkill skill) { + ParagraphBuilder paragraph = new ParagraphBuilder(); + paragraph.name(name); + paragraph.lineSpacing(0); + DocumentTextStyle gapStyle = style(BODY_FONT, LANGUAGE_SIZE, INK, false); + paragraph.textStyle(gapStyle); + if (skill.level().isEmpty()) { + return paragraph.build(); + } + int filled = (int) Math.round(skill.level().getAsDouble() * RATING_SCALE); + for (int disc = 0; disc < RATING_SCALE; disc++) { + paragraph.dot(RATING_DOT_DIAMETER, disc < filled ? ACCENT : RATING_EMPTY); + if (disc < RATING_SCALE - 1) { + // Two spaces, not one: the design sets the discs nearly twice + // their own diameter apart, and one space at this size is half + // of that. + paragraph.inlineText(" ", gapStyle); + } + } + return paragraph.build(); + } + + // -- presence ---------------------------------------------------------- + + private static boolean hasEntries(EntriesSection section) { + return section != null && !section.entries().isEmpty(); + } + + private static boolean hasSkills(SkillsSection section) { + return section != null + && section.groups().stream().anyMatch(group -> !group.entries().isEmpty()); + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/SlateOrangeIcons.java b/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/SlateOrangeIcons.java new file mode 100644 index 000000000..373a39701 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/SlateOrangeIcons.java @@ -0,0 +1,114 @@ +package com.demcha.compose.document.templates.cv.presets; + +import com.demcha.compose.document.svg.SvgIcon; + +import java.io.IOException; +import java.io.InputStream; +import java.io.UncheckedIOException; +import java.nio.charset.StandardCharsets; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Classpath loader for the Slate Orange CV marks: the four contact glyphs, + * the ten competency marks, the trophy the achievements share, and the three + * marks the closing block draws. + * + *

    They ship inside the templates artifact under + * {@code templates/cv/slate-orange/icons/} as SVG, so they scale with the box + * they are drawn into rather than being resampled. Parsed icons are cached + * per token, so a sheet that repeats a mark reads each file once per JVM.

    + * + *

    {@link #ENTRY_TOKENS} is the vocabulary a document names through + * {@code CvEntry.icon()}. It is scoped to this preset — another preset + * packages its own set — and an entry with no token is drawn without a mark. + * Each mark carries the width it is drawn at: an icon's box is not its glyph, + * and the sizes are per-glyph rather than one shared number.

    + */ +final class SlateOrangeIcons { + + static final String PHONE = "phone"; + static final String EMAIL = "email"; + static final String LINKEDIN = "linkedin"; + static final String LOCATION = "location"; + static final String ACHIEVEMENT = "achievement"; + + private static final String ICON_ROOT = "/templates/cv/slate-orange/icons/"; + + /** The width each mark is drawn at, as the asset resolver chose it. */ + private static final Map SIZES = Map.ofEntries( + Map.entry(PHONE, 10.2), + Map.entry(EMAIL, 10.2), + Map.entry(LINKEDIN, 10.2), + Map.entry(LOCATION, 10.2), + Map.entry("customer-service", 12.4), + Map.entry("operations", 12.4), + Map.entry("calendar", 12.4), + Map.entry("reporting", 12.4), + Map.entry("communication", 12.4), + Map.entry("problem-solving", 12.4), + Map.entry("sales", 12.4), + Map.entry("office-suite", 12.4), + Map.entry("crm", 12.4), + Map.entry("time-management", 12.4), + Map.entry(ACHIEVEMENT, 13.0), + Map.entry("availability", 11.8), + Map.entry("relocation", 11.8), + Map.entry("remote-work", 11.8)); + + /** The marks a document may name on a competency or a closing fact. */ + static final Set ENTRY_TOKENS = Set.of( + "customer-service", "operations", "calendar", "reporting", "communication", + "problem-solving", "sales", "office-suite", "crm", "time-management", + ACHIEVEMENT, "availability", "relocation", "remote-work"); + + private static final Map CACHE = new ConcurrentHashMap<>(); + + private SlateOrangeIcons() { + } + + /** + * The width one mark is drawn at. + * + * @param token one of the packaged tokens + * @return the drawn width in points + * @throws IllegalArgumentException when the token names no packaged mark, + * listing the ones a document may choose — an icon token is data, + * so a wrong one is a data error + */ + static double size(String token) { + Double size = SIZES.get(token); + if (size == null) { + throw new IllegalArgumentException( + "Unknown slate orange CV icon token '" + token + + "'. This preset packages " + ENTRY_TOKENS.stream().sorted().toList() + + " for an entry mark."); + } + return size; + } + + /** + * Reads one mark. + * + * @param token one of the packaged tokens + * @return the parsed icon + */ + static SvgIcon icon(String token) { + size(token); + return CACHE.computeIfAbsent(token, SlateOrangeIcons::read); + } + + private static SvgIcon read(String token) { + String resourcePath = ICON_ROOT + token + ".svg"; + try (InputStream input = SlateOrangeIcons.class.getResourceAsStream(resourcePath)) { + if (input == null) { + throw new IllegalStateException("Missing slate orange CV icon: " + resourcePath); + } + return SvgIcon.parse(new String(input.readAllBytes(), StandardCharsets.UTF_8)); + } catch (IOException e) { + throw new UncheckedIOException( + "Failed to read slate orange CV icon: " + resourcePath, e); + } + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/SlateOrangeMain.java b/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/SlateOrangeMain.java new file mode 100644 index 000000000..80802e7b8 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/SlateOrangeMain.java @@ -0,0 +1,424 @@ +package com.demcha.compose.document.templates.cv.presets; + +import com.demcha.compose.document.dsl.ListBuilder; +import com.demcha.compose.document.dsl.ParagraphBuilder; +import com.demcha.compose.document.dsl.SectionBuilder; +import com.demcha.compose.document.dsl.TableBuilder; +import com.demcha.compose.document.dsl.TimelineMarker; +import com.demcha.compose.document.dsl.TimelineRailEnd; +import com.demcha.compose.document.node.DocumentLinkOptions; +import com.demcha.compose.document.node.DocumentNode; +import com.demcha.compose.document.node.LayerAlign; +import com.demcha.compose.document.node.ListMarker; +import com.demcha.compose.document.node.TextAlign; +import com.demcha.compose.document.style.DocumentInsets; +import com.demcha.compose.document.style.DocumentStroke; +import com.demcha.compose.document.table.DocumentTableCell; +import com.demcha.compose.document.table.DocumentTableColumn; +import com.demcha.compose.document.table.DocumentTableStyle; +import com.demcha.compose.document.table.DocumentTableTextAnchor; +import com.demcha.compose.document.templates.cv.data.CvEntry; +import com.demcha.compose.document.templates.cv.data.EntriesSection; +import com.demcha.compose.document.templates.cv.data.ParagraphSection; + +import java.util.ArrayList; +import java.util.List; + +import static com.demcha.compose.document.templates.cv.presets.SlateOrangeStyles.ACCENT; +import static com.demcha.compose.document.templates.cv.presets.SlateOrangeStyles.BODY_FONT; +import static com.demcha.compose.document.templates.cv.presets.SlateOrangeStyles.BODY_SIZE; +import static com.demcha.compose.document.templates.cv.presets.SlateOrangeStyles.BODY_TOP; +import static com.demcha.compose.document.templates.cv.presets.SlateOrangeStyles.CREDENTIAL_GUTTER; +import static com.demcha.compose.document.templates.cv.presets.SlateOrangeStyles.LINE_FACTOR; +import static com.demcha.compose.document.templates.cv.presets.SlateOrangeStyles.CREDENTIAL_HEADING_SIZE; +import static com.demcha.compose.document.templates.cv.presets.SlateOrangeStyles.CREDENTIAL_HEADING_TO_RULE; +import static com.demcha.compose.document.templates.cv.presets.SlateOrangeStyles.CREDENTIAL_LEFT_COLUMN; +import static com.demcha.compose.document.templates.cv.presets.SlateOrangeStyles.CREDENTIAL_LINE_PITCH; +import static com.demcha.compose.document.templates.cv.presets.SlateOrangeStyles.CREDENTIAL_RIGHT_COLUMN; +import static com.demcha.compose.document.templates.cv.presets.SlateOrangeStyles.CREDENTIAL_RULE_SIZE; +import static com.demcha.compose.document.templates.cv.presets.SlateOrangeStyles.CREDENTIAL_RULE_TO_BODY; +import static com.demcha.compose.document.templates.cv.presets.SlateOrangeStyles.CREDENTIAL_SIZE; +import static com.demcha.compose.document.templates.cv.presets.SlateOrangeStyles.CREDENTIAL_TABLE_WIDTH; +import static com.demcha.compose.document.templates.cv.presets.SlateOrangeStyles.DEGREE_SIZE; +import static com.demcha.compose.document.templates.cv.presets.SlateOrangeStyles.DISPLAY_FONT; +import static com.demcha.compose.document.templates.cv.presets.SlateOrangeStyles.EDUCATION_LINE_PITCH; +import static com.demcha.compose.document.templates.cv.presets.SlateOrangeStyles.EDUCATION_LINE_SIZE; +import static com.demcha.compose.document.templates.cv.presets.SlateOrangeStyles.EMPLOYER_SIZE; +import static com.demcha.compose.document.templates.cv.presets.SlateOrangeStyles.EMPLOYER_TO_HIGHLIGHTS; +import static com.demcha.compose.document.templates.cv.presets.SlateOrangeStyles.ENTRY_GAP; +import static com.demcha.compose.document.templates.cv.presets.SlateOrangeStyles.ENTRY_INDENT; +import static com.demcha.compose.document.templates.cv.presets.SlateOrangeStyles.ENTRY_WIDTH; +import static com.demcha.compose.document.templates.cv.presets.SlateOrangeStyles.EXPERIENCE_TO_RULE; +import static com.demcha.compose.document.templates.cv.presets.SlateOrangeStyles.HALF_GAP; +import static com.demcha.compose.document.templates.cv.presets.SlateOrangeStyles.HIGHLIGHT_LEADING; +import static com.demcha.compose.document.templates.cv.presets.SlateOrangeStyles.HIGHLIGHT_PITCH; +import static com.demcha.compose.document.templates.cv.presets.SlateOrangeStyles.HIGHLIGHT_SIZE; +import static com.demcha.compose.document.templates.cv.presets.SlateOrangeStyles.INK; +import static com.demcha.compose.document.templates.cv.presets.SlateOrangeStyles.LINE_FACTOR; +import static com.demcha.compose.document.templates.cv.presets.SlateOrangeStyles.MARKER_DIAMETER; +import static com.demcha.compose.document.templates.cv.presets.SlateOrangeStyles.NO_BORDER; +import static com.demcha.compose.document.templates.cv.presets.SlateOrangeStyles.PAGE_MARGIN; +import static com.demcha.compose.document.templates.cv.presets.SlateOrangeStyles.PERIOD_COLUMN; +import static com.demcha.compose.document.templates.cv.presets.SlateOrangeStyles.PERIOD_SIZE; +import static com.demcha.compose.document.templates.cv.presets.SlateOrangeStyles.PROFILE_LEADING; +import static com.demcha.compose.document.templates.cv.presets.SlateOrangeStyles.PROFILE_TO_EXPERIENCE; +import static com.demcha.compose.document.templates.cv.presets.SlateOrangeStyles.ROLE_COLUMN; +import static com.demcha.compose.document.templates.cv.presets.SlateOrangeStyles.ROLE_SIZE; +import static com.demcha.compose.document.templates.cv.presets.SlateOrangeStyles.ROLE_TO_EMPLOYER; +import static com.demcha.compose.document.templates.cv.presets.SlateOrangeStyles.RULE; +import static com.demcha.compose.document.templates.cv.presets.SlateOrangeStyles.RULE_THICKNESS; +import static com.demcha.compose.document.templates.cv.presets.SlateOrangeStyles.RULE_TO_CREDENTIALS; +import static com.demcha.compose.document.templates.cv.presets.SlateOrangeStyles.RULE_TO_MAIN_BODY; +import static com.demcha.compose.document.templates.cv.presets.SlateOrangeStyles.TABLE_SLACK; +import static com.demcha.compose.document.templates.cv.presets.SlateOrangeStyles.cellStyle; +import static com.demcha.compose.document.templates.cv.presets.SlateOrangeStyles.gap; +import static com.demcha.compose.document.templates.cv.presets.SlateOrangeStyles.italic; +import static com.demcha.compose.document.templates.cv.presets.SlateOrangeStyles.leading; +import static com.demcha.compose.document.templates.cv.presets.SlateOrangeStyles.style; +import static com.demcha.compose.document.templates.cv.presets.SlateOrangeWidgets.heading; +import static com.demcha.compose.document.templates.cv.presets.SlateOrangeWidgets.spacer; +import static com.demcha.compose.document.templates.cv.presets.SlateOrangeWidgets.text; + +/** + * The wide column: the profile, the roles on a rail, and the credentials + * footer. + */ +final class SlateOrangeMain { + + private SlateOrangeMain() { + } + + static void compose(SectionBuilder main, ParagraphSection profile, + EntriesSection experience, EntriesSection education, + EntriesSection certifications) { + main.spacing(0); + main.padding((float) BODY_TOP, (float) PAGE_MARGIN, (float) PAGE_MARGIN, + (float) HALF_GAP); + if (profile != null && !profile.body().isBlank()) { + renderProfile(main, profile); + } + if (hasEntries(experience)) { + renderExperience(main, experience); + } + if (hasEntries(education) || hasEntries(certifications)) { + renderCredentials(main, education, certifications); + } + } + + /** The profile — one paragraph at the design's measured leading. */ + private static void renderProfile(SectionBuilder main, ParagraphSection profile) { + main.addSection("Profile", block -> { + block.spacing(0); + heading(block, "Profile", profile.title()); + block.addParagraph(p -> p + .name("ProfileBody") + .text(profile.body().replace(String.valueOf((char) 10), " ")) + .lineSpacing(leading(PROFILE_LEADING, BODY_SIZE)) + .textStyle(style(BODY_FONT, BODY_SIZE, INK, false)) + .margin((float) RULE_TO_MAIN_BODY, 0f, 0f, 0f)); + }); + } + + // -- experience -------------------------------------------------------- + + /** + * The roles held, on one rail. + * + *

    The rail is the left border of each entry's own section, so it + * stretches to whatever height that entry turns out to have and + * consecutive entries butt together into one unbroken line. The last entry + * does not carry it: the design's rail stops at the final marker rather + * than running past the final bullet, and the entry list is what decides + * which entry that is.

    + * + *

    The marker is a disc positioned on the rail by a layer stack, nudged + * left by the entry's own indent plus half the disc so its centre lands on + * the border rather than beside it. Both terms are geometry the sheet + * already holds, not offsets tuned by eye.

    + */ + private static void renderExperience(SectionBuilder main, EntriesSection experience) { + main.addSection("Experience", block -> { + block.spacing(0); + block.margin((float) PROFILE_TO_EXPERIENCE, 0f, 0f, 0f); + block.keepTogether(); + heading(block, "Experience", experience.title()); + SectionBuilder holder = new SectionBuilder(); + holder.name("ExperienceEntriesHolder"); + holder.spacing(0); + // The axis column centres the rail on itself, so the timeline starts half a disc + // left of the edge the accents drew on and the line lands back on it. + holder.margin(new DocumentInsets(RULE_TO_MAIN_BODY, 0, 0, -MARKER_DIAMETER / 2.0)); + holder.addTimeline(timeline -> { + timeline.markerOnRail() + .rail(rail -> rail + .stroke(DocumentStroke.of(RULE, RULE_THICKNESS)) + .from(TimelineRailEnd.MARKER).to(TimelineRailEnd.MARKER)) + .axisWidth(MARKER_DIAMETER) + .markerGap(ENTRY_INDENT - MARKER_DIAMETER / 2.0) + .gutter(0) + .spacing(ENTRY_GAP); + List entries = experience.entries(); + for (int i = 0; i < entries.size(); i++) { + CvEntry entry = entries.get(i); + int index = i; + timeline.entry(entryMarker(index), e -> e.content(body -> { + body.spacing(0); + renderEntry(body, entry, index); + })); + } + }); + DocumentNode entriesNode = holder.build(); + // The timeline goes in through a layer stack rather than straight into + // the block. A timeline publishes a row, a row cannot nest inside a row + // cell, and this block can sit in one — the stack is what insulates it. + // Unwinding the stacks that turn out not to need it buys no capability, + // so they stay until that is done as its own deliberate cleanup. + block.addLayerStack(stack -> stack + .name("ExperienceEntries") + .layer(entriesNode, LayerAlign.TOP_LEFT, 0)); + }); + } + + /** One entry: the role and its dates, the employer line, then the bullets. */ + private static void renderEntry(SectionBuilder body, CvEntry entry, int index) { + // The disc has left this stack for the timeline's axis column; the layer stays + // because the role line is a table and a table is not a row cell's to hold. + body.addLayerStack(stack -> stack + .name("RoleLine_" + index) + .layer(roleLine(entry, index), LayerAlign.TOP_LEFT, 0)); + body.addParagraph(p -> p + .name("Employer_" + index) + .text(entry.subtitle()) + .lineSpacing(0) + .textStyle(italic(BODY_FONT, EMPLOYER_SIZE, INK)) + .margin((float) ROLE_TO_EMPLOYER, 0f, (float) EMPLOYER_TO_HIGHLIGHTS, 0f)); + List highlights = lines(entry.body()); + if (highlights.isEmpty()) { + return; + } + body.addList(list -> list + .name("Highlights_" + index) + .items(highlights) + .marker(ListMarker.bullet()) + .textStyle(style(BODY_FONT, HIGHLIGHT_SIZE, INK, false)) + .itemSpacing(gap(HIGHLIGHT_PITCH, HIGHLIGHT_SIZE * LINE_FACTOR)) + .lineSpacing(leading(HIGHLIGHT_LEADING, HIGHLIGHT_SIZE))); + } + + /** The role and its dates, the dates flush right on the same line. */ + private static DocumentNode roleLine(CvEntry entry, int index) { + ParagraphBuilder role = new ParagraphBuilder() + .name("Role_" + index) + .text(entry.title()) + .lineSpacing(0) + .textStyle(style(BODY_FONT, ROLE_SIZE, INK, true)); + if (!entry.link().isBlank()) { + role.link(new DocumentLinkOptions(entry.link())); + } + return new TableBuilder() + .name("RoleTable_" + index) + .width(ENTRY_WIDTH) + .columns( + DocumentTableColumn.fixed(ROLE_COLUMN), + DocumentTableColumn.fixed(PERIOD_COLUMN)) + .defaultCellStyle(cellStyle(DocumentInsets.zero(), + DocumentTableTextAnchor.BOTTOM_LEFT)) + .rowCells( + DocumentTableCell.node(role.build()), + DocumentTableCell.node(new ParagraphBuilder() + .name("Period_" + index) + .text(entry.date()) + .align(TextAlign.RIGHT) + .lineSpacing(0) + .textStyle(style(BODY_FONT, PERIOD_SIZE, INK, false)) + .build())) + .build(); + } + + /** The disc that caps the rail beside a role line. */ + /** + * The disc that marks a role, as a timeline marker. + * + *

    The box is the disc's own line box — {@code LINE_FACTOR} times its size, which is + * what the paragraph measured to when the stack positioned it — so the anchor the rail + * runs through is the centre of the same box the disc has always occupied.

    + */ + private static TimelineMarker entryMarker(int index) { + return TimelineMarker.custom(MARKER_DIAMETER, MARKER_DIAMETER * LINE_FACTOR, + column -> column.addParagraph(paragraph -> { + paragraph.name("Marker_" + index); + paragraph.lineSpacing(0); + paragraph.textStyle(style(BODY_FONT, MARKER_DIAMETER, ACCENT, false)); + paragraph.dot(MARKER_DIAMETER, ACCENT); + })); + } + + // -- credentials ------------------------------------------------------- + + /** + * The credentials footer: a rule across the column, then education and + * certifications side by side. + * + *

    One table, three rows. The first is the two headings; the second is + * their orange rules, drawn as cells filled to half a point of height, + * because a cell's stroke is a box with no per-edge control and a line + * node in a cell is not drawn at all. The third holds each column's + * content: a single-column table on the left, because a cell holds one + * node and a degree is three stacked lines, and the certifications list on + * the right, which is one list node and needs no wrapper.

    + */ + private static void renderCredentials(SectionBuilder main, EntriesSection education, + EntriesSection certifications) { + main.addSection("Credentials", block -> { + block.spacing(0); + block.margin((float) EXPERIENCE_TO_RULE, 0f, 0f, 0f); + block.addLine(line -> line + .name("CredentialsRule") + .fill() + .thickness(RULE_THICKNESS) + .color(RULE)); + block.addTable(table -> { + table.name("CredentialTable"); + table.margin(new DocumentInsets(RULE_TO_CREDENTIALS, 0, 0, 0)); + table.width(CREDENTIAL_TABLE_WIDTH); + table.columns( + DocumentTableColumn.fixed(CREDENTIAL_LEFT_COLUMN), + DocumentTableColumn.fixed(CREDENTIAL_GUTTER), + DocumentTableColumn.fixed(CREDENTIAL_RIGHT_COLUMN)); + table.defaultCellStyle(cellStyle(DocumentInsets.zero(), + DocumentTableTextAnchor.TOP_LEFT)); + table.rowCells( + DocumentTableCell.node(text("EducationHeading", + education == null ? "" : education.title(), + style(DISPLAY_FONT, CREDENTIAL_HEADING_SIZE, INK, true))), + DocumentTableCell.node(spacer("CredentialGutterHeading")), + DocumentTableCell.node(text("CertificationsHeading", + certifications == null ? "" : certifications.title(), + style(DISPLAY_FONT, CREDENTIAL_HEADING_SIZE, INK, true)))); + table.rowCells( + credentialRule("EducationHeadingRule"), + DocumentTableCell.node(spacer("CredentialGutterRule")), + credentialRule("CertificationsHeadingRule")); + table.rowCells( + DocumentTableCell.node(educationLines(education)), + DocumentTableCell.node(spacer("CredentialGutterBody")), + DocumentTableCell.node(certificationList(certifications))); + table.rowStyle(0, cellStyle( + new DocumentInsets(0, 0, CREDENTIAL_HEADING_TO_RULE, 0), + DocumentTableTextAnchor.TOP_LEFT)); + table.rowStyle(2, cellStyle( + new DocumentInsets(CREDENTIAL_RULE_TO_BODY, 0, 0, 0), + DocumentTableTextAnchor.TOP_LEFT)); + }); + }); + } + + /** A heading rule, as a cell filled to a hairline of height. */ + private static DocumentTableCell credentialRule(String name) { + return DocumentTableCell + .node(new ParagraphBuilder() + .name(name) + .text(" ") + .lineSpacing(0) + .textStyle(style(BODY_FONT, CREDENTIAL_RULE_SIZE, ACCENT, false)) + .build()) + .withStyle(DocumentTableStyle.builder() + .padding(DocumentInsets.zero()) + .fillColor(ACCENT) + .stroke(NO_BORDER) + .build()); + } + + /** + * Each degree as three stacked lines: the award, the institution and + * whatever the entry says about when. + */ + private static DocumentNode educationLines(EntriesSection education) { + double width = CREDENTIAL_LEFT_COLUMN - TABLE_SLACK; + TableBuilder table = new TableBuilder() + .name("EducationLines") + .width(width) + .columns(DocumentTableColumn.fixed(width)) + .defaultCellStyle(cellStyle( + new DocumentInsets(0, 0, + gap(EDUCATION_LINE_PITCH, EDUCATION_LINE_SIZE * LINE_FACTOR), 0), + DocumentTableTextAnchor.TOP_LEFT)); + if (education == null) { + return table.rowCells(DocumentTableCell.node(spacer("EducationEmpty"))).build(); + } + List entries = education.entries(); + for (int index = 0; index < entries.size(); index++) { + CvEntry entry = entries.get(index); + ParagraphBuilder degree = new ParagraphBuilder() + .name("Degree_" + index) + .text(entry.title()) + .lineSpacing(0) + .textStyle(style(BODY_FONT, DEGREE_SIZE, INK, true)); + if (!entry.link().isBlank()) { + degree.link(new DocumentLinkOptions(entry.link())); + } + table.rowCells(DocumentTableCell.node(degree.build())); + table.rowCells(DocumentTableCell.node(new ParagraphBuilder() + .name("Institution_" + index) + .text(entry.subtitle()) + .lineSpacing(0) + .textStyle(italic(BODY_FONT, EDUCATION_LINE_SIZE, INK)) + .build())); + table.rowCells(DocumentTableCell.node(text("EducationDetail_" + index, + detail(entry), style(BODY_FONT, EDUCATION_LINE_SIZE, INK, false)))); + } + return table.build(); + } + + /** + * The line under an institution: what the entry says about where and when, + * joined the way the design joins them. + */ + private static String detail(CvEntry entry) { + StringBuilder detail = new StringBuilder(); + if (!entry.place().isBlank()) { + detail.append(entry.place()); + } + if (!entry.date().isBlank()) { + detail.append(detail.isEmpty() ? "" : " " + (char) 0x2022 + " ").append(entry.date()); + } + return detail.toString(); + } + + /** The certifications, as one bulleted list. */ + private static DocumentNode certificationList(EntriesSection certifications) { + List items = new ArrayList<>(); + if (certifications != null) { + for (CvEntry entry : certifications.entries()) { + items.add(entry.title()); + } + } + if (items.isEmpty()) { + return spacer("CertificationsEmpty"); + } + return new ListBuilder() + .name("CertificationItems") + .items(items) + .marker(ListMarker.bullet()) + .textStyle(style(BODY_FONT, CREDENTIAL_SIZE, INK, false)) + .itemSpacing(gap(CREDENTIAL_LINE_PITCH, CREDENTIAL_SIZE * LINE_FACTOR)) + .build(); + } + + // -- shared ------------------------------------------------------------ + + /** A body, one entry per line the document wrote. */ + private static List lines(String body) { + List out = new ArrayList<>(); + for (String line : body.split(String.valueOf((char) 10))) { + if (!line.isBlank()) { + out.add(line.strip()); + } + } + return out; + } + + private static boolean hasEntries(EntriesSection section) { + return section != null && !section.entries().isEmpty(); + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/SlateOrangeMasthead.java b/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/SlateOrangeMasthead.java new file mode 100644 index 000000000..dedcceb18 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/SlateOrangeMasthead.java @@ -0,0 +1,292 @@ +package com.demcha.compose.document.templates.cv.presets; + +import com.demcha.compose.document.api.DocumentSession; +import com.demcha.compose.document.api.PageBackgroundFill; +import com.demcha.compose.document.dsl.LineBuilder; +import com.demcha.compose.document.dsl.PageFlowBuilder; +import com.demcha.compose.document.dsl.SectionBuilder; +import com.demcha.compose.document.node.DocumentLinkOptions; +import com.demcha.compose.document.node.HorizontalAlign; +import com.demcha.compose.document.node.TextAlign; +import com.demcha.compose.document.style.DocumentRowColumn; +import com.demcha.compose.document.style.DocumentTextStyle; +import com.demcha.compose.document.templates.core.identity.Contact; +import com.demcha.compose.document.templates.core.identity.ContactUri; +import com.demcha.compose.document.templates.core.identity.Link; +import com.demcha.compose.document.templates.cv.data.CvIdentity; +import com.demcha.compose.document.templates.cv.data.ParagraphSection; + +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; + +import static com.demcha.compose.document.templates.cv.presets.SlateOrangeStyles.ACCENT; +import static com.demcha.compose.document.templates.cv.presets.SlateOrangeStyles.BODY_FONT; +import static com.demcha.compose.document.templates.cv.presets.SlateOrangeStyles.BODY_TOP; +import static com.demcha.compose.document.templates.cv.presets.SlateOrangeStyles.CONTACT_PAD_LEFT; +import static com.demcha.compose.document.templates.cv.presets.SlateOrangeStyles.CONTACT_PITCH; +import static com.demcha.compose.document.templates.cv.presets.SlateOrangeStyles.CONTACT_SIZE; +import static com.demcha.compose.document.templates.cv.presets.SlateOrangeStyles.CONTACT_TOP; +import static com.demcha.compose.document.templates.cv.presets.SlateOrangeStyles.CREDENTIALS_DIVIDER_HEIGHT; +import static com.demcha.compose.document.templates.cv.presets.SlateOrangeStyles.CREDENTIALS_DIVIDER_X; +import static com.demcha.compose.document.templates.cv.presets.SlateOrangeStyles.CREDENTIALS_DIVIDER_Y; +import static com.demcha.compose.document.templates.cv.presets.SlateOrangeStyles.DISPLAY_FONT; +import static com.demcha.compose.document.templates.cv.presets.SlateOrangeStyles.HAIRLINE_BOTTOM; +import static com.demcha.compose.document.templates.cv.presets.SlateOrangeStyles.HAIRLINE_HEIGHT; +import static com.demcha.compose.document.templates.cv.presets.SlateOrangeStyles.HAIRLINE_THICKNESS; +import static com.demcha.compose.document.templates.cv.presets.SlateOrangeStyles.HAIRLINE_TOP; +import static com.demcha.compose.document.templates.cv.presets.SlateOrangeStyles.HALF_GAP; +import static com.demcha.compose.document.templates.cv.presets.SlateOrangeStyles.IDENTITY_TOP; +import static com.demcha.compose.document.templates.cv.presets.SlateOrangeStyles.IDENTITY_WIDTH; +import static com.demcha.compose.document.templates.cv.presets.SlateOrangeStyles.INITIALS_TO_RULE; +import static com.demcha.compose.document.templates.cv.presets.SlateOrangeStyles.LINE_FACTOR; +import static com.demcha.compose.document.templates.cv.presets.SlateOrangeStyles.MASTHEAD_HEIGHT; +import static com.demcha.compose.document.templates.cv.presets.SlateOrangeStyles.MASTHEAD_INK; +import static com.demcha.compose.document.templates.cv.presets.SlateOrangeStyles.MASTHEAD_MUTED; +import static com.demcha.compose.document.templates.cv.presets.SlateOrangeStyles.MONOGRAM_RULE_THICKNESS; +import static com.demcha.compose.document.templates.cv.presets.SlateOrangeStyles.MONOGRAM_RULE_WIDTH; +import static com.demcha.compose.document.templates.cv.presets.SlateOrangeStyles.MONOGRAM_SIZE; +import static com.demcha.compose.document.templates.cv.presets.SlateOrangeStyles.MONOGRAM_TOP; +import static com.demcha.compose.document.templates.cv.presets.SlateOrangeStyles.NAME_SIZE; +import static com.demcha.compose.document.templates.cv.presets.SlateOrangeStyles.NAME_TO_ROLE; +import static com.demcha.compose.document.templates.cv.presets.SlateOrangeStyles.PAGE_HEIGHT; +import static com.demcha.compose.document.templates.cv.presets.SlateOrangeStyles.PAGE_MARGIN; +import static com.demcha.compose.document.templates.cv.presets.SlateOrangeStyles.PAGE_WIDTH; +import static com.demcha.compose.document.templates.cv.presets.SlateOrangeStyles.ROLE_LINE_SIZE; +import static com.demcha.compose.document.templates.cv.presets.SlateOrangeStyles.ROLE_TO_TAGLINE; +import static com.demcha.compose.document.templates.cv.presets.SlateOrangeStyles.RULE; +import static com.demcha.compose.document.templates.cv.presets.SlateOrangeStyles.RULE_FAINT; +import static com.demcha.compose.document.templates.cv.presets.SlateOrangeStyles.RULE_THICKNESS; +import static com.demcha.compose.document.templates.cv.presets.SlateOrangeStyles.SIDEBAR_WIDTH; +import static com.demcha.compose.document.templates.cv.presets.SlateOrangeStyles.SLATE; +import static com.demcha.compose.document.templates.cv.presets.SlateOrangeStyles.TAGLINE_SEPARATOR; +import static com.demcha.compose.document.templates.cv.presets.SlateOrangeStyles.TAGLINE_SIZE; +import static com.demcha.compose.document.templates.cv.presets.SlateOrangeStyles.TILE_WIDTH; +import static com.demcha.compose.document.templates.cv.presets.SlateOrangeStyles.gap; +import static com.demcha.compose.document.templates.cv.presets.SlateOrangeStyles.style; +import static com.demcha.compose.document.templates.cv.presets.SlateOrangeWidgets.inlineIcon; + +/** + * The page chrome and the band above the body: the monogram tile, the + * identity strip, the orange hairline and the contact column. + */ +final class SlateOrangeMasthead { + + private SlateOrangeMasthead() { + } + + /** One line of the contact column: a mark, a value and where it points. */ + private record Channel(String token, String value, String href) { + } + + /** + * The four fills whose geometry is a fact about the page rather than about + * any content: the slate band, the orange tile over its left end, the + * hairline dividing the two body columns, and the fainter one dividing the + * two credential columns. + * + *

    They are page backgrounds rather than section fills or lines: a fill + * on a section is bounded by its content and would stop short of the paper + * edge, while a page background takes its geometry from the canvas and + * reaches all four. Order matters — the tile is listed after the band so + * it paints over it.

    + */ + static void renderChrome(DocumentSession document) { + document.pageBackgrounds(List.of( + PageBackgroundFill.topBandPoints(MASTHEAD_HEIGHT, PAGE_HEIGHT, SLATE), + new PageBackgroundFill(0.0, 0.0, + TILE_WIDTH / PAGE_WIDTH, MASTHEAD_HEIGHT / PAGE_HEIGHT, ACCENT), + new PageBackgroundFill( + (PAGE_MARGIN + SIDEBAR_WIDTH + HALF_GAP) / PAGE_WIDTH, + (MASTHEAD_HEIGHT + BODY_TOP) / PAGE_HEIGHT, + RULE_THICKNESS / PAGE_WIDTH, + (PAGE_HEIGHT - MASTHEAD_HEIGHT - BODY_TOP - PAGE_MARGIN) / PAGE_HEIGHT, + RULE), + new PageBackgroundFill( + CREDENTIALS_DIVIDER_X, CREDENTIALS_DIVIDER_Y, + RULE_THICKNESS / PAGE_WIDTH, CREDENTIALS_DIVIDER_HEIGHT, + RULE_FAINT))); + } + + /** + * The band's content, as four cells of one top-level row. + * + *

    The row's height is set by the hairline cell rather than by whichever + * text block happens to be tallest: that cell's padding and its line add + * up to the band's height exactly, so the body below starts where the + * slate fill ends however the type is later adjusted.

    + */ + static void render(PageFlowBuilder page, CvIdentity identity, ParagraphSection specialisms) { + page.addRow("Masthead", row -> { + row.spacing(0); + row.columns( + DocumentRowColumn.fixed(TILE_WIDTH), + DocumentRowColumn.fixed(IDENTITY_WIDTH), + DocumentRowColumn.fixed(HAIRLINE_THICKNESS), + // The remainder, not a fourth measured width: four fixed + // columns summing to the page leave the engine nothing to + // round with, and it refuses the row by a third of a point. + DocumentRowColumn.weight(1.0)); + row.addSection("Monogram", tile -> renderTile(tile, identity)); + row.addSection("Identity", block -> renderIdentity(block, identity, specialisms)); + row.addSection("MastheadHairline", SlateOrangeMasthead::renderHairline); + row.addSection("Contact", block -> renderContact(block, identity)); + }); + } + + // -- the tile ---------------------------------------------------------- + + /** + * The initials and the short rule under them, centred in the tile. + * + *

    Both are centred on the cell rather than positioned at a measured x, + * so they follow the same constant the tile's fill uses. The initials come + * from the name itself: a document states its name once, and a second + * place to keep true is a second place to be wrong.

    + */ + private static void renderTile(SectionBuilder tile, CvIdentity identity) { + tile.spacing(0); + tile.padding((float) MONOGRAM_TOP, 0f, 0f, 0f); + tile.addParagraph(p -> p + .name("Initials") + .text(initials(identity)) + .align(TextAlign.CENTER) + .lineSpacing(0) + .textStyle(style(DISPLAY_FONT, MONOGRAM_SIZE, MASTHEAD_INK, true)) + .margin(0f, 0f, (float) INITIALS_TO_RULE, 0f)); + tile.addAligned(HorizontalAlign.CENTER, new LineBuilder() + .name("MonogramRule") + .horizontal(MONOGRAM_RULE_WIDTH) + .thickness(MONOGRAM_RULE_THICKNESS) + .color(MASTHEAD_INK) + .build()); + } + + private static String initials(CvIdentity identity) { + StringBuilder out = new StringBuilder(2); + appendInitial(out, identity.name().first()); + appendInitial(out, identity.name().last()); + return out.toString(); + } + + private static void appendInitial(StringBuilder out, String part) { + if (!part.isBlank()) { + out.append(Character.toUpperCase(part.charAt(0))); + } + } + + // -- the identity strip ------------------------------------------------ + + /** Name, role line and specialism strip, stacked on one left edge. */ + private static void renderIdentity(SectionBuilder block, CvIdentity identity, + ParagraphSection specialisms) { + block.spacing(0); + block.padding((float) IDENTITY_TOP, 0f, 0f, (float) PAGE_MARGIN); + block.addParagraph(p -> p + .name("Name") + .text(identity.name().full().toUpperCase(Locale.ROOT)) + .lineSpacing(0) + .textStyle(style(DISPLAY_FONT, NAME_SIZE, MASTHEAD_INK, true)) + .margin(0f, 0f, (float) NAME_TO_ROLE, 0f)); + block.addParagraph(p -> p + .name("RoleLine") + .text(identity.jobTitle().toUpperCase(Locale.ROOT)) + .lineSpacing(0) + .textStyle(style(DISPLAY_FONT, ROLE_LINE_SIZE, ACCENT, true)) + .margin(0f, 0f, (float) ROLE_TO_TAGLINE, 0f)); + if (specialisms == null || specialisms.body().isBlank()) { + return; + } + block.addParagraph(p -> p + .name("Tagline") + .text(String.join(TAGLINE_SEPARATOR, lines(specialisms.body()))) + .lineSpacing(0) + .textStyle(style(DISPLAY_FONT, TAGLINE_SIZE, MASTHEAD_MUTED, false))); + } + + /** The specialisms, one per line the document wrote. */ + private static List lines(String body) { + List out = new ArrayList<>(); + for (String line : body.split(String.valueOf((char) 10))) { + if (!line.isBlank()) { + out.add(line.strip().toUpperCase(Locale.ROOT)); + } + } + return out; + } + + // -- the hairline ------------------------------------------------------ + + /** + * The vertical accent between the two halves of the band. It is a cell of + * the row, so its horizontal position comes from the row's own column + * widths rather than from a measured x — and its padding is what gives the + * row its height. + */ + private static void renderHairline(SectionBuilder cell) { + cell.spacing(0); + cell.padding((float) HAIRLINE_TOP, 0f, (float) HAIRLINE_BOTTOM, 0f); + cell.addLine(line -> line + .name("MastheadHairlineRule") + .vertical(HAIRLINE_HEIGHT) + .thickness(HAIRLINE_THICKNESS) + .color(ACCENT)); + } + + // -- the contact column ------------------------------------------------ + + /** + * The contact lines. + * + *

    Each is one paragraph rather than a table: the mark is an inline run + * measured into the line box, so it shares a baseline with its value by + * construction, and nothing here has to line up with a column. Where the + * value points somewhere the run becomes an inline link, so the target + * reaches the PDF as a real annotation rather than as text that merely + * looks like one.

    + * + *

    A link shows its own label and carries the address behind it, so the + * column's width does not depend on how long a profile happens to be + * called.

    + */ + private static void renderContact(SectionBuilder block, CvIdentity identity) { + block.spacing(0); + block.padding((float) CONTACT_TOP, 0f, 0f, (float) CONTACT_PAD_LEFT); + DocumentTextStyle valueStyle = style(BODY_FONT, CONTACT_SIZE, MASTHEAD_INK, false); + List channels = channels(identity); + for (int i = 0; i < channels.size(); i++) { + Channel channel = channels.get(i); + boolean last = i == channels.size() - 1; + int index = i; + block.addParagraph(p -> { + p.name("Contact_" + index + "_" + channel.token()); + p.lineSpacing(0); + inlineIcon(p, channel.token()); + p.inlineText(" ", valueStyle); + if (channel.href() == null || channel.href().isBlank()) { + p.inlineText(channel.value(), valueStyle); + } else { + p.inlineText(channel.value(), valueStyle, + new DocumentLinkOptions(channel.href())); + } + p.margin(0f, 0f, + last ? 0f : (float) gap(CONTACT_PITCH, CONTACT_SIZE * LINE_FACTOR), 0f); + }); + } + } + + /** The column's order: how to call, how to write, the links, then where. */ + private static List channels(CvIdentity identity) { + Contact contact = identity.contact(); + List channels = new ArrayList<>(); + channels.add(new Channel(SlateOrangeIcons.PHONE, contact.phone(), + ContactUri.tel(contact.phone()))); + channels.add(new Channel(SlateOrangeIcons.EMAIL, contact.email(), + "mailto:" + contact.email())); + for (Link link : identity.links()) { + channels.add(new Channel(SlateOrangeIcons.LINKEDIN, link.label(), link.url())); + } + channels.add(new Channel(SlateOrangeIcons.LOCATION, contact.address(), null)); + return channels; + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/SlateOrangeStyles.java b/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/SlateOrangeStyles.java new file mode 100644 index 000000000..0ab50afd2 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/SlateOrangeStyles.java @@ -0,0 +1,342 @@ +package com.demcha.compose.document.templates.cv.presets; + +import com.demcha.compose.document.api.DocumentPageSize; +import com.demcha.compose.document.style.DocumentColor; +import com.demcha.compose.document.style.DocumentInsets; +import com.demcha.compose.document.style.DocumentStroke; +import com.demcha.compose.document.style.DocumentTextDecoration; +import com.demcha.compose.document.style.DocumentTextStyle; +import com.demcha.compose.document.table.DocumentTableStyle; +import com.demcha.compose.document.table.DocumentTableTextAnchor; +import com.demcha.compose.font.FontName; + +/** + * The measured geometry, palette and type scale of the Slate Orange CV. + * + *

    Everything horizontal derives from four numbers

    + * + *

    {@link #PAGE_WIDTH}, {@link #PAGE_MARGIN}, {@link #SIDEBAR_WEIGHT} and + * {@link #COLUMN_GAP}: the page-background bands, the body row's weights and + * every table's column widths read the same four, so a fill and the content + * sitting on it cannot drift apart. Only genuinely independent marks — a rule + * thickness, a marker diameter — are written as points.

    + * + *

    Vertical pitches are measured, then converted

    + * + *

    The engine's line box is {@link #LINE_FACTOR} times the type size and + * {@code lineSpacing} adds points on top of it, so a measured pitch minus the + * line it has to clear is the gap that produces it — {@link #gap} and + * {@link #leading} are that conversion. A CSS-style multiple would be + * silently wrong.

    + */ +final class SlateOrangeStyles { + + private SlateOrangeStyles() { + } + + // -- type -------------------------------------------------------------- + + /** The condensed display face: name, monogram and every section heading. */ + static final FontName DISPLAY_FONT = FontName.ASAP_CONDENSED; + + /** + * The body face: everything the display face does not set. + * + *

    Condensed, and chosen by measurement rather than by eye. Against the + * design on three separate strings — a summary line, an experience bullet + * and a competency label — this face averages within one per cent, where + * the face its letterforms most resemble runs a tenth to a quarter too + * wide on all three. That is the difference between a bullet on one line + * and a bullet on two.

    + */ + static final FontName BODY_FONT = FontName.FIRA_SANS_CONDENSED; + + static final double MONOGRAM_SIZE = 50.0; + static final double NAME_SIZE = 40.5; + static final double ROLE_LINE_SIZE = 16.5; + static final double TAGLINE_SIZE = 10.5; + static final double CONTACT_SIZE = 9.6; + static final double HEADING_SIZE = 12.5; + + /** + * The body size, set from the design's line WIDTH rather than its ink + * height, because the two disagree here: matched on height the summary ran + * to six lines where the design has five and every line broke on a + * different word. Matching the width sets the type a few per cent small + * and puts every break back where the design has it. + */ + static final double BODY_SIZE = 9.4; + + /** + * The sidebar label size, set the same way and then backed off a quarter + * point: the longest additional-information label needed all but four + * points of its column and the engine broke it anyway. One size has to + * hold both sidebar blocks, because the design sets both at one size. + */ + static final double LABEL_SIZE = 8.65; + + static final double ACHIEVEMENT_TITLE_SIZE = 9.4; + static final double ACHIEVEMENT_BODY_SIZE = 8.3; + static final double LANGUAGE_SIZE = 8.8; + static final double LEVEL_SIZE = 6.2; + static final double ROLE_SIZE = 10.35; + static final double EMPLOYER_SIZE = 9.1; + static final double PERIOD_SIZE = 8.1; + static final double HIGHLIGHT_SIZE = 9.05; + static final double DEGREE_SIZE = 8.7; + static final double EDUCATION_LINE_SIZE = 8.6; + static final double CREDENTIAL_SIZE = 7.25; + + /** + * The credential headings, which the design sets smaller than the ones + * above them. Not an oversight to be tidied into {@link #HEADING_SIZE}: + * the two credential columns are a footer rather than a third and fourth + * section of the page, and the design's caps measure smaller to say so. + */ + static final double CREDENTIAL_HEADING_SIZE = 10.0; + + /** The engine's line box as a multiple of the type size. */ + static final double LINE_FACTOR = 1.2; + + // -- palette ----------------------------------------------------------- + + static final DocumentColor SLATE = DocumentColor.rgb(34, 46, 56); + static final DocumentColor ACCENT = DocumentColor.rgb(194, 96, 72); + static final DocumentColor INK = DocumentColor.rgb(46, 57, 68); + static final DocumentColor MASTHEAD_INK = DocumentColor.WHITE; + static final DocumentColor MASTHEAD_MUTED = DocumentColor.rgb(216, 220, 224); + static final DocumentColor MUTED = DocumentColor.rgb(138, 144, 153); + static final DocumentColor RULE = DocumentColor.rgb(200, 200, 202); + static final DocumentColor RULE_FAINT = DocumentColor.rgb(227, 227, 227); + static final DocumentColor RATING_EMPTY = DocumentColor.rgb(202, 205, 207); + + // -- the page ---------------------------------------------------------- + + static final double PAGE_WIDTH = 595.276; + static final double PAGE_HEIGHT = 841.89; + static final DocumentPageSize PAGE = DocumentPageSize.A4; + static final double PAGE_MARGIN = 22.0; + static final double CONTENT_WIDTH = PAGE_WIDTH - 2.0 * PAGE_MARGIN; + + static final double MASTHEAD_HEIGHT = 136.6; + static final double TILE_WIDTH = 85.8; + static final double COLUMN_GAP = 45.7; + static final double SIDEBAR_WEIGHT = 0.31514; + static final double MAIN_WEIGHT = 1.0 - SIDEBAR_WEIGHT; + static final double COLUMNS_WIDTH = CONTENT_WIDTH - COLUMN_GAP; + static final double SIDEBAR_WIDTH = COLUMNS_WIDTH * SIDEBAR_WEIGHT; + static final double MAIN_WIDTH = COLUMNS_WIDTH * MAIN_WEIGHT; + + /** + * The body row carries no padding and no spacing of its own: each column + * pads itself and half the gap goes to each side, so the page margin and + * the gap live inside the cells and the cell weights are what is left once + * they are added back on. + */ + static final double HALF_GAP = COLUMN_GAP / 2.0; + static final double SIDEBAR_CELL = SIDEBAR_WIDTH + PAGE_MARGIN + HALF_GAP; + static final double MAIN_CELL = MAIN_WIDTH + PAGE_MARGIN + HALF_GAP; + static final double SIDEBAR_CELL_WEIGHT = SIDEBAR_CELL / PAGE_WIDTH; + static final double MAIN_CELL_WEIGHT = MAIN_CELL / PAGE_WIDTH; + + // -- rules and marks --------------------------------------------------- + + static final double RULE_THICKNESS = 0.6; + static final double HAIRLINE_THICKNESS = 1.2; + static final double MONOGRAM_RULE_THICKNESS = 1.7; + static final double MARKER_DIAMETER = 5.6; + static final double RATING_DOT_DIAMETER = 5.6; + + /** + * A hundredth of a point of slack under every table. The engine compares a + * table's declared width against its container's to the last bit, and a + * sum of derived doubles lands a ten-thousandth of a point over — enough + * to be refused. Taking it off the last column leaves every other column + * exactly where the measurement put it. + */ + static final double TABLE_SLACK = 0.01; + + /** + * No cell borders. + * + *

    A table draws its rules per cell from that cell's own style, and the + * default style has one. The tables here are a layout mechanism rather + * than a grid the reader should see, so every cell states the absence + * explicitly: a zero-width, fully transparent stroke.

    + */ + static final DocumentStroke NO_BORDER = + DocumentStroke.of(DocumentColor.rgba(0, 0, 0, 0), 0.0); + + // -- the masthead ------------------------------------------------------ + + static final double HAIRLINE_X = 383.7; + static final double IDENTITY_WIDTH = HAIRLINE_X - TILE_WIDTH; + static final double HAIRLINE_TOP = 34.4; + static final double HAIRLINE_HEIGHT = 79.0; + static final double HAIRLINE_BOTTOM = MASTHEAD_HEIGHT - HAIRLINE_TOP - HAIRLINE_HEIGHT; + static final double MONOGRAM_TOP = 35.8; + static final double MONOGRAM_RULE_WIDTH = 37.2; + static final double INITIALS_TO_RULE = 1.7; + static final double IDENTITY_TOP = 27.4; + static final double NAME_TO_ROLE = 1.5; + static final double ROLE_TO_TAGLINE = 7.5; + static final double CONTACT_TOP = 36.8; + static final double CONTACT_PAD_LEFT = 21.4; + static final double CONTACT_PITCH = 21.1; + + /** + * The specialism strip's separator. + * + *

    Nine spaces a side rather than two: the design tracks that whole + * line, a text style carries no letter-spacing, and padding the separator + * recovers the width without putting spaces inside a word — which is what + * the extracted text would then say the document contains.

    + */ + static final String TAGLINE_SEPARATOR = " • "; + + // -- the rhythm between blocks ----------------------------------------- + + static final double BODY_TOP = 18.5; + static final double HEADING_TO_RULE = 3.5; + static final double RULE_TO_SIDEBAR_BODY = 9.5; + static final double RULE_TO_MAIN_BODY = 13.0; + + /** + * The three gaps between sidebar blocks are three separate measurements + * off the design, not one constant applied three times. Deriving them from + * each other would be inventing a rhythm the design does not have. + */ + static final double ACHIEVEMENTS_TOP_GAP = 23.6; + static final double LANGUAGES_TOP_GAP = 17.3; + static final double ADDITIONAL_TOP_GAP = 23.9; + + static final double ICON_COLUMN = 24.8; + + /** + * The closing block's icon column, which the design makes narrower than + * the competencies'. Worth its own constant rather than rounding to the + * shared one: the longest label in that block cleared the shared column by + * less than the rounding and broke onto a second line. + */ + static final double ADDITIONAL_ICON_COLUMN = 22.9; + + static final double SIDEBAR_TABLE_WIDTH = SIDEBAR_WIDTH - TABLE_SLACK; + static final double COMPETENCY_PITCH = 23.15; + static final double ACHIEVEMENT_BODY_LEADING = 11.3; + + /** + * Half the gap the design shows between achievements, because the trophy's + * own line box is taller than the title beside it and a table row is as + * tall as its tallest cell — the mark has already paid for the rest. + */ + static final double ACHIEVEMENT_GAP = 3.5; + + static final double LANGUAGE_PITCH = 16.4; + static final double ADDITIONAL_PITCH = 25.1; + static final double LANGUAGE_NAME_COLUMN = SIDEBAR_WIDTH * 0.230; + static final double LANGUAGE_LEVEL_COLUMN = SIDEBAR_WIDTH * 0.407; + static final double LANGUAGE_RATING_COLUMN = + SIDEBAR_TABLE_WIDTH - LANGUAGE_NAME_COLUMN - LANGUAGE_LEVEL_COLUMN; + + static final double PROFILE_LEADING = 13.83; + static final double PROFILE_TO_EXPERIENCE = 23.0; + static final double ENTRY_INDENT = 13.0; + static final double ENTRY_WIDTH = MAIN_WIDTH - ENTRY_INDENT - TABLE_SLACK; + + /** + * The design's three inter-entry gaps differ, and this is the one value + * that has to serve all three. It sits at the low end of that range rather + * than the middle: the body row is atomic and the page has about a point + * of slack, so a gap that split the difference would cost a second page. + */ + static final double ENTRY_GAP = 18.4; + + static final double ROLE_TO_EMPLOYER = 2.0; + static final double EMPLOYER_TO_HIGHLIGHTS = 6.6; + static final double HIGHLIGHT_PITCH = 14.96; + static final double HIGHLIGHT_LEADING = 12.99; + static final double PERIOD_COLUMN = ENTRY_WIDTH * 0.30; + static final double ROLE_COLUMN = ENTRY_WIDTH - PERIOD_COLUMN; + + static final double EXPERIENCE_TO_RULE = 12.0; + static final double RULE_TO_CREDENTIALS = 12.1; + static final double CREDENTIAL_LEFT_COLUMN = MAIN_WIDTH * 0.400; + static final double CREDENTIAL_GUTTER = MAIN_WIDTH * 0.0585; + static final double CREDENTIAL_TABLE_WIDTH = MAIN_WIDTH - TABLE_SLACK; + static final double CREDENTIAL_RIGHT_COLUMN = + CREDENTIAL_TABLE_WIDTH - CREDENTIAL_LEFT_COLUMN - CREDENTIAL_GUTTER; + + /** + * The credential headings sit closer to their rules than the headings + * above them do. A table row's padding is not a paragraph's margin — it + * has no line box to clear — so the same visual gap is a smaller number + * here. + */ + static final double CREDENTIAL_HEADING_TO_RULE = 2.5; + static final double CREDENTIAL_RULE_TO_BODY = 6.5; + static final double EDUCATION_LINE_PITCH = 12.4; + static final double CREDENTIAL_LINE_PITCH = 11.9; + + /** A heading rule inside a credential column, as a filled hairline row. */ + static final double CREDENTIAL_RULE_SIZE = 0.5; + + /** + * The credentials divider is the one mark whose extent is measured rather + * than derived: a table cell's stroke is a box with no per-edge control, + * so the divider cannot be the right column's own border and is a page + * background like the three above it. + */ + static final double CREDENTIALS_DIVIDER_X = 0.61422; + static final double CREDENTIALS_DIVIDER_Y = 0.89671; + static final double CREDENTIALS_DIVIDER_HEIGHT = 0.07713; + + // -- helpers ----------------------------------------------------------- + + static DocumentTextStyle style(FontName font, double size, DocumentColor color, + boolean bold) { + return DocumentTextStyle.builder() + .fontName(font) + .size(size) + .color(color) + .decoration(bold ? DocumentTextDecoration.BOLD : DocumentTextDecoration.DEFAULT) + .build(); + } + + static DocumentTextStyle italic(FontName font, double size, DocumentColor color) { + return DocumentTextStyle.builder() + .fontName(font) + .size(size) + .color(color) + .decoration(DocumentTextDecoration.ITALIC) + .build(); + } + + /** A cell's padding and how its content sits in it. */ + static DocumentTableStyle cellStyle(DocumentInsets padding, + DocumentTableTextAnchor anchor) { + return DocumentTableStyle.builder() + .padding(padding) + .textAnchor(anchor) + .stroke(NO_BORDER) + .build(); + } + + /** A measured pitch, minus the content it has to clear, is the gap that produces it. */ + static double gap(double pitch, double contentHeight) { + return Math.max(0.0, pitch - contentHeight); + } + + /** + * A measured leading, as the additive value {@code lineSpacing} actually + * is: the line box is {@link #LINE_FACTOR} times the type size and the + * setting adds points on top of it. + */ + static double leading(double measuredLeading, double size) { + return Math.max(0.0, measuredLeading - LINE_FACTOR * size); + } + + /** A node name built from a heading, with the spacing taken out of it. */ + static String compact(String value) { + return value == null ? "" : value.replaceAll("[^A-Za-z0-9]", ""); + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/SlateOrangeWidgets.java b/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/SlateOrangeWidgets.java new file mode 100644 index 000000000..c44000072 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/SlateOrangeWidgets.java @@ -0,0 +1,162 @@ +package com.demcha.compose.document.templates.cv.presets; + +import com.demcha.compose.document.dsl.ParagraphBuilder; +import com.demcha.compose.document.dsl.SectionBuilder; +import com.demcha.compose.document.node.DocumentNode; +import com.demcha.compose.document.node.InlineImageAlignment; +import com.demcha.compose.document.style.DocumentInsets; +import com.demcha.compose.document.style.DocumentTextStyle; +import com.demcha.compose.document.table.DocumentTableCell; +import com.demcha.compose.document.table.DocumentTableColumn; +import com.demcha.compose.document.table.DocumentTableTextAnchor; +import com.demcha.compose.document.templates.cv.data.CvEntry; + +import java.util.List; + +import static com.demcha.compose.document.templates.cv.presets.SlateOrangeStyles.ACCENT; +import static com.demcha.compose.document.templates.cv.presets.SlateOrangeStyles.BODY_FONT; +import static com.demcha.compose.document.templates.cv.presets.SlateOrangeStyles.CREDENTIAL_RULE_SIZE; +import static com.demcha.compose.document.templates.cv.presets.SlateOrangeStyles.DISPLAY_FONT; +import static com.demcha.compose.document.templates.cv.presets.SlateOrangeStyles.HEADING_SIZE; +import static com.demcha.compose.document.templates.cv.presets.SlateOrangeStyles.HEADING_TO_RULE; +import static com.demcha.compose.document.templates.cv.presets.SlateOrangeStyles.INK; +import static com.demcha.compose.document.templates.cv.presets.SlateOrangeStyles.LABEL_SIZE; +import static com.demcha.compose.document.templates.cv.presets.SlateOrangeStyles.LINE_FACTOR; +import static com.demcha.compose.document.templates.cv.presets.SlateOrangeStyles.RULE_THICKNESS; +import static com.demcha.compose.document.templates.cv.presets.SlateOrangeStyles.RULE_TO_SIDEBAR_BODY; +import static com.demcha.compose.document.templates.cv.presets.SlateOrangeStyles.SIDEBAR_TABLE_WIDTH; +import static com.demcha.compose.document.templates.cv.presets.SlateOrangeStyles.cellStyle; +import static com.demcha.compose.document.templates.cv.presets.SlateOrangeStyles.compact; +import static com.demcha.compose.document.templates.cv.presets.SlateOrangeStyles.gap; +import static com.demcha.compose.document.templates.cv.presets.SlateOrangeStyles.style; + +/** + * The idioms this sheet repeats: the heading over its rule, a mark as a + * cell's node, a line of text as a cell's node, the empty cell, and the + * mark-and-label table both sidebar blocks are built from. + */ +final class SlateOrangeWidgets { + + private SlateOrangeWidgets() { + } + + /** + * A caps heading over its orange rule. + * + *

    The rule is filled rather than given a width, so it spans whichever + * column the heading is in — one method serves the sidebar and the main + * column alike. The credential columns cannot use it, because they live + * inside table cells where a line node is not drawn; their rules are + * filled cells instead.

    + */ + static void heading(SectionBuilder block, String name, String text) { + block.addParagraph(p -> p + .name(name + "Heading") + .text(text) + .lineSpacing(0) + .textStyle(style(DISPLAY_FONT, HEADING_SIZE, INK, true)) + .margin(0f, 0f, (float) HEADING_TO_RULE, 0f)); + block.addLine(line -> line + .name(name + "HeadingRule") + .fill() + .thickness(RULE_THICKNESS) + .color(ACCENT)); + } + + /** One line of text as a cell's single node. */ + static DocumentNode text(String name, String value, DocumentTextStyle textStyle) { + return new ParagraphBuilder() + .name(name) + .text(value) + .lineSpacing(0) + .textStyle(textStyle) + .build(); + } + + /** An empty cell that still has to be a paragraph, because a cell holds a node. */ + static DocumentNode spacer(String name) { + return new ParagraphBuilder() + .name(name) + .text(" ") + .lineSpacing(0) + .textStyle(style(BODY_FONT, CREDENTIAL_RULE_SIZE, INK, false)) + .build(); + } + + /** Places a mark inline, on the baseline of the paragraph it opens. */ + static void inlineIcon(ParagraphBuilder paragraph, String token) { + paragraph.inlineSvgIcon(SlateOrangeIcons.icon(token), SlateOrangeIcons.size(token), + InlineImageAlignment.CENTER); + } + + /** + * A mark on its own, as a cell's single node. + * + *

    Styled at the size of the label beside it although it carries no + * text: the default line box would set the row's pitch instead of the mark + * doing so. An inline run is measured into that box, so a mark taller than + * the label would be drawn clipped to it — whichever is larger wins.

    + */ + static DocumentNode iconCell(String name, String token, double labelSize) { + double boxSize = Math.max(labelSize, SlateOrangeIcons.size(token) / LINE_FACTOR); + ParagraphBuilder paragraph = new ParagraphBuilder(); + paragraph.name(name); + paragraph.lineSpacing(0); + paragraph.textStyle(style(BODY_FONT, boxSize, INK, false)); + inlineIcon(paragraph, token); + return paragraph.build(); + } + + /** + * The shape both mark-and-label blocks share: a fixed mark column beside a + * label column, one table row per entry. + * + *

    The pitch is the table's own row padding, so adding an eleventh entry + * changes nothing about the spacing — the alternative, a bottom margin + * repeated on every label, is the same fact stated ten times. It is a gap + * BETWEEN rows, so the last row does not carry it: there it would become a + * gap between this block and the next, which the next block already + * owns.

    + */ + static void iconTable(SectionBuilder block, String tableName, String rowName, + List entries, double pitch, double iconColumn) { + block.addTable(table -> { + table.name(tableName); + table.margin(new DocumentInsets(RULE_TO_SIDEBAR_BODY, 0, 0, 0)); + table.width(SIDEBAR_TABLE_WIDTH); + table.columns( + DocumentTableColumn.fixed(iconColumn), + DocumentTableColumn.fixed(SIDEBAR_TABLE_WIDTH - iconColumn)); + table.defaultCellStyle(cellStyle( + new DocumentInsets(0, 0, gap(pitch, rowHeight(entries)), 0), + DocumentTableTextAnchor.CENTER_LEFT)); + for (int index = 0; index < entries.size(); index++) { + CvEntry entry = entries.get(index); + table.rowCells( + DocumentTableCell.node(entry.icon().isBlank() + ? spacer(rowName + "Icon_" + index) + : iconCell(rowName + "Icon_" + index, entry.icon(), LABEL_SIZE)), + DocumentTableCell.node(text(rowName + "Label_" + index, entry.title(), + style(BODY_FONT, LABEL_SIZE, INK, false)))); + } + table.rowStyle(entries.size() - 1, + cellStyle(DocumentInsets.zero(), DocumentTableTextAnchor.CENTER_LEFT)); + }); + } + + /** A row is as tall as the tallest mark in the block or its label's line box. */ + private static double rowHeight(List entries) { + double tallest = LABEL_SIZE * LINE_FACTOR; + for (CvEntry entry : entries) { + if (!entry.icon().isBlank()) { + tallest = Math.max(tallest, SlateOrangeIcons.size(entry.icon())); + } + } + return tallest; + } + + /** A node name built from a heading, with the spacing taken out of it. */ + static String nameOf(String value) { + return compact(value); + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/TealPulse.java b/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/TealPulse.java new file mode 100644 index 000000000..a5b054406 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/TealPulse.java @@ -0,0 +1,220 @@ +package com.demcha.compose.document.templates.cv.presets; + +import com.demcha.compose.document.api.DocumentSession; +import com.demcha.compose.document.style.DocumentInsets; +import com.demcha.compose.document.templates.api.DocumentTemplate; +import com.demcha.compose.document.templates.cv.components.SectionLookup; +import com.demcha.compose.document.templates.cv.data.CvDocument; +import com.demcha.compose.document.templates.cv.data.CvSection; +import com.demcha.compose.document.templates.cv.data.EntriesSection; +import com.demcha.compose.document.templates.cv.data.ParagraphSection; +import com.demcha.compose.document.templates.cv.data.SkillsSection; + +import java.util.List; +import java.util.Objects; + +import static com.demcha.compose.document.templates.cv.presets.TealPulseStyles.MARGIN_X; +import static com.demcha.compose.document.templates.cv.presets.TealPulseStyles.PAD_BOTTOM; +import static com.demcha.compose.document.templates.cv.presets.TealPulseStyles.PAD_TOP; +import static com.demcha.compose.document.templates.cv.presets.TealPulseStyles.PAGE; + +/** + * Teal Pulse — a clinical one-page CV in five stacked bands: a heart crossed + * by a pulse beside the name, a contact strip divided by short rules, a + * two-column body carrying the competencies beside the summary and the roles, + * a three-column closing band, and a tracked line under a rule that ends in a + * small heart. + * + *

    Exactly one of those bands is two-column, and both vertical rules on the + * sheet are the left border of the column to their right rather than lines + * placed beside it — so they are the grid: change a weight and the rules + * follow.

    + * + *

    The page is the design's, not the caller's

    + * + *

    The design was drawn on a raster whose proportion is not A4's, and every + * length on the sheet is a share of that grid, so the preset sets its own page + * size and margin. A session configured by the caller is overwritten. This is + * the opposite choice from {@link CharcoalGold}, which follows the page it is + * given, and it is made here because the sheet is a fixed composition rather + * than a layout that rescales.

    + * + *

    One page, and what happens past it

    + * + *

    The sheet is drawn for one page but does not insist on it, which sets it + * apart from the other ported presets in this package. The bands are stacked + * in the page flow, so a CV with more roles than the design holds carries the + * closing band onto a second page rather than losing anything — it will look + * like a one-page design that ran over, because that is what it is.

    + * + *

    What cannot flow is the body itself: its two columns are a single row, + * and a row is atomic. A body taller than one page — the competencies beside + * the summary and every role — raises {@code AtomicNodeTooLargeException}, + * naming the node and the height it needed. The preset draws no cap of its + * own, because a CV that quietly loses a role is worse than one that refuses + * to compose. {@link TimelineMinimal} is the preset in this package that + * splits its own columns across pages.

    + * + *

    How a document reaches its berth

    + * + *

    The bands are fixed, so the preset assigns each section a berth by title + * rather than reading {@link + * com.demcha.compose.document.templates.cv.data.Slot}. It has six:

    + * + *
      + *
    • competencies, a {@link SkillsSection} — the narrow column;
    • + *
    • the summary, a {@link ParagraphSection}, and experience, an + * {@link EntriesSection} whose bodies are one highlight per line — the + * wide column;
    • + *
    • education, certifications and the closing facts, + * {@link EntriesSection}s — the three columns of the closing band;
    • + *
    • the tagline, a {@link ParagraphSection} whose body is the line under + * the closing rule. Only its body is drawn: the title is how the + * document names the berth, not something the sheet prints.
    • + *
    + * + *

    A section whose title matches no berth is not drawn, and a berth with no + * section takes its badge and heading with it. The competencies are skills + * without levels: this design writes them as dotted lines, so a rating would + * have nowhere to go.

    + * + *

    The marks, the tracking and the links

    + * + *

    Every mark is chrome — the brand's two halves, the four contact glyphs, + * the five badges and the closing heart — so no document names one, and the + * badge each band carries is fixed by the berth it heads.

    + * + *

    Headings are letter-spaced by writing each character as its own run with + * a small space between: a text style carries no letter-spacing, and the gap + * is split across several spacers because a run's size sets its line's height + * as well as its advance.

    + * + *

    The email, the phone and each link are reachable from the PDF, with the + * {@code mailto:} and {@code tel:} targets built from the values. A link is + * drawn as its own label with the address behind it — {@code Link("LinkedIn", + * "https://…")} sets the word and links the URL — so the strip's gaps do not + * depend on how long a profile's address happens to be. A role and a degree + * become links when their entry carries {@code CvEntry.link()}.

    + * + *

    Fonts

    + * + *

    The sheet is set in Lato, with Poppins for the name and Barlow Condensed + * for the headings. The templates artifact carries none of them — register + * them on the session, or the engine substitutes and the measured geometry + * here no longer matches the type sitting in it.

    + * + *

    Usage:

    + *
    {@code
    + * DocumentTemplate template = TealPulse.create();
    + * template.compose(session, cv);
    + * }
    + * + * @since 2.4.0 + */ +public final class TealPulse { + + /** Stable identifier of this preset. */ + public static final String ID = "teal-pulse"; + + /** Human-readable name of this preset. */ + public static final String DISPLAY_NAME = "Teal Pulse"; + + private static final List COMPETENCY_KEYS = + List.of("competencies", "competency", "skills", "expertise"); + private static final List SUMMARY_KEYS = + List.of("summary", "profile", "about"); + private static final List TAGLINE_KEYS = + List.of("tagline", "motto", "closing"); + private static final List EXPERIENCE_KEYS = + List.of("experience", "employment", "work history", "career"); + private static final List EDUCATION_KEYS = + List.of("education", "qualifications"); + private static final List CERTIFICATION_KEYS = + List.of("certifications", "certification", "licences", "licenses"); + private static final List FACT_KEYS = + List.of("additional", "details", "other"); + + private TealPulse() { + } + + /** + * Creates the template. + * + * @return a template composing a {@link CvDocument} + */ + public static DocumentTemplate create() { + return new Template(); + } + + private record Template() implements DocumentTemplate { + + @Override + public String id() { + return ID; + } + + @Override + public String displayName() { + return DISPLAY_NAME; + } + + @Override + public void compose(DocumentSession document, CvDocument doc) { + Objects.requireNonNull(document, "document"); + Objects.requireNonNull(doc, "doc"); + List sections = doc.sections(); + + SkillsSection competencies = berth(sections, SkillsSection.class, COMPETENCY_KEYS); + // The tagline is claimed first: its own keys are narrow, but the + // summary's are broad enough to take a closing line titled loosely. + ParagraphSection tagline = berth(sections, ParagraphSection.class, TAGLINE_KEYS); + ParagraphSection summary = + berthExcept(sections, ParagraphSection.class, SUMMARY_KEYS, tagline); + EntriesSection experience = berth(sections, EntriesSection.class, EXPERIENCE_KEYS); + EntriesSection education = berth(sections, EntriesSection.class, EDUCATION_KEYS); + EntriesSection certifications = + berth(sections, EntriesSection.class, CERTIFICATION_KEYS); + EntriesSection facts = berth(sections, EntriesSection.class, FACT_KEYS); + + // The sheet is a fixed composition on the design's own page, so the + // preset sets both rather than following what the caller configured. + document.pageSize(PAGE).margin(DocumentInsets.zero()); + document.pageFlow(page -> { + page.name("TealPulseCv"); + page.spacing(0); + page.padding(new DocumentInsets(PAD_TOP, MARGIN_X, PAD_BOTTOM, MARGIN_X)); + TealPulseMasthead.render(page, doc.identity()); + TealPulseBody.render(page, competencies, summary, experience); + TealPulseClosing.render(page, education, certifications, facts, tagline); + }); + } + + /** + * The first section of the wanted shape whose title names this berth, + * or {@code null} when the document fills no such berth. + */ + private static T berth(List sections, + Class type, + List keys) { + return berthExcept(sections, type, keys, null); + } + + /** The same, skipping a section another berth has already claimed. */ + private static T berthExcept(List sections, + Class type, + List keys, + CvSection taken) { + for (CvSection section : sections) { + if (!type.isInstance(section) || section == taken) { + continue; + } + for (String key : keys) { + if (SectionLookup.titleContains(section.title(), key)) { + return type.cast(section); + } + } + } + return null; + } + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/TealPulseBody.java b/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/TealPulseBody.java new file mode 100644 index 000000000..47a2cc99e --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/TealPulseBody.java @@ -0,0 +1,233 @@ +package com.demcha.compose.document.templates.cv.presets; + +import com.demcha.compose.document.dsl.PageFlowBuilder; +import com.demcha.compose.document.dsl.ParagraphBuilder; +import com.demcha.compose.document.dsl.SectionBuilder; +import com.demcha.compose.document.node.DocumentLinkOptions; +import com.demcha.compose.document.node.RowVerticalAlign; +import com.demcha.compose.document.node.TextAlign; +import com.demcha.compose.document.style.DocumentInsets; +import com.demcha.compose.document.style.DocumentTextDecoration; +import com.demcha.compose.document.style.DocumentTextStyle; +import com.demcha.compose.document.templates.cv.data.CvEntry; +import com.demcha.compose.document.templates.cv.data.CvSkill; +import com.demcha.compose.document.templates.cv.data.EntriesSection; +import com.demcha.compose.document.templates.cv.data.ParagraphSection; +import com.demcha.compose.document.templates.cv.data.SkillGroup; +import com.demcha.compose.document.templates.cv.data.SkillsSection; + +import java.util.ArrayList; +import java.util.List; + +import static com.demcha.compose.document.templates.cv.presets.TealPulseStyles.ACCENT; +import static com.demcha.compose.document.templates.cv.presets.TealPulseStyles.ACCENT_DEEP; +import static com.demcha.compose.document.templates.cv.presets.TealPulseStyles.BODY_FONT; +import static com.demcha.compose.document.templates.cv.presets.TealPulseStyles.BODY_TEXT; +import static com.demcha.compose.document.templates.cv.presets.TealPulseStyles.BODY_TO_CLOSING_RULE; +import static com.demcha.compose.document.templates.cv.presets.TealPulseStyles.BULLET_GAP; +import static com.demcha.compose.document.templates.cv.presets.TealPulseStyles.BULLET_LEADING; +import static com.demcha.compose.document.templates.cv.presets.TealPulseStyles.BULLET_SIZE; +import static com.demcha.compose.document.templates.cv.presets.TealPulseStyles.COMPETENCY_GAP; +import static com.demcha.compose.document.templates.cv.presets.TealPulseStyles.COMPETENCY_SIZE; +import static com.demcha.compose.document.templates.cv.presets.TealPulseStyles.DASH_TO_HEADING; +import static com.demcha.compose.document.templates.cv.presets.TealPulseStyles.DIVIDER_WIDTH; +import static com.demcha.compose.document.templates.cv.presets.TealPulseStyles.ENTRY_GAP; +import static com.demcha.compose.document.templates.cv.presets.TealPulseStyles.ENTRY_HEADLINE_SIZE; +import static com.demcha.compose.document.templates.cv.presets.TealPulseStyles.ENTRY_TO_BULLETS; +import static com.demcha.compose.document.templates.cv.presets.TealPulseStyles.HEADER_TO_BODY; +import static com.demcha.compose.document.templates.cv.presets.TealPulseStyles.HEADER_TO_ENTRY; +import static com.demcha.compose.document.templates.cv.presets.TealPulseStyles.HEADING_FONT; +import static com.demcha.compose.document.templates.cv.presets.TealPulseStyles.HEADING_TO_ITEMS; +import static com.demcha.compose.document.templates.cv.presets.TealPulseStyles.ITEMS_TO_CLOSING_RULE; +import static com.demcha.compose.document.templates.cv.presets.TealPulseStyles.MAIN_PAD_LEFT; +import static com.demcha.compose.document.templates.cv.presets.TealPulseStyles.MAIN_WEIGHT; +import static com.demcha.compose.document.templates.cv.presets.TealPulseStyles.RULE_PALE; +import static com.demcha.compose.document.templates.cv.presets.TealPulseStyles.RULE_SOFT; +import static com.demcha.compose.document.templates.cv.presets.TealPulseStyles.SIDEBAR_CLOSING_RULE_THICKNESS; +import static com.demcha.compose.document.templates.cv.presets.TealPulseStyles.SIDEBAR_CLOSING_RULE_WIDTH; +import static com.demcha.compose.document.templates.cv.presets.TealPulseStyles.SIDEBAR_DASH_THICKNESS; +import static com.demcha.compose.document.templates.cv.presets.TealPulseStyles.SIDEBAR_DASH_WIDTH; +import static com.demcha.compose.document.templates.cv.presets.TealPulseStyles.SIDEBAR_HEADING_SIZE; +import static com.demcha.compose.document.templates.cv.presets.TealPulseStyles.SIDEBAR_HEADING_TRACKING_EM; +import static com.demcha.compose.document.templates.cv.presets.TealPulseStyles.SIDEBAR_PAD_TOP; +import static com.demcha.compose.document.templates.cv.presets.TealPulseStyles.SIDEBAR_WEIGHT; +import static com.demcha.compose.document.templates.cv.presets.TealPulseStyles.SUMMARY_LEADING; +import static com.demcha.compose.document.templates.cv.presets.TealPulseStyles.SUMMARY_RIGHT_INSET; +import static com.demcha.compose.document.templates.cv.presets.TealPulseStyles.SUMMARY_SIZE; +import static com.demcha.compose.document.templates.cv.presets.TealPulseStyles.SUMMARY_TO_EXPERIENCE; +import static com.demcha.compose.document.templates.cv.presets.TealPulseStyles.style; +import static com.demcha.compose.document.templates.cv.presets.TealPulseWidgets.dottedList; +import static com.demcha.compose.document.templates.cv.presets.TealPulseWidgets.sectionHeader; +import static com.demcha.compose.document.templates.cv.presets.TealPulseWidgets.tracked; + +/** + * The body row: the competencies in the narrow column, the summary and the + * roles in the wide one. + */ +final class TealPulseBody { + + private TealPulseBody() { + } + + /** + * The one two-column band on the sheet. + * + *

    The rule between the columns is the main column's own left accent, + * not a drawn line. That is what makes its height follow the taller of the + * two columns, which is what the design shows: it runs well past the end + * of the competency list.

    + */ + static void render(PageFlowBuilder page, SkillsSection competencies, + ParagraphSection summary, EntriesSection experience) { + page.addRow("BodyGrid", row -> { + row.weights(SIDEBAR_WEIGHT, MAIN_WEIGHT); + row.verticalAlign(RowVerticalAlign.TOP); + row.spacing(0); + row.addSection("Sidebar", sidebar -> renderCompetencies(sidebar, competencies)); + row.addSection("Main", main -> { + main.spacing(0); + main.accentLeft(RULE_SOFT, DIVIDER_WIDTH); + main.padding(new DocumentInsets(0, 0, 0, MAIN_PAD_LEFT)); + renderSummary(main, summary); + renderExperience(main, experience); + }); + row.margin(new DocumentInsets(0, 0, BODY_TO_CLOSING_RULE, 0)); + }); + } + + // -- the narrow column ------------------------------------------------- + + /** A dash, a tracked heading, the dotted lines, and a pale closing rule. */ + private static void renderCompetencies(SectionBuilder section, SkillsSection competencies) { + section.spacing(0); + section.padding(new DocumentInsets(SIDEBAR_PAD_TOP, 0, 0, 0)); + if (competencies == null) { + return; + } + section.addLine(line -> line + .name("CompetenciesDash") + .horizontal(SIDEBAR_DASH_WIDTH) + .thickness(SIDEBAR_DASH_THICKNESS) + .color(ACCENT) + .margin(new DocumentInsets(0, 0, DASH_TO_HEADING, 0))); + + DocumentTextStyle headingStyle = style( + HEADING_FONT, SIDEBAR_HEADING_SIZE, ACCENT_DEEP, DocumentTextDecoration.BOLD); + ParagraphBuilder heading = new ParagraphBuilder() + .name("CompetenciesHeading") + .textStyle(headingStyle) + .align(TextAlign.LEFT) + .lineSpacing(1.0); + tracked(heading, competencies.title(), headingStyle, SIDEBAR_HEADING_TRACKING_EM); + section.add(heading.margin(new DocumentInsets(0, 0, HEADING_TO_ITEMS, 0)).build()); + + List names = new ArrayList<>(); + for (SkillGroup group : competencies.groups()) { + for (CvSkill skill : group.entries()) { + names.add(skill.name()); + } + } + dottedList(section, "CompetencyItems", names, COMPETENCY_SIZE, 1.0, COMPETENCY_GAP); + section.addLine(line -> line + .name("CompetenciesClosingRule") + .horizontal(SIDEBAR_CLOSING_RULE_WIDTH) + .thickness(SIDEBAR_CLOSING_RULE_THICKNESS) + .color(RULE_PALE) + .margin(new DocumentInsets(ITEMS_TO_CLOSING_RULE, 0, 0, 0))); + } + + // -- the wide column --------------------------------------------------- + + /** + * The summary under its badge. + * + *

    The paragraph carries a right inset because the design breaks its + * first line with a finger's width of the column still free: reproducing + * the line breaks means reproducing the measure, not only the type + * size.

    + * + *

    The summary is one flowing block here, so a body written as several + * lines is joined into one and left to wrap. This is the one berth that + * does that — everywhere else a line the document wrote is a line the + * sheet draws.

    + */ + private static void renderSummary(SectionBuilder main, ParagraphSection summary) { + if (summary == null || summary.body().isBlank()) { + return; + } + main.add(sectionHeader(summary.title(), TealPulseIcons.SUMMARY, HEADER_TO_BODY)); + main.addParagraph(paragraph -> paragraph + .name("SummaryText") + .text(summary.body().replace(String.valueOf((char) 10), " ")) + .textStyle(style(BODY_FONT, SUMMARY_SIZE, BODY_TEXT, + DocumentTextDecoration.DEFAULT)) + .align(TextAlign.LEFT) + .lineSpacing(SUMMARY_LEADING) + .margin(new DocumentInsets(0, SUMMARY_RIGHT_INSET, SUMMARY_TO_EXPERIENCE, 0))); + } + + /** The roles, each a two-voice headline over its dotted highlights. */ + private static void renderExperience(SectionBuilder main, EntriesSection experience) { + if (experience == null || experience.entries().isEmpty()) { + return; + } + main.add(sectionHeader(experience.title(), TealPulseIcons.EXPERIENCE, HEADER_TO_ENTRY)); + List entries = experience.entries(); + for (int i = 0; i < entries.size(); i++) { + CvEntry entry = entries.get(i); + boolean last = i == entries.size() - 1; + int index = i; + DocumentTextStyle roleStyle = style( + BODY_FONT, ENTRY_HEADLINE_SIZE, ACCENT_DEEP, DocumentTextDecoration.BOLD); + DocumentTextStyle metaStyle = style( + BODY_FONT, ENTRY_HEADLINE_SIZE, BODY_TEXT, DocumentTextDecoration.BOLD); + main.addParagraph(paragraph -> { + paragraph.name("ExperienceHeadline_" + index) + .textStyle(metaStyle) + .align(TextAlign.LEFT) + .lineSpacing(1.0); + if (entry.link().isBlank()) { + paragraph.inlineText(entry.title(), roleStyle); + } else { + paragraph.inlineText(entry.title(), roleStyle, + new DocumentLinkOptions(entry.link())); + } + paragraph.inlineText(headlineTail(entry), metaStyle) + .margin(new DocumentInsets(0, 0, ENTRY_TO_BULLETS, 0)); + }); + main.addSection("ExperienceHighlights_" + index, highlights -> { + highlights.spacing(0); + dottedList(highlights, "Highlights_" + index, lines(entry.body()), + BULLET_SIZE, BULLET_LEADING, BULLET_GAP); + highlights.margin(new DocumentInsets(0, 0, last ? 0 : ENTRY_GAP, 0)); + }); + } + } + + /** + * What follows the role in its headline: the employer, then the dates, + * joined the way the design joins them. A dash and a pipe are typography + * rather than content, so they live here and not in the document. + */ + private static String headlineTail(CvEntry entry) { + StringBuilder tail = new StringBuilder(); + if (!entry.subtitle().isBlank()) { + tail.append(' ').append((char) 0x2014).append(' ').append(entry.subtitle()); + } + if (!entry.date().isBlank()) { + tail.append(tail.isEmpty() ? " " : " | ").append(entry.date()); + } + return tail.toString(); + } + + /** A body, one entry per line the document wrote. */ + static List lines(String body) { + List out = new ArrayList<>(); + for (String line : body.split(String.valueOf((char) 10))) { + if (!line.isBlank()) { + out.add(line.strip()); + } + } + return out; + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/TealPulseClosing.java b/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/TealPulseClosing.java new file mode 100644 index 000000000..3dd8bd146 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/TealPulseClosing.java @@ -0,0 +1,234 @@ +package com.demcha.compose.document.templates.cv.presets; + +import com.demcha.compose.document.dsl.PageFlowBuilder; +import com.demcha.compose.document.dsl.ParagraphBuilder; +import com.demcha.compose.document.dsl.SectionBuilder; +import com.demcha.compose.document.dsl.ShapeBuilder; +import com.demcha.compose.document.node.DocumentLinkOptions; +import com.demcha.compose.document.node.DocumentNode; +import com.demcha.compose.document.node.LayerAlign; +import com.demcha.compose.document.node.RowVerticalAlign; +import com.demcha.compose.document.node.TextAlign; +import com.demcha.compose.document.style.ClipPolicy; +import com.demcha.compose.document.style.DocumentInsets; +import com.demcha.compose.document.style.DocumentTextDecoration; +import com.demcha.compose.document.style.DocumentTextStyle; +import com.demcha.compose.document.templates.cv.data.CvEntry; +import com.demcha.compose.document.templates.cv.data.EntriesSection; +import com.demcha.compose.document.templates.cv.data.ParagraphSection; + +import java.util.ArrayList; +import java.util.List; + +import static com.demcha.compose.document.templates.cv.presets.TealPulseStyles.ACCENT_DEEP; +import static com.demcha.compose.document.templates.cv.presets.TealPulseStyles.BAND_BODY_SIZE; +import static com.demcha.compose.document.templates.cv.presets.TealPulseStyles.BAND_COL1_WIDTH; +import static com.demcha.compose.document.templates.cv.presets.TealPulseStyles.BAND_COL2_WIDTH; +import static com.demcha.compose.document.templates.cv.presets.TealPulseStyles.BAND_COL3_TEXT_INSET; +import static com.demcha.compose.document.templates.cv.presets.TealPulseStyles.BAND_COL3_WIDTH; +import static com.demcha.compose.document.templates.cv.presets.TealPulseStyles.BAND_DEGREE_SIZE; +import static com.demcha.compose.document.templates.cv.presets.TealPulseStyles.BAND_FACT_GAP; +import static com.demcha.compose.document.templates.cv.presets.TealPulseStyles.BAND_LEADING; +import static com.demcha.compose.document.templates.cv.presets.TealPulseStyles.BAND_LINE_GAP; +import static com.demcha.compose.document.templates.cv.presets.TealPulseStyles.BAND_PAD_LEFT; +import static com.demcha.compose.document.templates.cv.presets.TealPulseStyles.BAND_TO_TAGLINE_RULE; +import static com.demcha.compose.document.templates.cv.presets.TealPulseStyles.BAND_WEIGHT_1; +import static com.demcha.compose.document.templates.cv.presets.TealPulseStyles.BAND_WEIGHT_2; +import static com.demcha.compose.document.templates.cv.presets.TealPulseStyles.BAND_WEIGHT_3; +import static com.demcha.compose.document.templates.cv.presets.TealPulseStyles.BODY_FONT; +import static com.demcha.compose.document.templates.cv.presets.TealPulseStyles.BODY_TEXT; +import static com.demcha.compose.document.templates.cv.presets.TealPulseStyles.CERT_ITEM_GAP; +import static com.demcha.compose.document.templates.cv.presets.TealPulseStyles.CLOSING_RULE_TO_BAND; +import static com.demcha.compose.document.templates.cv.presets.TealPulseStyles.CONTENT_WIDTH; +import static com.demcha.compose.document.templates.cv.presets.TealPulseStyles.DIVIDER_WIDTH; +import static com.demcha.compose.document.templates.cv.presets.TealPulseStyles.FULL_RULE_THICKNESS; +import static com.demcha.compose.document.templates.cv.presets.TealPulseStyles.HEADING_FONT; +import static com.demcha.compose.document.templates.cv.presets.TealPulseStyles.RULE_SOFT; +import static com.demcha.compose.document.templates.cv.presets.TealPulseStyles.RULE_STRONG; +import static com.demcha.compose.document.templates.cv.presets.TealPulseStyles.TAGLINE_RULE_HEIGHT; +import static com.demcha.compose.document.templates.cv.presets.TealPulseStyles.TAGLINE_RULE_TO_TEXT; +import static com.demcha.compose.document.templates.cv.presets.TealPulseStyles.TAGLINE_SIZE; +import static com.demcha.compose.document.templates.cv.presets.TealPulseStyles.TAGLINE_TRACKING_EM; +import static com.demcha.compose.document.templates.cv.presets.TealPulseStyles.compact; +import static com.demcha.compose.document.templates.cv.presets.TealPulseStyles.style; +import static com.demcha.compose.document.templates.cv.presets.TealPulseWidgets.columnHeader; +import static com.demcha.compose.document.templates.cv.presets.TealPulseWidgets.dottedList; +import static com.demcha.compose.document.templates.cv.presets.TealPulseWidgets.icon; +import static com.demcha.compose.document.templates.cv.presets.TealPulseWidgets.tracked; + +/** + * What closes the sheet: the three-column band and the tagline under its + * rule. + */ +final class TealPulseClosing { + + private TealPulseClosing() { + } + + static void render(PageFlowBuilder page, EntriesSection education, + EntriesSection certifications, EntriesSection facts, + ParagraphSection tagline) { + page.addLine(line -> line + .name("ClosingBandRule") + .horizontal(CONTENT_WIDTH) + .thickness(FULL_RULE_THICKNESS) + .color(RULE_STRONG) + .margin(new DocumentInsets(0, 0, CLOSING_RULE_TO_BAND, 0))); + renderBand(page, education, certifications, facts); + renderTagline(page, tagline); + } + + // -- the band ---------------------------------------------------------- + + /** + * The three closing columns. + * + *

    The two rules between them are left accents on the second and third + * columns, so they sit on the band's own weight boundaries and cannot + * drift off the grid.

    + */ + private static void renderBand(PageFlowBuilder page, EntriesSection education, + EntriesSection certifications, EntriesSection facts) { + page.addRow("ClosingBand", row -> { + row.weights(BAND_WEIGHT_1, BAND_WEIGHT_2, BAND_WEIGHT_3); + row.verticalAlign(RowVerticalAlign.TOP); + row.spacing(0); + row.addSection("EducationColumn", column -> { + column.spacing(0); + renderEducation(column, education); + }); + row.addSection("CertificationsColumn", column -> { + column.spacing(0); + column.accentLeft(RULE_SOFT, DIVIDER_WIDTH); + column.padding(new DocumentInsets(0, 0, 0, BAND_PAD_LEFT)); + renderCertifications(column, certifications); + }); + row.addSection("AdditionalColumn", column -> { + column.spacing(0); + column.accentLeft(RULE_SOFT, DIVIDER_WIDTH); + column.padding(new DocumentInsets(0, 0, 0, BAND_PAD_LEFT)); + renderFacts(column, facts); + }); + row.margin(new DocumentInsets(0, 0, BAND_TO_TAGLINE_RULE, 0)); + }); + } + + /** Each degree as three stacked lines: the award, the campus, the years. */ + private static void renderEducation(SectionBuilder column, EntriesSection education) { + if (education == null || education.entries().isEmpty()) { + return; + } + column.add(columnHeader(education.title(), TealPulseIcons.EDUCATION, BAND_COL1_WIDTH)); + List entries = education.entries(); + for (int index = 0; index < entries.size(); index++) { + CvEntry entry = entries.get(index); + boolean last = index == entries.size() - 1; + column.add(bandLine("EducationDegree_" + index, entry.title(), BAND_DEGREE_SIZE, + DocumentTextDecoration.BOLD, BAND_LINE_GAP, entry.link())); + column.add(bandLine("EducationInstitution_" + index, entry.subtitle(), + BAND_BODY_SIZE, DocumentTextDecoration.DEFAULT, BAND_LINE_GAP, "")); + column.add(bandLine("EducationDates_" + index, entry.date(), BAND_BODY_SIZE, + DocumentTextDecoration.DEFAULT, last ? 0 : BAND_LINE_GAP, "")); + } + } + + /** One line of closing-band body text, carrying a link when it has one. */ + private static DocumentNode bandLine(String name, String text, double size, + DocumentTextDecoration decoration, double gapBelow, + String link) { + DocumentTextStyle textStyle = style(BODY_FONT, size, BODY_TEXT, decoration); + ParagraphBuilder paragraph = new ParagraphBuilder() + .name(name) + .text(text) + .textStyle(textStyle) + .align(TextAlign.LEFT) + .lineSpacing(1.0) + .margin(new DocumentInsets(0, 0, gapBelow, 0)); + if (link != null && !link.isBlank()) { + paragraph.link(new DocumentLinkOptions(link)); + } + return paragraph.build(); + } + + private static void renderCertifications(SectionBuilder column, EntriesSection section) { + if (section == null || section.entries().isEmpty()) { + return; + } + column.add(columnHeader(section.title(), TealPulseIcons.CERTIFICATIONS, + BAND_COL2_WIDTH)); + List titles = new ArrayList<>(); + for (CvEntry entry : section.entries()) { + titles.add(entry.title()); + } + dottedList(column, "CertificationItems", titles, BAND_BODY_SIZE, 1.0, CERT_ITEM_GAP); + } + + /** + * The closing facts: a bold label and its value as two runs of one + * wrapping paragraph, which is why a fact that runs to a second line + * starts it at the column edge rather than under its value. + */ + private static void renderFacts(SectionBuilder column, EntriesSection section) { + if (section == null || section.entries().isEmpty()) { + return; + } + column.add(columnHeader(section.title(), TealPulseIcons.ADDITIONAL, BAND_COL3_WIDTH)); + DocumentTextStyle labelStyle = + style(BODY_FONT, BAND_BODY_SIZE, BODY_TEXT, DocumentTextDecoration.BOLD); + DocumentTextStyle valueStyle = + style(BODY_FONT, BAND_BODY_SIZE, BODY_TEXT, DocumentTextDecoration.DEFAULT); + column.addSection("AdditionalFacts", facts -> { + facts.spacing(BAND_FACT_GAP); + facts.padding(new DocumentInsets(0, BAND_COL3_TEXT_INSET, 0, 0)); + for (CvEntry entry : section.entries()) { + facts.addParagraph(paragraph -> paragraph + .name("Fact_" + compact(entry.title())) + .textStyle(valueStyle) + .align(TextAlign.LEFT) + .lineSpacing(BAND_LEADING) + .inlineText(entry.title(), labelStyle) + .inlineText(" " + entry.body().replace(String.valueOf((char) 10), " "), + valueStyle) + .margin(DocumentInsets.zero())); + } + }); + } + + // -- the tagline ------------------------------------------------------- + + /** + * The rule and the heart are one terminal — the rule anchored to its left, + * the heart to its right — so the mark follows the rule's end whatever the + * content width becomes. The tracked line sits under it. + */ + private static void renderTagline(PageFlowBuilder page, ParagraphSection tagline) { + if (tagline == null || tagline.body().isBlank()) { + return; + } + double heartSize = TealPulseIcons.size(TealPulseIcons.HEART); + double heartLift = -(heartSize - TAGLINE_RULE_HEIGHT) / 2.0; + DocumentNode rule = new ShapeBuilder() + .name("TaglineRuleLine") + .size(CONTENT_WIDTH, FULL_RULE_THICKNESS) + .fillColor(RULE_STRONG) + .build(); + page.addContainer(container -> container + .name("TaglineRule") + .rectangle(CONTENT_WIDTH, TAGLINE_RULE_HEIGHT) + .clipPolicy(ClipPolicy.OVERFLOW_VISIBLE) + .centerLeft(rule) + .position(icon(TealPulseIcons.HEART), 0, heartLift, LayerAlign.CENTER_RIGHT) + .margin(new DocumentInsets(0, 0, TAGLINE_RULE_TO_TEXT, 0))); + + DocumentTextStyle textStyle = + style(HEADING_FONT, TAGLINE_SIZE, ACCENT_DEEP, DocumentTextDecoration.BOLD); + ParagraphBuilder line = new ParagraphBuilder() + .name("Tagline") + .textStyle(textStyle) + .align(TextAlign.CENTER) + .lineSpacing(1.0); + tracked(line, tagline.body().replace(String.valueOf((char) 10), " "), textStyle, + TAGLINE_TRACKING_EM); + page.add(line.margin(DocumentInsets.zero()).build()); + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/TealPulseIcons.java b/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/TealPulseIcons.java new file mode 100644 index 000000000..388e205e5 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/TealPulseIcons.java @@ -0,0 +1,105 @@ +package com.demcha.compose.document.templates.cv.presets; + +import com.demcha.compose.document.svg.SvgIcon; + +import java.io.IOException; +import java.io.InputStream; +import java.io.UncheckedIOException; +import java.nio.charset.StandardCharsets; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Classpath loader for the Teal Pulse CV marks: the two halves of the brand + * mark, the four contact glyphs, the five badge glyphs and the heart that + * closes the sheet. + * + *

    They ship inside the templates artifact under + * {@code templates/cv/teal-pulse/icons/} as SVG, so they scale with the box + * they are drawn into rather than being resampled. Parsed icons are cached + * per token, so a sheet that repeats a mark reads each file once per JVM.

    + * + *

    Every mark here is chrome — this sheet draws the same ones whatever the + * CV says — so the tokens are constants and no document names one. Each + * carries the width it is drawn at: an icon's box is not its glyph, and the + * sizes are measurements off the drawing rather than a shared constant.

    + */ +final class TealPulseIcons { + + static final String BRAND_HEART = "brand-heart"; + static final String BRAND_PULSE = "brand-pulse"; + static final String LOCATION = "location"; + static final String EMAIL = "email"; + static final String PHONE = "phone"; + static final String LINKEDIN = "linkedin"; + static final String SUMMARY = "summary"; + static final String EXPERIENCE = "experience"; + static final String EDUCATION = "education"; + static final String CERTIFICATIONS = "certifications"; + static final String ADDITIONAL = "additional"; + static final String HEART = "heart"; + + private static final String ICON_ROOT = "/templates/cv/teal-pulse/icons/"; + + /** + * The width each mark is drawn at — its whole box, not the glyph inside, + * which sits a few points within it. These are points rather than + * measurements off the drawing: the asset resolver chose them per glyph so + * that each one's visible ink matches what the design shows. + */ + private static final Map SIZES = Map.ofEntries( + Map.entry(BRAND_HEART, 77.1), + Map.entry(BRAND_PULSE, 92.8), + Map.entry(LOCATION, 15.6), + Map.entry(EMAIL, 16.2), + Map.entry(PHONE, 14.3), + Map.entry(LINKEDIN, 13.6), + Map.entry(SUMMARY, 20.0), + Map.entry(EXPERIENCE, 20.0), + Map.entry(EDUCATION, 17.5), + Map.entry(CERTIFICATIONS, 17.5), + Map.entry(ADDITIONAL, 17.5), + Map.entry(HEART, 14.9)); + + private static final Map CACHE = new ConcurrentHashMap<>(); + + private TealPulseIcons() { + } + + /** + * The width one mark is drawn at. + * + * @param token one of the packaged tokens + * @return the drawn width in points + */ + static double size(String token) { + Double size = SIZES.get(token); + if (size == null) { + throw new IllegalStateException("No size for teal pulse CV icon '" + token + "'"); + } + return size; + } + + /** + * Reads one mark. + * + * @param token one of the packaged tokens + * @return the parsed icon + */ + static SvgIcon icon(String token) { + return CACHE.computeIfAbsent(token, TealPulseIcons::read); + } + + private static SvgIcon read(String token) { + String resourcePath = ICON_ROOT + token + ".svg"; + try (InputStream input = TealPulseIcons.class.getResourceAsStream(resourcePath)) { + if (input == null) { + throw new IllegalStateException("Missing teal pulse CV icon: " + resourcePath); + } + return SvgIcon.parse(new String(input.readAllBytes(), StandardCharsets.UTF_8)); + } catch (IOException e) { + throw new UncheckedIOException( + "Failed to read teal pulse CV icon: " + resourcePath, e); + } + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/TealPulseMasthead.java b/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/TealPulseMasthead.java new file mode 100644 index 000000000..091ae5f02 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/TealPulseMasthead.java @@ -0,0 +1,252 @@ +package com.demcha.compose.document.templates.cv.presets; + +import com.demcha.compose.document.dsl.PageFlowBuilder; +import com.demcha.compose.document.dsl.ParagraphBuilder; +import com.demcha.compose.document.dsl.PathBuilder; +import com.demcha.compose.document.dsl.RowBuilder; +import com.demcha.compose.document.dsl.SectionBuilder; +import com.demcha.compose.document.dsl.ShapeContainerBuilder; +import com.demcha.compose.document.node.DocumentLinkOptions; +import com.demcha.compose.document.node.DocumentNode; +import com.demcha.compose.document.node.InlineImageAlignment; +import com.demcha.compose.document.node.LayerAlign; +import com.demcha.compose.document.node.RowArrangement; +import com.demcha.compose.document.node.RowVerticalAlign; +import com.demcha.compose.document.node.TextAlign; +import com.demcha.compose.document.style.ClipPolicy; +import com.demcha.compose.document.style.DocumentInsets; +import com.demcha.compose.document.style.DocumentLineCap; +import com.demcha.compose.document.style.DocumentLineJoin; +import com.demcha.compose.document.style.DocumentRowColumn; +import com.demcha.compose.document.style.DocumentStroke; +import com.demcha.compose.document.style.DocumentTextDecoration; +import com.demcha.compose.document.style.DocumentTextStyle; +import com.demcha.compose.document.svg.SvgPath; +import com.demcha.compose.document.templates.core.identity.Contact; +import com.demcha.compose.document.templates.core.identity.ContactUri; +import com.demcha.compose.document.templates.core.identity.Link; +import com.demcha.compose.document.templates.cv.data.CvIdentity; + +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; + +import static com.demcha.compose.document.templates.cv.presets.TealPulseStyles.ACCENT; +import static com.demcha.compose.document.templates.cv.presets.TealPulseStyles.ACCENT_DEEP; +import static com.demcha.compose.document.templates.cv.presets.TealPulseStyles.BODY_FONT; +import static com.demcha.compose.document.templates.cv.presets.TealPulseStyles.BODY_TEXT; +import static com.demcha.compose.document.templates.cv.presets.TealPulseStyles.CONTACT_ICON_DROP; +import static com.demcha.compose.document.templates.cv.presets.TealPulseStyles.CONTACT_ICON_GAP; +import static com.demcha.compose.document.templates.cv.presets.TealPulseStyles.CONTACT_PAIR_LIFT; +import static com.demcha.compose.document.templates.cv.presets.TealPulseStyles.CONTACT_SIZE; +import static com.demcha.compose.document.templates.cv.presets.TealPulseStyles.CONTACT_TO_RULE; +import static com.demcha.compose.document.templates.cv.presets.TealPulseStyles.CONTENT_WIDTH; +import static com.demcha.compose.document.templates.cv.presets.TealPulseStyles.DISPLAY_FONT; +import static com.demcha.compose.document.templates.cv.presets.TealPulseStyles.DISPLAY_TEXT; +import static com.demcha.compose.document.templates.cv.presets.TealPulseStyles.FULL_RULE_THICKNESS; +import static com.demcha.compose.document.templates.cv.presets.TealPulseStyles.LOGO_HEIGHT; +import static com.demcha.compose.document.templates.cv.presets.TealPulseStyles.LOGO_TO_IDENTITY; +import static com.demcha.compose.document.templates.cv.presets.TealPulseStyles.LOGO_WIDTH; +import static com.demcha.compose.document.templates.cv.presets.TealPulseStyles.MARK_STROKE; +import static com.demcha.compose.document.templates.cv.presets.TealPulseStyles.MASTHEAD_TO_CONTACT; +import static com.demcha.compose.document.templates.cv.presets.TealPulseStyles.NAME_SIZE; +import static com.demcha.compose.document.templates.cv.presets.TealPulseStyles.NAME_TO_ROLE; +import static com.demcha.compose.document.templates.cv.presets.TealPulseStyles.NAME_TRACKING_EM; +import static com.demcha.compose.document.templates.cv.presets.TealPulseStyles.PULSE_BOX_HEIGHT; +import static com.demcha.compose.document.templates.cv.presets.TealPulseStyles.PULSE_BOX_WIDTH; +import static com.demcha.compose.document.templates.cv.presets.TealPulseStyles.PULSE_DROP; +import static com.demcha.compose.document.templates.cv.presets.TealPulseStyles.PULSE_SHIFT; +import static com.demcha.compose.document.templates.cv.presets.TealPulseStyles.ROLE_SIZE; +import static com.demcha.compose.document.templates.cv.presets.TealPulseStyles.ROLE_TRACKING_EM; +import static com.demcha.compose.document.templates.cv.presets.TealPulseStyles.RULE_STRONG; +import static com.demcha.compose.document.templates.cv.presets.TealPulseStyles.RULE_TO_BODY; +import static com.demcha.compose.document.templates.cv.presets.TealPulseStyles.SEPARATOR; +import static com.demcha.compose.document.templates.cv.presets.TealPulseStyles.SEPARATOR_HEIGHT; +import static com.demcha.compose.document.templates.cv.presets.TealPulseStyles.SEPARATOR_THICKNESS; +import static com.demcha.compose.document.templates.cv.presets.TealPulseStyles.compact; +import static com.demcha.compose.document.templates.cv.presets.TealPulseStyles.gapRun; +import static com.demcha.compose.document.templates.cv.presets.TealPulseStyles.style; +import static com.demcha.compose.document.templates.cv.presets.TealPulseWidgets.icon; +import static com.demcha.compose.document.templates.cv.presets.TealPulseWidgets.tracked; + +/** + * The top of the sheet: the mark beside the name, the contact strip, and the + * rule that closes them. + */ +final class TealPulseMasthead { + + private TealPulseMasthead() { + } + + /** One channel of the contact strip: a mark, a value and where it points. */ + private record Channel(String token, String value, String href) { + } + + static void render(PageFlowBuilder page, CvIdentity identity) { + page.addRow("Masthead", row -> { + row.columns(DocumentRowColumn.fixed(LOGO_WIDTH), DocumentRowColumn.weight(1)); + row.verticalAlign(RowVerticalAlign.CENTER); + row.spacing(LOGO_TO_IDENTITY); + row.add(brandMark()); + row.addSection("Identity", section -> renderIdentity(section, identity)); + row.margin(new DocumentInsets(0, 0, MASTHEAD_TO_CONTACT, 0)); + }); + renderContactStrip(page, identity); + page.addLine(line -> line + .name("MastheadRule") + .horizontal(CONTENT_WIDTH) + .thickness(FULL_RULE_THICKNESS) + .color(RULE_STRONG) + .margin(new DocumentInsets(0, 0, RULE_TO_BODY, 0))); + } + + // -- the mark ---------------------------------------------------------- + + /** + * A heart outline crossed by a flat pulse. + * + *

    No single glyph has this geometry — every heartbeat icon puts the + * pulse in the left half — so the mark is two vectors centred in one box. + * Each child is wider or taller than that box, and a shape container + * clamps an over-sized child to the top left rather than centring it, so + * each is pulled back by half its own overflow. Those two corrections + * exist only to cancel the clamp.

    + */ + private static DocumentNode brandMark() { + double heartSize = TealPulseIcons.size(TealPulseIcons.BRAND_HEART); + double heartLift = -(heartSize - LOGO_HEIGHT) / 2.0; + return new ShapeContainerBuilder() + .name("BrandMark") + .rectangle(LOGO_WIDTH, LOGO_HEIGHT) + .clipPolicy(ClipPolicy.OVERFLOW_VISIBLE) + .position(icon(TealPulseIcons.BRAND_HEART), 0, heartLift, LayerAlign.CENTER) + .position(pulse(), PULSE_SHIFT, PULSE_DROP, LayerAlign.CENTER) + .build(); + } + + /** + * The pulse, drawn from the packaged glyph's own geometry at a box this + * preset chooses, so the line spans the mark while the spike keeps the + * height the design draws. The stroke is the design's, not the glyph's. + */ + private static DocumentNode pulse() { + SvgPath geometry = TealPulseIcons.icon(TealPulseIcons.BRAND_PULSE) + .layers().get(0).geometry(); + return new PathBuilder() + .name("BrandPulse") + .size(PULSE_BOX_WIDTH, PULSE_BOX_HEIGHT) + .svg(geometry) + .stroke(DocumentStroke.of(ACCENT, MARK_STROKE)) + .lineCap(DocumentLineCap.ROUND) + .lineJoin(DocumentLineJoin.ROUND) + .build(); + } + + // -- the name ---------------------------------------------------------- + + /** The tracked display name over the tracked role, both in capitals. */ + private static void renderIdentity(SectionBuilder section, CvIdentity identity) { + section.spacing(0); + DocumentTextStyle nameStyle = + style(DISPLAY_FONT, NAME_SIZE, DISPLAY_TEXT, DocumentTextDecoration.DEFAULT); + ParagraphBuilder name = new ParagraphBuilder() + .name("DisplayName") + .textStyle(nameStyle) + .align(TextAlign.LEFT) + .lineSpacing(1.0); + tracked(name, identity.name().full().toUpperCase(Locale.ROOT), nameStyle, + NAME_TRACKING_EM); + section.add(name.margin(new DocumentInsets(0, 0, NAME_TO_ROLE, 0)).build()); + + DocumentTextStyle roleStyle = + style(DISPLAY_FONT, ROLE_SIZE, ACCENT_DEEP, DocumentTextDecoration.BOLD); + ParagraphBuilder role = new ParagraphBuilder() + .name("ProfessionalTitle") + .textStyle(roleStyle) + .align(TextAlign.LEFT) + .lineSpacing(1.0); + tracked(role, identity.jobTitle().toUpperCase(Locale.ROOT), roleStyle, ROLE_TRACKING_EM); + section.add(role.margin(DocumentInsets.zero()).build()); + } + + // -- the strip --------------------------------------------------------- + + /** + * The contact strip: the three channels and a row per link, each a mark + * and a value, divided by short rules. + * + *

    Each pair is one paragraph, so the row's distribution can never split + * a mark from its value. Spreading the children pins the run to both + * margins and makes the gaps even, which a fixed gap would reach only by + * luck of the font's metrics.

    + * + *

    A link shows its own label and carries the address behind it, so the + * strip's gaps do not depend on how long a profile's URL happens to be.

    + */ + private static void renderContactStrip(PageFlowBuilder page, CvIdentity identity) { + List channels = channels(identity); + page.addRow("ContactStrip", row -> { + row.verticalAlign(RowVerticalAlign.CENTER); + row.arrangement(RowArrangement.SPACE_BETWEEN); + for (int index = 0; index < channels.size(); index++) { + if (index > 0) { + separator(row, index); + } + row.add(channelPair(channels.get(index), index)); + } + row.margin(new DocumentInsets(0, 0, CONTACT_TO_RULE, 0)); + }); + } + + /** The strip's order: where, then how to write, then how to call, then links. */ + private static List channels(CvIdentity identity) { + Contact contact = identity.contact(); + List channels = new ArrayList<>(); + channels.add(new Channel(TealPulseIcons.LOCATION, contact.address(), null)); + channels.add(new Channel(TealPulseIcons.EMAIL, contact.email(), + "mailto:" + contact.email())); + channels.add(new Channel(TealPulseIcons.PHONE, contact.phone(), ContactUri.tel(contact.phone()))); + for (Link link : identity.links()) { + channels.add(new Channel(TealPulseIcons.LINKEDIN, link.label(), link.url())); + } + return channels; + } + + private static DocumentNode channelPair(Channel channel, int index) { + DocumentTextStyle textStyle = + style(BODY_FONT, CONTACT_SIZE, BODY_TEXT, DocumentTextDecoration.DEFAULT); + DocumentLinkOptions link = channel.href() == null || channel.href().isBlank() + ? null + : new DocumentLinkOptions(channel.href()); + ParagraphBuilder pair = new ParagraphBuilder() + .name("Contact_" + index + "_" + compact(channel.token())) + .textStyle(textStyle) + .align(TextAlign.LEFT) + .lineSpacing(1.0) + .inlineSvgIcon(TealPulseIcons.icon(channel.token()), + TealPulseIcons.size(channel.token()), InlineImageAlignment.CENTER, + CONTACT_ICON_DROP, link); + String value = gapRun(CONTACT_ICON_GAP, CONTACT_SIZE) + channel.value(); + if (link == null) { + pair.inlineText(value, textStyle); + } else { + pair.inlineText(value, textStyle, link); + } + // The pair still sits below the axis the design uses. It is centred in + // a row whose height the separators set, so a NEGATIVE top margin of + // twice the wanted lift raises it by half of it. Negative rather than a + // positive bottom margin: both shift a centred child equally, but a + // bottom margin would grow the box past the separators and take the + // whole strip — and everything under it — down the page. + return pair.margin(new DocumentInsets(-2 * CONTACT_PAIR_LIFT, 0, 0, 0)).build(); + } + + private static void separator(RowBuilder row, int index) { + row.addLine(line -> line + .name("ContactSeparator_" + index) + .vertical(SEPARATOR_HEIGHT) + .thickness(SEPARATOR_THICKNESS) + .color(SEPARATOR) + .margin(DocumentInsets.zero())); + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/TealPulseStyles.java b/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/TealPulseStyles.java new file mode 100644 index 000000000..5f8cb68e4 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/TealPulseStyles.java @@ -0,0 +1,313 @@ +package com.demcha.compose.document.templates.cv.presets; + +import com.demcha.compose.document.api.DocumentPageSize; +import com.demcha.compose.document.style.DocumentColor; +import com.demcha.compose.document.style.DocumentTextDecoration; +import com.demcha.compose.document.style.DocumentTextStyle; +import com.demcha.compose.font.FontName; + +/** + * The measured geometry, palette and type scale of the Teal Pulse CV. + * + *

    Every length is a measurement off one grid

    + * + *

    The design was drawn on a 1103×1426 raster, so each length here is + * written as {@link #px} of a pixel measured off it and {@code px} is the one + * conversion into points. Re-measuring the drawing therefore changes one + * number per length and never a chain of them — and the page itself takes + * that grid's proportion rather than A4's, which is why this preset sets its + * own page.

    + * + *

    Leading is not a multiple of the type size

    + * + *

    A paragraph's line pitch on this engine is about + * {@code 1.216 × size + 0.872 × lineSpacing}, so the leading constants below + * are the values that produce the drawn pitch rather than ratios of it. A + * single-line paragraph ignores leading entirely and takes {@code 1.216 × + * size}, which is why the bullet rhythm is section spacing instead.

    + */ +final class TealPulseStyles { + + private TealPulseStyles() { + } + + // -- the grid ---------------------------------------------------------- + + /** The raster the design was drawn on. */ + static final double REFERENCE_WIDTH_PX = 1103.0; + static final double REFERENCE_HEIGHT_PX = 1426.0; + + static final double PAGE_WIDTH = 595.28; + static final double PAGE_HEIGHT = PAGE_WIDTH * REFERENCE_HEIGHT_PX / REFERENCE_WIDTH_PX; + static final DocumentPageSize PAGE = DocumentPageSize.of(PAGE_WIDTH, PAGE_HEIGHT); + + /** + * One measurement off the drawing, in points. + * + * @param referencePixels the pixel measured on the 1103-wide raster + * @return the same distance on the page + */ + static double px(double referencePixels) { + return referencePixels * PAGE_WIDTH / REFERENCE_WIDTH_PX; + } + + static final double MARGIN_X = px(53); + static final double PAD_TOP = px(34); + static final double PAD_BOTTOM = px(14); + static final double CONTENT_WIDTH = PAGE_WIDTH - 2 * MARGIN_X; + + /** The sidebar runs from the left margin to the column rule. */ + static final double SIDEBAR_WEIGHT = 0.26533; + static final double MAIN_WEIGHT = 1.0 - SIDEBAR_WEIGHT; + static final double MAIN_PAD_LEFT = px(32); + static final double MAIN_CONTENT_WIDTH = CONTENT_WIDTH * MAIN_WEIGHT - MAIN_PAD_LEFT; + + /** + * The closing band's first divider is collinear with the body's column + * rule, so its first weight is the sidebar's — that is the shared grid, + * not a coincidence to be measured twice. + */ + static final double BAND_WEIGHT_1 = SIDEBAR_WEIGHT; + static final double BAND_WEIGHT_2 = 0.37789; + static final double BAND_WEIGHT_3 = 1.0 - BAND_WEIGHT_1 - BAND_WEIGHT_2; + + static final double BAND_PAD_LEFT = px(36); + + /** + * Two right insets, because the design uses two: a heading's underline + * stops well short of the divider while the body under it runs on almost + * to the rule. One inset for both would either wrap the longest line or + * overrun every underline. + */ + static final double BAND_RULE_INSET = px(40); + static final double BAND_COL1_WIDTH = CONTENT_WIDTH * BAND_WEIGHT_1 - BAND_RULE_INSET; + static final double BAND_COL2_WIDTH = + CONTENT_WIDTH * BAND_WEIGHT_2 - BAND_PAD_LEFT - BAND_RULE_INSET; + static final double BAND_COL3_WIDTH = CONTENT_WIDTH * BAND_WEIGHT_3 - BAND_PAD_LEFT; + + /** + * Two blocks wrap inside a measure narrower than the column holding them, + * and the design is unambiguous about both: the summary breaks with a + * finger's width of its column still free, and so does the first fact. + * Reproducing the line breaks means reproducing the measure, not only the + * type size. + */ + static final double SUMMARY_RIGHT_INSET = px(105); + static final double BAND_COL3_TEXT_INSET = px(79); + + // -- type -------------------------------------------------------------- + + static final FontName DISPLAY_FONT = FontName.POPPINS; + + /** + * The drawing's heading face sets about a fifth narrower per cap height + * than the body's bold, so the headings take a condensed face and keep + * both dimensions rather than shrinking to fit and losing the cap height. + */ + static final FontName HEADING_FONT = FontName.BARLOW_CONDENSED; + static final FontName BODY_FONT = FontName.LATO; + + static final double NAME_SIZE = 39.5; + static final double ROLE_SIZE = 13.84; + static final double CONTACT_SIZE = 7.86; + static final double SIDEBAR_HEADING_SIZE = 10.4; + static final double COMPETENCY_SIZE = 9.09; + static final double MAIN_HEADING_SIZE = 12.7; + static final double SUMMARY_SIZE = 9.91; + static final double ENTRY_HEADLINE_SIZE = 8.90; + static final double BULLET_SIZE = 8.16; + static final double BAND_HEADING_SIZE = 9.6; + static final double BAND_BODY_SIZE = 8.6; + static final double BAND_DEGREE_SIZE = 10.55; + static final double TAGLINE_SIZE = 8.09; + + static final double SUMMARY_LEADING = 5.37; + static final double BULLET_LEADING = 1.0; + static final double BAND_LEADING = 4.71; + + // -- tracking ---------------------------------------------------------- + + /** + * A space's advance as a fraction of the type size. Tracking is written as + * spacer runs between the letters because a text style carries no + * letter-spacing, and this is what turns a wanted em fraction into a + * number of spaces. + */ + static final double SPACE_ADVANCE_EM = 0.25; + + static final double NAME_TRACKING_EM = 0.045; + static final double ROLE_TRACKING_EM = 0.43; + static final double MAIN_HEADING_TRACKING_EM = 0.124; + static final double SIDEBAR_HEADING_TRACKING_EM = 0.251; + static final double BAND_HEADING_TRACKING_EM = 0.215; + static final double TAGLINE_TRACKING_EM = 0.239; + + // -- palette ----------------------------------------------------------- + + static final DocumentColor ACCENT = DocumentColor.rgb(6, 100, 112); + static final DocumentColor ACCENT_DEEP = DocumentColor.rgb(0, 80, 95); + static final DocumentColor RULE_STRONG = DocumentColor.rgb(0, 88, 104); + static final DocumentColor RULE_SOFT = DocumentColor.rgb(119, 174, 182); + static final DocumentColor RULE_PALE = DocumentColor.rgb(177, 204, 207); + static final DocumentColor SEPARATOR = DocumentColor.rgb(54, 125, 135); + static final DocumentColor DISPLAY_TEXT = DocumentColor.rgb(34, 36, 50); + static final DocumentColor BODY_TEXT = DocumentColor.rgb(17, 17, 17); + static final DocumentColor PAPER = DocumentColor.WHITE; + + // -- the mark ---------------------------------------------------------- + + static final double LOGO_WIDTH = px(132); + static final double LOGO_HEIGHT = px(105); + static final double LOGO_TO_IDENTITY = px(39); + static final double MARK_STROKE = px(3); + + /** The pulse crosses the heart below its centre, not through it. */ + static final double PULSE_DROP = px(6.5); + + /** + * The pulse's box is its INK box: a path is mapped onto the size given by + * its own bounds rather than its viewBox. Its height is the spike's, and + * that is why the pulse is a path and not an icon — the drawing's line is + * flatter than any heartbeat glyph, and an icon keeps its aspect. + */ + static final double PULSE_BOX_WIDTH = px(172); + static final double PULSE_BOX_HEIGHT = px(82); + + /** + * A shape container clamps an over-wide child to the left exactly as it + * top-clamps an over-tall one, so the pulse is pulled back by half its own + * overflow. + */ + static final double PULSE_SHIFT = -(PULSE_BOX_WIDTH - LOGO_WIDTH) / 2.0; + + // -- rules, badges and dots -------------------------------------------- + + static final double DIVIDER_WIDTH = px(2); + static final double FULL_RULE_THICKNESS = px(2.5); + static final double HEADER_RULE_THICKNESS = px(2.5); + static final double BAND_RULE_THICKNESS = px(3); + static final double SIDEBAR_DASH_WIDTH = px(54); + static final double SIDEBAR_DASH_THICKNESS = px(3); + static final double SIDEBAR_CLOSING_RULE_WIDTH = px(216); + static final double SIDEBAR_CLOSING_RULE_THICKNESS = px(3); + static final double TAGLINE_RULE_HEIGHT = px(21); + + static final double BADGE_MAIN = px(58); + static final double BADGE_BAND = px(51); + static final double SECTION_GAP = px(17); + static final double BAND_HEADER_GAP = px(18); + static final double DOT_DIAMETER = px(6); + static final double DOT_GAP = px(17); + + static final double SEPARATOR_HEIGHT = px(38); + static final double SEPARATOR_THICKNESS = px(1.5); + + /** + * A contact icon is twice the height of the value beside it, so it is the + * icon that sets the line box: centring puts the icon on that box's middle + * while the value still sits near its foot, and the pair reads as standing + * at two levels. These two corrections put both back where the drawing has + * them — the first raises the pair, the second returns the icon to the + * value's own axis. + */ + static final double CONTACT_ICON_DROP = -px(4); + static final double CONTACT_PAIR_LIFT = px(4); + + /** + * The gap asked of the type, not the gap that appears: an icon's box + * carries its own bearing, so about half the drawn gap is requested and + * the bearing makes up the rest. + */ + static final double CONTACT_ICON_GAP = px(7.5); + + // -- the rhythm between blocks ----------------------------------------- + + static final double MASTHEAD_TO_CONTACT = px(17); + static final double CONTACT_TO_RULE = px(23); + static final double RULE_TO_BODY = px(29); + static final double NAME_TO_ROLE = px(0); + static final double SIDEBAR_PAD_TOP = px(9); + static final double DASH_TO_HEADING = px(14); + static final double HEADING_TO_ITEMS = px(35); + static final double COMPETENCY_GAP = px(29.2); + static final double ITEMS_TO_CLOSING_RULE = px(50); + static final double HEADER_TO_BODY = px(20); + static final double SUMMARY_TO_EXPERIENCE = px(29); + static final double HEADER_TO_ENTRY = px(14); + static final double ENTRY_TO_BULLETS = px(13); + static final double ENTRY_GAP = px(34); + static final double BODY_TO_CLOSING_RULE = px(30); + static final double CLOSING_RULE_TO_BAND = px(27); + static final double BAND_HEADING_TO_UNDERLINE = px(9.5); + static final double BAND_HEADER_TO_BODY = px(22); + + /** + * The closing-band heading does not sit on its badge's centre line: the + * stack is seated below it so the underline clears the badge's lower half + * rather than cutting through it. + */ + static final double BAND_HEADING_DROP = px(12); + static final double BAND_LINE_GAP = px(10.5); + static final double BAND_FACT_GAP = px(14.6); + static final double CERT_ITEM_GAP = px(12.9); + static final double BAND_TO_TAGLINE_RULE = px(22); + static final double TAGLINE_RULE_TO_TEXT = px(1.6); + + /** + * Bullet pitch is section spacing, not line spacing: a single-line + * paragraph takes the font's own line height whatever leading it is given, + * so the drawn pitch has to be the gap between paragraphs. + */ + static final double BULLET_GAP = px(7.9); + + // -- helpers ----------------------------------------------------------- + + static DocumentTextStyle style(FontName font, double size, DocumentColor color, + DocumentTextDecoration decoration) { + return DocumentTextStyle.builder() + .fontName(font) + .size(size) + .color(color) + .decoration(decoration) + .build(); + } + + /** + * The size a spacer run is set at so that {@code runs} of them advance the + * wanted fraction of an em. + */ + static DocumentTextStyle spacerStyle(DocumentTextStyle textStyle, double trackingEm, + int runs) { + return textStyle.withSize(trackingEm * textStyle.size() / (runs * SPACE_ADVANCE_EM)); + } + + /** + * How many spacer characters carry one gap. + * + *

    A run's size sets the line's height as well as its advance, so a + * single space wide enough for heavy tracking would make the line taller + * than its own type. Splitting the gap across several smaller spaces keeps + * every spacer below the text size.

    + */ + static int spacerRuns(double trackingEm) { + return Math.max(1, (int) Math.ceil(trackingEm / SPACE_ADVANCE_EM)); + } + + /** + * Spaces that advance about {@code width} points at {@code size}. + * + *

    Deliberately not one resized space: a run's size sets the line's + * height as well as its advance, so a single space wide enough for the gap + * would make its line several times too tall. The count follows the size, + * so changing the type scale moves the gap with it.

    + */ + static String gapRun(double width, double size) { + int spaces = (int) Math.max(1, Math.round(width / (SPACE_ADVANCE_EM * size))); + return " ".repeat(spaces); + } + + /** A node name built from a heading, with the spacing taken out of it. */ + static String compact(String value) { + return value == null ? "" : value.replaceAll("[^A-Za-z0-9]", ""); + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/TealPulseWidgets.java b/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/TealPulseWidgets.java new file mode 100644 index 000000000..c2f35dfeb --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/TealPulseWidgets.java @@ -0,0 +1,254 @@ +package com.demcha.compose.document.templates.cv.presets; + +import com.demcha.compose.document.dsl.ParagraphBuilder; +import com.demcha.compose.document.dsl.SectionBuilder; +import com.demcha.compose.document.dsl.ShapeBuilder; +import com.demcha.compose.document.dsl.ShapeContainerBuilder; +import com.demcha.compose.document.node.DocumentNode; +import com.demcha.compose.document.node.LayerAlign; +import com.demcha.compose.document.node.TextAlign; +import com.demcha.compose.document.style.ClipPolicy; +import com.demcha.compose.document.style.DocumentInsets; +import com.demcha.compose.document.style.DocumentTextDecoration; +import com.demcha.compose.document.style.DocumentTextStyle; + +import java.util.List; + +import static com.demcha.compose.document.templates.cv.presets.TealPulseStyles.ACCENT; +import static com.demcha.compose.document.templates.cv.presets.TealPulseStyles.ACCENT_DEEP; +import static com.demcha.compose.document.templates.cv.presets.TealPulseStyles.BADGE_BAND; +import static com.demcha.compose.document.templates.cv.presets.TealPulseStyles.BADGE_MAIN; +import static com.demcha.compose.document.templates.cv.presets.TealPulseStyles.BAND_HEADER_GAP; +import static com.demcha.compose.document.templates.cv.presets.TealPulseStyles.BAND_HEADER_TO_BODY; +import static com.demcha.compose.document.templates.cv.presets.TealPulseStyles.BAND_HEADING_DROP; +import static com.demcha.compose.document.templates.cv.presets.TealPulseStyles.BAND_HEADING_SIZE; +import static com.demcha.compose.document.templates.cv.presets.TealPulseStyles.BAND_HEADING_TO_UNDERLINE; +import static com.demcha.compose.document.templates.cv.presets.TealPulseStyles.BAND_HEADING_TRACKING_EM; +import static com.demcha.compose.document.templates.cv.presets.TealPulseStyles.BAND_RULE_THICKNESS; +import static com.demcha.compose.document.templates.cv.presets.TealPulseStyles.BODY_FONT; +import static com.demcha.compose.document.templates.cv.presets.TealPulseStyles.BODY_TEXT; +import static com.demcha.compose.document.templates.cv.presets.TealPulseStyles.DOT_DIAMETER; +import static com.demcha.compose.document.templates.cv.presets.TealPulseStyles.DOT_GAP; +import static com.demcha.compose.document.templates.cv.presets.TealPulseStyles.HEADER_RULE_THICKNESS; +import static com.demcha.compose.document.templates.cv.presets.TealPulseStyles.HEADING_FONT; +import static com.demcha.compose.document.templates.cv.presets.TealPulseStyles.MAIN_CONTENT_WIDTH; +import static com.demcha.compose.document.templates.cv.presets.TealPulseStyles.MAIN_HEADING_SIZE; +import static com.demcha.compose.document.templates.cv.presets.TealPulseStyles.MAIN_HEADING_TRACKING_EM; +import static com.demcha.compose.document.templates.cv.presets.TealPulseStyles.PAPER; +import static com.demcha.compose.document.templates.cv.presets.TealPulseStyles.SECTION_GAP; +import static com.demcha.compose.document.templates.cv.presets.TealPulseStyles.compact; +import static com.demcha.compose.document.templates.cv.presets.TealPulseStyles.gapRun; +import static com.demcha.compose.document.templates.cv.presets.TealPulseStyles.spacerRuns; +import static com.demcha.compose.document.templates.cv.presets.TealPulseStyles.spacerStyle; +import static com.demcha.compose.document.templates.cv.presets.TealPulseStyles.style; + +/** + * The idioms this sheet repeats: the tracked run, the badge, the dotted line + * and the two heading bars. + */ +final class TealPulseWidgets { + + private TealPulseWidgets() { + } + + // -- tracking ---------------------------------------------------------- + + /** + * A letter-spaced run. + * + *

    A text style carries no letter-spacing, so every character is its own + * run and the gaps between them are spaces set small enough to advance the + * wanted fraction of an em. The gap is split across several spacers rather + * than carried on one, because a run's size sets its line's height as well + * as its advance.

    + * + * @param paragraph the paragraph to write into + * @param text the text to space out + * @param textStyle the style of the letters + * @param trackingEm the gap as a fraction of an em + */ + static void tracked(ParagraphBuilder paragraph, String text, DocumentTextStyle textStyle, + double trackingEm) { + int runs = spacerRuns(trackingEm); + DocumentTextStyle spacer = spacerStyle(textStyle, trackingEm, runs); + for (int index = 0; index < text.length(); index++) { + paragraph.inlineText(String.valueOf(text.charAt(index)), textStyle); + if (index + 1 < text.length()) { + paragraph.inlineText(" ".repeat(runs), spacer); + } + } + } + + /** + * The same, with the paper laid behind every run, so a rule the heading is + * drawn over shows only past the end of the words. + */ + static void knockedOut(ParagraphBuilder paragraph, String text, + DocumentTextStyle textStyle, double trackingEm) { + int runs = spacerRuns(trackingEm); + DocumentTextStyle spacer = spacerStyle(textStyle, trackingEm, runs); + for (int index = 0; index < text.length(); index++) { + knockedOutRun(paragraph, String.valueOf(text.charAt(index)), textStyle); + if (index + 1 < text.length()) { + knockedOutRun(paragraph, " ".repeat(runs), spacer); + } + } + } + + /** One knocked-out run. No horizontal padding, so neighbours abut. */ + private static void knockedOutRun(ParagraphBuilder paragraph, String text, + DocumentTextStyle textStyle) { + paragraph.inlineHighlight(text, textStyle, PAPER, 0, + new DocumentInsets(HEADER_RULE_THICKNESS, 0, HEADER_RULE_THICKNESS, 0)); + } + + // -- marks ------------------------------------------------------------- + + /** A teal disc owning a white glyph — the pair moves and scales as one. */ + static DocumentNode badge(String name, String token, double diameter) { + return new ShapeContainerBuilder() + .name("Badge_" + compact(name)) + .circle(diameter) + .fillColor(ACCENT) + .clipPolicy(ClipPolicy.OVERFLOW_VISIBLE) + .center(icon(token)) + .build(); + } + + /** + * One mark as a node. It is wrapped in a section rather than placed + * directly, so the vector path reaches the page rather than a coloured + * stand-in shaped like its box. + */ + static DocumentNode icon(String token) { + return new SectionBuilder() + .name("Icon_" + compact(token)) + .spacing(0) + .padding(DocumentInsets.zero()) + .margin(DocumentInsets.zero()) + .addSvgIcon(TealPulseIcons.icon(token), TealPulseIcons.size(token)) + .build(); + } + + // -- lines ------------------------------------------------------------- + + /** + * A list of near-black labels, each led by a teal dot. + * + *

    A real list, marker column and all: the dot is the marker and + * {@link TealPulseStyles#DOT_GAP} is the gap after it, in points. That is + * what {@code DOT_GAP} always meant and never got. Written as a paragraph + * per label — a dot run, then spaces standing in for the gap, then the text + * — the gap was a whole number of spaces measured at each label's own type + * size, so this sheet rendered its one declared 9.175pt as 9.308 beside the + * competencies and 8.356 beside the highlights. Wrapped labels were the + * larger cost: with no marker column to hang under, every line after the + * first returned to the label's own left edge.

    + * + *

    Markers are left un-normalized because the labels are the document's + * own words and nothing is prefixing them with one; stripping a leading + * {@code "- "} here would edit the data.

    + */ + static void dottedList(SectionBuilder host, String name, List labels, + double size, double leading, double itemGap) { + DocumentTextStyle textStyle = + style(BODY_FONT, size, BODY_TEXT, DocumentTextDecoration.DEFAULT); + host.addList(list -> { + list.name(name) + .textStyle(textStyle) + .align(TextAlign.LEFT) + .lineSpacing(leading) + .marker(marker -> marker.dot(DOT_DIAMETER, ACCENT)) + .hangingIndent(true) + .markerGap(DOT_GAP) + .itemSpacing(itemGap) + .normalizeMarkers(false) + .margin(DocumentInsets.zero()); + for (String label : labels) { + list.addItem(label); + } + }); + } + + // -- heading bars ------------------------------------------------------ + + /** + * The main column's heading: a badge, the words, and a rule running from + * them to the right margin. + * + *

    The obvious construction is unavailable here. A row of fixed / auto / + * weighted cells would let the layout derive the rule's length from the + * heading's width, but a row cannot nest inside a row cell and the main + * column is a row cell; the alternative, a shape container placing + * children by anchor, needs every width up front.

    + * + *

    So the rule is drawn at full length from just past the badge and the + * heading is laid over it with the paper knocked out behind every run. The + * rule then shows exactly where the heading is not, which is the picture an + * auto column would have produced, and its visible length still follows the + * words rather than a width guessed while authoring.

    + */ + static DocumentNode sectionHeader(String title, String token, double gapBelow) { + double railStart = BADGE_MAIN + SECTION_GAP; + DocumentNode rail = new ShapeBuilder() + .name("SectionHeaderRule_" + compact(title)) + .size(MAIN_CONTENT_WIDTH - railStart, HEADER_RULE_THICKNESS) + .fillColor(ACCENT) + .build(); + DocumentTextStyle headingStyle = style( + HEADING_FONT, MAIN_HEADING_SIZE, ACCENT_DEEP, DocumentTextDecoration.BOLD); + ParagraphBuilder heading = new ParagraphBuilder() + .name("SectionHeading_" + compact(title)) + .textStyle(headingStyle) + .align(TextAlign.LEFT) + .lineSpacing(1.0); + knockedOut(heading, title, headingStyle, MAIN_HEADING_TRACKING_EM); + knockedOutRun(heading, gapRun(SECTION_GAP, MAIN_HEADING_SIZE), headingStyle); + return new ShapeContainerBuilder() + .name("SectionHeader_" + compact(title)) + .rectangle(MAIN_CONTENT_WIDTH, BADGE_MAIN) + .clipPolicy(ClipPolicy.OVERFLOW_VISIBLE) + .centerLeft(badge(title, token, BADGE_MAIN)) + .position(rail, railStart, 0, LayerAlign.CENTER_LEFT, 0) + .position(heading.margin(DocumentInsets.zero()).build(), + railStart, 0, LayerAlign.CENTER_LEFT, 1) + .margin(new DocumentInsets(0, 0, gapBelow, 0)) + .build(); + } + + /** + * The closing band's heading: the rule sits UNDER the words rather than + * after them, so its width is derivable — the column less the badge and + * the gap — and nothing has to be knocked out. + */ + static DocumentNode columnHeader(String title, String token, double columnWidth) { + DocumentTextStyle headingStyle = style( + HEADING_FONT, BAND_HEADING_SIZE, ACCENT_DEEP, DocumentTextDecoration.BOLD); + ParagraphBuilder heading = new ParagraphBuilder() + .name("ColumnHeading_" + compact(title)) + .textStyle(headingStyle) + .align(TextAlign.LEFT) + .lineSpacing(1.0); + tracked(heading, title, headingStyle, BAND_HEADING_TRACKING_EM); + double stackStart = BADGE_BAND + BAND_HEADER_GAP; + DocumentNode stack = new SectionBuilder() + .name("ColumnHeadingStack_" + compact(title)) + .spacing(0) + .add(heading.margin(DocumentInsets.zero()).build()) + .addLine(line -> line + .name("ColumnHeadingRule_" + compact(title)) + .horizontal(columnWidth - stackStart) + .thickness(BAND_RULE_THICKNESS) + .color(ACCENT) + .margin(new DocumentInsets(BAND_HEADING_TO_UNDERLINE, 0, 0, 0))) + .build(); + return new ShapeContainerBuilder() + .name("ColumnHeader_" + compact(title)) + .rectangle(columnWidth, BADGE_BAND) + .clipPolicy(ClipPolicy.OVERFLOW_VISIBLE) + .centerLeft(badge(title, token, BADGE_BAND)) + .position(stack, stackStart, BAND_HEADING_DROP, LayerAlign.CENTER_LEFT) + .margin(new DocumentInsets(0, 0, BAND_HEADER_TO_BODY, 0)) + .build(); + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/TerracottaRail.java b/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/TerracottaRail.java new file mode 100644 index 000000000..af64d4f60 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/TerracottaRail.java @@ -0,0 +1,226 @@ +package com.demcha.compose.document.templates.cv.presets; + +import com.demcha.compose.document.api.DocumentSession; +import com.demcha.compose.document.templates.api.DocumentTemplate; +import com.demcha.compose.document.templates.cv.components.SectionLookup; +import com.demcha.compose.document.templates.cv.data.CvDocument; +import com.demcha.compose.document.templates.cv.data.CvSection; +import com.demcha.compose.document.templates.cv.data.EntriesSection; +import com.demcha.compose.document.templates.cv.data.ParagraphSection; +import com.demcha.compose.document.templates.cv.data.SkillsSection; + +import java.util.List; +import java.util.Objects; + +import static com.demcha.compose.document.templates.cv.presets.TerracottaRailStyles.MAIN_WEIGHT; +import static com.demcha.compose.document.templates.cv.presets.TerracottaRailStyles.SIDEBAR_WEIGHT; + +/** + * Terracotta Rail — a one-page CV in two columns: a narrow column carrying a + * serif monogram over a terracotta rule, the contact channels behind their + * marks, two bulleted lists and a block of closing facts, beside a wide + * column carrying a letter-spaced masthead, the summary, the roles held on a + * ringed rail, a projects grid and the degrees. + * + *

    The columns are divided by a hairline the sidebar carries as its own + * right border, so it is as tall as the sidebar rather than as tall as the + * page. The preset paints nothing: the sheet is drawn on white paper and + * leaves the page size and the margin to the caller — the geometry follows + * the page's own width, so another page rescales rather than breaks.

    + * + *

    One page, and what happens past it

    + * + *

    The two columns are a single row, and a row is atomic — it cannot be + * split — so a CV longer than the sheet does not flow onto a second page: + * composing it raises {@code AtomicNodeTooLargeException}, naming the node + * and the height it needed. It draws no cap of its own, because a CV that + * quietly loses a job is worse than one that refuses to compose. + * {@link TimelineMinimal} is the preset in this package that splits its own + * columns across pages.

    + * + *

    How a document reaches its berth

    + * + *

    The columns are fixed, so the preset assigns each section a berth by + * title rather than reading {@link + * com.demcha.compose.document.templates.cv.data.Slot}. It has eight:

    + * + *
      + *
    • competencies and software, {@link SkillsSection}s; certifications, + * an {@link EntriesSection} of one-line entries; and the closing + * facts, an {@link EntriesSection} whose bodies are one line per + * value — in the sidebar;
    • + *
    • the summary, a {@link ParagraphSection} of one paragraph per line; + * experience, projects and education, {@link EntriesSection}s — in the + * main column.
    • + *
    + * + *

    A section whose title matches no berth is not drawn, a berth with no + * section takes its heading and the divider above it with it, and a berth is + * filled by the first section that names it. The two bulleted lists are + * skills without levels: this design writes them as plain lines, so a rating + * would have nowhere to go — one takes a terracotta square and no dash under + * its heading, the other a disc and a dash, which is how the sheet tells two + * lists of one-liners apart.

    + * + *

    The monogram, the marks and the links

    + * + *

    The monogram is drawn from the name's own initials rather than a field + * of its own: a document states its name once, and a monogram that could + * disagree with it would be a second place to keep true.

    + * + *

    Each fact and each project takes the mark its entry names in + * {@code CvEntry.icon()}, from this preset's own vocabulary — + * {@code globe}, {@code badge}, {@code clock}, {@code building}, + * {@code hotel}, {@code house} — and an unknown token is reported as a data + * error naming the set. An entry with no token is drawn without a mark.

    + * + *

    Every title the preset draws — a role, a project, a degree, a + * credential — becomes a link when its entry carries {@code CvEntry.link()}. + * It costs the layout nothing, because a link is an annotation rather than + * ink, so a linked title and a plain one are the same line. The closing facts + * are the exception: their bold line is a label for the values under it — + * "Languages:", "Availability:" — rather than the name of something a reader + * could open.

    + * + *

    The email, the phone and each link are reachable from the PDF, with the + * {@code mailto:} and {@code tel:} targets built from the values. A link is + * drawn as its own label with the address behind it — {@code Link("LinkedIn", + * "https://…")} sets the word and links the URL — so the four contact rows sit + * on one axis at one size whatever a profile happens to be called. It takes + * the mark of the network it points at, or a globe.

    + * + *

    Fonts

    + * + *

    The sheet is set in Lato, with PT Serif for the monogram. The templates + * artifact carries neither — register them on the session, or the engine + * substitutes and the measured geometry here no longer matches the type + * sitting in it.

    + * + *

    Usage:

    + *
    {@code
    + * DocumentTemplate template = TerracottaRail.create();
    + * template.compose(session, cv);
    + * }
    + * + * @since 2.4.0 + */ +public final class TerracottaRail { + + /** Stable identifier of this preset. */ + public static final String ID = "terracotta-rail"; + + /** Human-readable name of this preset. */ + public static final String DISPLAY_NAME = "Terracotta Rail"; + + /** + * The margin this preset expects: none. Both columns run to the paper + * edge and each carries its own padding. + */ + public static final double RECOMMENDED_MARGIN = 0.0; + + private static final List COMPETENCY_KEYS = + List.of("competencies", "competency", "skills", "expertise"); + private static final List SOFTWARE_KEYS = + List.of("software", "tools", "stack"); + private static final List CERTIFICATION_KEYS = + List.of("certifications", "certification", "licences", "licenses"); + private static final List FACT_KEYS = + List.of("additional", "details", "other"); + private static final List SUMMARY_KEYS = + List.of("summary", "profile", "about"); + private static final List EXPERIENCE_KEYS = + List.of("experience", "employment", "work history", "career"); + private static final List PROJECT_KEYS = + List.of("projects", "project", "portfolio", "selected work"); + private static final List EDUCATION_KEYS = + List.of("education", "qualifications"); + + private TerracottaRail() { + } + + /** + * Creates the template. + * + * @return a template composing a {@link CvDocument} + */ + public static DocumentTemplate create() { + return new Template(); + } + + private record Template() implements DocumentTemplate { + + @Override + public String id() { + return ID; + } + + @Override + public String displayName() { + return DISPLAY_NAME; + } + + @Override + public void compose(DocumentSession document, CvDocument doc) { + Objects.requireNonNull(document, "document"); + Objects.requireNonNull(doc, "doc"); + List sections = doc.sections(); + + // Software first, and the competency berth then skips whatever it + // claimed: a section titled for both — "Software Skills" — would + // otherwise fill the two berths and be drawn twice. + SkillsSection software = berth(sections, SkillsSection.class, SOFTWARE_KEYS); + SkillsSection competencies = + berthExcept(sections, SkillsSection.class, COMPETENCY_KEYS, software); + EntriesSection certifications = + berth(sections, EntriesSection.class, CERTIFICATION_KEYS); + EntriesSection facts = berth(sections, EntriesSection.class, FACT_KEYS); + ParagraphSection summary = berth(sections, ParagraphSection.class, SUMMARY_KEYS); + EntriesSection experience = berth(sections, EntriesSection.class, EXPERIENCE_KEYS); + EntriesSection projects = berth(sections, EntriesSection.class, PROJECT_KEYS); + EntriesSection education = berth(sections, EntriesSection.class, EDUCATION_KEYS); + + document.pageFlow(page -> page + .name("TerracottaRailCv") + .spacing(0) + .addRow("Body", row -> { + row.name("Body"); + row.spacing(0); + row.weights(SIDEBAR_WEIGHT, MAIN_WEIGHT); + row.addSection("Sidebar", side -> + TerracottaRailAside.compose(side, doc.identity(), competencies, + software, certifications, facts)); + row.addSection("MainColumn", main -> + TerracottaRailMain.compose(main, doc.identity(), summary, + experience, projects, education)); + })); + } + + /** + * The first section of the wanted shape whose title names this berth, + * or {@code null} when the document fills no such berth. + */ + private static T berth(List sections, + Class type, + List keys) { + return berthExcept(sections, type, keys, null); + } + + /** The same, skipping a section another berth has already claimed. */ + private static T berthExcept(List sections, + Class type, + List keys, + CvSection taken) { + for (CvSection section : sections) { + if (!type.isInstance(section) || section == taken) { + continue; + } + for (String key : keys) { + if (SectionLookup.titleContains(section.title(), key)) { + return type.cast(section); + } + } + } + return null; + } + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/TerracottaRailAside.java b/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/TerracottaRailAside.java new file mode 100644 index 000000000..e72a7b111 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/TerracottaRailAside.java @@ -0,0 +1,295 @@ +package com.demcha.compose.document.templates.cv.presets; + +import com.demcha.compose.document.dsl.SectionBuilder; +import com.demcha.compose.document.node.DocumentLinkOptions; +import com.demcha.compose.document.style.DocumentInsets; +import com.demcha.compose.document.templates.core.identity.Contact; +import com.demcha.compose.document.templates.core.identity.ContactUri; +import com.demcha.compose.document.templates.core.identity.Link; +import com.demcha.compose.document.templates.cv.data.CvEntry; +import com.demcha.compose.document.templates.cv.data.CvIdentity; +import com.demcha.compose.document.templates.cv.data.CvSkill; +import com.demcha.compose.document.templates.cv.data.EntriesSection; +import com.demcha.compose.document.templates.cv.data.SkillGroup; +import com.demcha.compose.document.templates.cv.data.SkillsSection; + +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; + +import static com.demcha.compose.document.templates.cv.presets.TerracottaRailStyles.ACCENT; +import static com.demcha.compose.document.templates.cv.presets.TerracottaRailStyles.ACCENT_RULE_THICKNESS; +import static com.demcha.compose.document.templates.cv.presets.TerracottaRailStyles.COLUMN_PAD_BOTTOM; +import static com.demcha.compose.document.templates.cv.presets.TerracottaRailStyles.CONTACT_ROW_GAP; +import static com.demcha.compose.document.templates.cv.presets.TerracottaRailStyles.DETAIL_SIZE; +import static com.demcha.compose.document.templates.cv.presets.TerracottaRailStyles.INFO_BLOCK_GAP; +import static com.demcha.compose.document.templates.cv.presets.TerracottaRailStyles.INFO_LINE_GAP; +import static com.demcha.compose.document.templates.cv.presets.TerracottaRailStyles.INFO_WEIGHTS; +import static com.demcha.compose.document.templates.cv.presets.TerracottaRailStyles.INK; +import static com.demcha.compose.document.templates.cv.presets.TerracottaRailStyles.MONOGRAM_RULE_TO_CONTACT_GAP; +import static com.demcha.compose.document.templates.cv.presets.TerracottaRailStyles.MONOGRAM_RULE_WIDTH; +import static com.demcha.compose.document.templates.cv.presets.TerracottaRailStyles.MONOGRAM_SIZE; +import static com.demcha.compose.document.templates.cv.presets.TerracottaRailStyles.MONOGRAM_TO_RULE_GAP; +import static com.demcha.compose.document.templates.cv.presets.TerracottaRailStyles.RULE; +import static com.demcha.compose.document.templates.cv.presets.TerracottaRailStyles.RULE_THICKNESS; +import static com.demcha.compose.document.templates.cv.presets.TerracottaRailStyles.SIDEBAR_DASH_WIDTH; +import static com.demcha.compose.document.templates.cv.presets.TerracottaRailStyles.SIDEBAR_DIVIDER_GAP; +import static com.demcha.compose.document.templates.cv.presets.TerracottaRailStyles.SIDEBAR_HEADING_SPACER; +import static com.demcha.compose.document.templates.cv.presets.TerracottaRailStyles.SIDEBAR_PAD_LEFT; +import static com.demcha.compose.document.templates.cv.presets.TerracottaRailStyles.SIDEBAR_PAD_RIGHT; +import static com.demcha.compose.document.templates.cv.presets.TerracottaRailStyles.SIDEBAR_PAD_TOP; +import static com.demcha.compose.document.templates.cv.presets.TerracottaRailStyles.serif; +import static com.demcha.compose.document.templates.cv.presets.TerracottaRailStyles.text; +import static com.demcha.compose.document.templates.cv.presets.TerracottaRailWidgets.bulletLine; +import static com.demcha.compose.document.templates.cv.presets.TerracottaRailWidgets.divider; +import static com.demcha.compose.document.templates.cv.presets.TerracottaRailWidgets.heading; +import static com.demcha.compose.document.templates.cv.presets.TerracottaRailWidgets.headingWithDash; +import static com.demcha.compose.document.templates.cv.presets.TerracottaRailWidgets.inlineIcon; +import static com.demcha.compose.document.templates.cv.presets.TerracottaRailWidgets.layeredRow; + +/** + * The narrow column: the monogram, the contact channels, the two bulleted + * lists, the credentials and the closing facts. + */ +final class TerracottaRailAside { + + private TerracottaRailAside() { + } + + static void compose(SectionBuilder side, CvIdentity identity, SkillsSection competencies, + SkillsSection software, EntriesSection certifications, + EntriesSection facts) { + side.name("Sidebar"); + side.spacing(0); + side.accentRight(RULE, RULE_THICKNESS); + side.padding((float) SIDEBAR_PAD_TOP, (float) SIDEBAR_PAD_RIGHT, + (float) COLUMN_PAD_BOTTOM, (float) SIDEBAR_PAD_LEFT); + + renderMonogram(side, identity); + renderContact(side, identity); + + if (hasSkills(competencies)) { + divider(side, "AfterContact", SIDEBAR_DIVIDER_GAP, SIDEBAR_DIVIDER_GAP); + renderBulletedSkills(side, competencies, "CoreCompetencies", "Competency", false); + } + if (hasSkills(software)) { + divider(side, "AfterCompetencies", SIDEBAR_DIVIDER_GAP, SIDEBAR_DIVIDER_GAP); + renderBulletedSkills(side, software, "Software", "Software", true); + } + if (hasEntries(certifications)) { + divider(side, "AfterSoftware", SIDEBAR_DIVIDER_GAP, SIDEBAR_DIVIDER_GAP); + renderCertifications(side, certifications); + } + if (hasEntries(facts)) { + divider(side, "AfterCertifications", SIDEBAR_DIVIDER_GAP, SIDEBAR_DIVIDER_GAP); + renderFacts(side, facts); + } + } + + // -- the lockup -------------------------------------------------------- + + /** + * The monogram over its terracotta rule. It is drawn from the name's own + * initials rather than a field of its own — a document states the name + * once, and a monogram that could disagree with it would be a second + * place to keep true. + */ + private static void renderMonogram(SectionBuilder side, CvIdentity identity) { + side.addParagraph(p -> p + .name("Monogram") + .text(monogram(identity)) + .textStyle(serif(MONOGRAM_SIZE, ACCENT)) + .margin(0f, 0f, (float) MONOGRAM_TO_RULE_GAP, 0f)); + side.addLine(line -> line + .name("MonogramRule") + .horizontal(MONOGRAM_RULE_WIDTH) + .thickness(ACCENT_RULE_THICKNESS) + .color(ACCENT) + .margin(new DocumentInsets(0, 0, MONOGRAM_RULE_TO_CONTACT_GAP, 0))); + } + + /** The initials of the given and the family name, in capitals. */ + private static String monogram(CvIdentity identity) { + StringBuilder out = new StringBuilder(2); + appendInitial(out, identity.name().first()); + appendInitial(out, identity.name().last()); + return out.toString(); + } + + private static void appendInitial(StringBuilder out, String part) { + if (!part.isBlank()) { + out.append(Character.toUpperCase(part.charAt(0))); + } + } + + // -- the channels ------------------------------------------------------ + + /** + * The contact block: the three channels and a row per link, all on one + * axis behind their marks. + * + *

    A link shows its own label and carries the address behind it, which + * is what keeps the rows the same. Writing the URL out would make that row + * as wide as whatever the reader's profile happens to be called — long + * enough to need setting smaller than the rows above it, and a different + * width for every document.

    + */ + private static void renderContact(SectionBuilder side, CvIdentity identity) { + side.addSection("Contact", block -> { + block.spacing(0); + // The contact triple is non-blank by construction, so the three + // channels are always drawn; only the links are optional. + Contact contact = identity.contact(); + channel(block, 0, TerracottaRailIcons.EMAIL, contact.email(), + "mailto:" + contact.email()); + channel(block, 1, TerracottaRailIcons.PHONE, contact.phone(), + ContactUri.tel(contact.phone())); + channel(block, 2, TerracottaRailIcons.LOCATION, contact.address(), null); + int index = 3; + for (Link link : identity.links()) { + channel(block, index++, markFor(link), link.label(), link.url()); + } + }); + } + + private static void channel(SectionBuilder block, int index, String token, String value, + String href) { + block.addParagraph(p -> { + p.name("Contact_" + index); + inlineIcon(p, token, TerracottaRailIcons.CONTACT_SIZE); + p.inlineText(" "); + if (href == null || href.isBlank()) { + p.inlineText(value, text(DETAIL_SIZE, INK, false)); + } else { + p.inlineText(value, text(DETAIL_SIZE, INK, false), + new DocumentLinkOptions(href)); + } + p.margin(0f, 0f, (float) CONTACT_ROW_GAP, 0f); + }); + } + + /** A link takes the mark of the network it points at, or a globe. */ + private static String markFor(Link link) { + String target = (link.url() + " " + link.label()).toLowerCase(Locale.ROOT); + return target.contains("linkedin") + ? TerracottaRailIcons.LINKEDIN + : TerracottaRailIcons.GLOBE; + } + + // -- the lists --------------------------------------------------------- + + /** + * A bulleted list of skills. The competencies take a terracotta square + * and no dash under their heading; the software list takes a disc and a + * dash — the design's own way of telling two lists of one-liners apart. + */ + private static void renderBulletedSkills(SectionBuilder side, SkillsSection section, + String blockName, String prefix, + boolean discBullet) { + side.addSection(blockName, block -> { + block.spacing(0); + if (discBullet) { + headingWithDash(block, section.title(), SIDEBAR_HEADING_SPACER, + SIDEBAR_DASH_WIDTH); + } else { + heading(block, section.title(), SIDEBAR_HEADING_SPACER); + } + int index = 0; + for (SkillGroup group : section.groups()) { + for (CvSkill skill : group.entries()) { + String name = prefix + "_" + index++; + if (discBullet) { + bulletLine(block, name, p -> p.dot(3.0, ACCENT), skill.name(), + INK, DETAIL_SIZE, null); + } else { + bulletLine(block, name, + p -> inlineIcon(p, TerracottaRailIcons.SQUARE, + TerracottaRailIcons.BULLET_SIZE), + skill.name(), INK, DETAIL_SIZE, null); + } + } + } + }); + } + + /** The credentials: one disc-bulleted line each. */ + private static void renderCertifications(SectionBuilder side, EntriesSection section) { + side.addSection("Certifications", block -> { + block.spacing(0); + headingWithDash(block, section.title(), SIDEBAR_HEADING_SPACER, SIDEBAR_DASH_WIDTH); + List entries = section.entries(); + for (int index = 0; index < entries.size(); index++) { + CvEntry entry = entries.get(index); + bulletLine(block, "Certification_" + index, p -> p.dot(3.0, ACCENT), + entry.title(), INK, DETAIL_SIZE, entry.link()); + } + }); + } + + /** + * The closing facts: a mark beside a label and the lines under it. + * + *

    The mark is an inline run inside a paragraph rather than a block + * icon node: a block icon in a row cell makes the whole row lay its cells + * out vertically.

    + */ + private static void renderFacts(SectionBuilder side, EntriesSection section) { + side.addSection("AdditionalInformation", block -> { + block.spacing(0); + heading(block, section.title(), SIDEBAR_HEADING_SPACER); + List entries = section.entries(); + for (int i = 0; i < entries.size(); i++) { + CvEntry entry = entries.get(i); + int index = i; + layeredRow(block, "AddInfo_" + index, 0.0, INFO_BLOCK_GAP, row -> { + row.weights(INFO_WEIGHTS[0], INFO_WEIGHTS[1]); + row.addParagraph(p -> { + p.name("Icon_" + index); + if (!entry.icon().isBlank()) { + inlineIcon(p, entry.icon(), TerracottaRailIcons.FACT_SIZE); + } + }); + row.addSection("Text_" + index, cell -> { + cell.spacing(0); + cell.addParagraph(p -> p + .name("Label_" + index) + .text(entry.title()) + .textStyle(text(DETAIL_SIZE, INK, true)) + .margin(0f, 0f, (float) INFO_LINE_GAP, 0f)); + for (String line : lines(entry.body())) { + cell.addParagraph(p -> p + .name("Val_" + index) + .text(line) + .textStyle(text(DETAIL_SIZE, INK, false)) + .margin(0f, 0f, (float) INFO_LINE_GAP, 0f)); + } + }); + }); + } + }); + } + + /** A fact's body, one line per line the document wrote. */ + private static List lines(String body) { + List out = new ArrayList<>(); + for (String line : body.split(String.valueOf((char) 10))) { + if (!line.isBlank()) { + out.add(line.strip()); + } + } + return out; + } + + // -- presence ---------------------------------------------------------- + + private static boolean hasSkills(SkillsSection section) { + if (section == null) { + return false; + } + return section.groups().stream().anyMatch(group -> !group.entries().isEmpty()); + } + + private static boolean hasEntries(EntriesSection section) { + return section != null && !section.entries().isEmpty(); + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/TerracottaRailIcons.java b/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/TerracottaRailIcons.java new file mode 100644 index 000000000..24f8795af --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/TerracottaRailIcons.java @@ -0,0 +1,97 @@ +package com.demcha.compose.document.templates.cv.presets; + +import com.demcha.compose.document.svg.SvgIcon; + +import java.io.IOException; +import java.io.InputStream; +import java.io.UncheckedIOException; +import java.nio.charset.StandardCharsets; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Classpath loader for the Terracotta Rail CV marks — the four contact + * glyphs and the bullet in the sidebar, the three marks a fact block may + * name, and the three a project may. + * + *

    They ship inside the templates artifact under + * {@code templates/cv/terracotta-rail/icons/} as SVG, so they scale with the + * box they are drawn into rather than being resampled. Parsed icons are + * cached per token, so a sheet that repeats a mark reads each file once per + * JVM.

    + * + *

    {@link #ENTRY_TOKENS} is the vocabulary a document names through + * {@code CvEntry.icon()}. It is scoped to this preset — another preset + * packages its own set — and an entry that names none is drawn without a + * mark.

    + */ +final class TerracottaRailIcons { + + static final String EMAIL = "email"; + static final String PHONE = "phone"; + static final String LOCATION = "location"; + static final String LINKEDIN = "linkedin"; + static final String GLOBE = "globe"; + static final String SQUARE = "square"; + + private static final String ICON_ROOT = "/templates/cv/terracotta-rail/icons/"; + + /** A contact glyph is set at the size of the line beside it. */ + static final double CONTACT_SIZE = 10.0; + + /** The competency bullet — a terracotta square, not a disc. */ + static final double BULLET_SIZE = 3.8; + + /** A fact's mark stands beside a two-line block, so it is drawn larger. */ + static final double FACT_SIZE = 16.0; + + /** A project's mark is the sketch that opens its row. */ + static final double PROJECT_SIZE = 22.0; + + /** The marks a document may name on a fact or a project entry. */ + static final Set ENTRY_TOKENS = + Set.of(GLOBE, "badge", "clock", "building", "hotel", "house"); + + private static final Set TOKENS = + Set.of(EMAIL, PHONE, LOCATION, LINKEDIN, SQUARE, + GLOBE, "badge", "clock", "building", "hotel", "house"); + + private static final Map CACHE = new ConcurrentHashMap<>(); + + private TerracottaRailIcons() { + } + + /** + * Reads one mark. + * + * @param token one of the packaged tokens + * @return the parsed icon + * @throws IllegalArgumentException when the token names no packaged mark, + * listing the ones a document may choose — an icon token is data, + * so a wrong one is a data error + */ + static SvgIcon icon(String token) { + if (!TOKENS.contains(token)) { + throw new IllegalArgumentException( + "Unknown terracotta rail CV icon token '" + token + + "'. This preset packages " + ENTRY_TOKENS.stream().sorted().toList() + + " for an entry mark."); + } + return CACHE.computeIfAbsent(token, TerracottaRailIcons::read); + } + + private static SvgIcon read(String token) { + String resourcePath = ICON_ROOT + token + ".svg"; + try (InputStream input = TerracottaRailIcons.class.getResourceAsStream(resourcePath)) { + if (input == null) { + throw new IllegalStateException( + "Missing terracotta rail CV icon: " + resourcePath); + } + return SvgIcon.parse(new String(input.readAllBytes(), StandardCharsets.UTF_8)); + } catch (IOException e) { + throw new UncheckedIOException( + "Failed to read terracotta rail CV icon: " + resourcePath, e); + } + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/TerracottaRailMain.java b/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/TerracottaRailMain.java new file mode 100644 index 000000000..fa43204bf --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/TerracottaRailMain.java @@ -0,0 +1,381 @@ +package com.demcha.compose.document.templates.cv.presets; + +import com.demcha.compose.document.dsl.SectionBuilder; +import com.demcha.compose.document.dsl.TimelineRailEnd; +import com.demcha.compose.document.node.DocumentLinkOptions; +import com.demcha.compose.document.node.DocumentNode; +import com.demcha.compose.document.node.LayerAlign; +import com.demcha.compose.document.node.ListMarker; +import com.demcha.compose.document.style.DocumentInsets; +import com.demcha.compose.document.style.DocumentStroke; +import com.demcha.compose.document.templates.cv.data.CvEntry; +import com.demcha.compose.document.templates.cv.data.CvIdentity; +import com.demcha.compose.document.templates.cv.data.EntriesSection; +import com.demcha.compose.document.templates.cv.data.ParagraphSection; + +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; + +import static com.demcha.compose.document.templates.cv.presets.TerracottaRailStyles.ACCENT; +import static com.demcha.compose.document.templates.cv.presets.TerracottaRailStyles.BODY_SIZE; +import static com.demcha.compose.document.templates.cv.presets.TerracottaRailStyles.COLUMN_PAD_BOTTOM; +import static com.demcha.compose.document.templates.cv.presets.TerracottaRailStyles.DETAIL_SIZE; +import static com.demcha.compose.document.templates.cv.presets.TerracottaRailStyles.EDUCATION_BAND_PAD_RIGHT; +import static com.demcha.compose.document.templates.cv.presets.TerracottaRailStyles.EDUCATION_BAND_WEIGHT; +import static com.demcha.compose.document.templates.cv.presets.TerracottaRailStyles.EDUCATION_ENTRY_WIDTH; +import static com.demcha.compose.document.templates.cv.presets.TerracottaRailStyles.EDUCATION_PERIOD_SHARE; +import static com.demcha.compose.document.templates.cv.presets.TerracottaRailStyles.EMPLOYER_GAP; +import static com.demcha.compose.document.templates.cv.presets.TerracottaRailStyles.ENTRY_GAP; +import static com.demcha.compose.document.templates.cv.presets.TerracottaRailStyles.ENTRY_INDENT; +import static com.demcha.compose.document.templates.cv.presets.TerracottaRailStyles.ENTRY_WIDTH; +import static com.demcha.compose.document.templates.cv.presets.TerracottaRailStyles.HIGHLIGHT_ITEM_SPACING; +import static com.demcha.compose.document.templates.cv.presets.TerracottaRailStyles.HIGHLIGHT_LINE_SPACING; +import static com.demcha.compose.document.templates.cv.presets.TerracottaRailStyles.INK; +import static com.demcha.compose.document.templates.cv.presets.TerracottaRailStyles.ITEM_TITLE_SIZE; +import static com.demcha.compose.document.templates.cv.presets.TerracottaRailStyles.MAIN_DASH_WIDTH; +import static com.demcha.compose.document.templates.cv.presets.TerracottaRailStyles.MAIN_DIVIDER_BOTTOM; +import static com.demcha.compose.document.templates.cv.presets.TerracottaRailStyles.MAIN_DIVIDER_TOP; +import static com.demcha.compose.document.templates.cv.presets.TerracottaRailStyles.MAIN_HEADING_SPACER; +import static com.demcha.compose.document.templates.cv.presets.TerracottaRailStyles.MAIN_PAD_LEFT; +import static com.demcha.compose.document.templates.cv.presets.TerracottaRailStyles.MAIN_PAD_RIGHT; +import static com.demcha.compose.document.templates.cv.presets.TerracottaRailStyles.MAIN_PAD_TOP; +import static com.demcha.compose.document.templates.cv.presets.TerracottaRailStyles.MASTHEAD_DIVIDER_BOTTOM; +import static com.demcha.compose.document.templates.cv.presets.TerracottaRailStyles.MASTHEAD_DIVIDER_TOP; +import static com.demcha.compose.document.templates.cv.presets.TerracottaRailStyles.MUTED; +import static com.demcha.compose.document.templates.cv.presets.TerracottaRailStyles.NAME_SIZE; +import static com.demcha.compose.document.templates.cv.presets.TerracottaRailStyles.NAME_TO_SUBTITLE_GAP; +import static com.demcha.compose.document.templates.cv.presets.TerracottaRailStyles.PROJECT_DESCRIPTION_PAD_LEFT; +import static com.demcha.compose.document.templates.cv.presets.TerracottaRailStyles.PROJECT_DIVIDER_GAP; +import static com.demcha.compose.document.templates.cv.presets.TerracottaRailStyles.PROJECT_LINE_SPACING; +import static com.demcha.compose.document.templates.cv.presets.TerracottaRailStyles.PROJECT_META_PAD_LEFT; +import static com.demcha.compose.document.templates.cv.presets.TerracottaRailStyles.PROJECT_META_PAD_RIGHT; +import static com.demcha.compose.document.templates.cv.presets.TerracottaRailStyles.PROJECT_ROW_GAP; +import static com.demcha.compose.document.templates.cv.presets.TerracottaRailStyles.PROJECT_WEIGHTS; +import static com.demcha.compose.document.templates.cv.presets.TerracottaRailStyles.ROLE_PERIOD_SHARE; +import static com.demcha.compose.document.templates.cv.presets.TerracottaRailStyles.RULE; +import static com.demcha.compose.document.templates.cv.presets.TerracottaRailStyles.RULE_THICKNESS; +import static com.demcha.compose.document.templates.cv.presets.TerracottaRailStyles.SUBTITLE_SIZE; +import static com.demcha.compose.document.templates.cv.presets.TerracottaRailStyles.SUBTITLE_TO_RULE_GAP; +import static com.demcha.compose.document.templates.cv.presets.TerracottaRailStyles.SUMMARY_LINE_GAP; +import static com.demcha.compose.document.templates.cv.presets.TerracottaRailStyles.SUMMARY_LINE_SPACING; +import static com.demcha.compose.document.templates.cv.presets.TerracottaRailStyles.italic; +import static com.demcha.compose.document.templates.cv.presets.TerracottaRailStyles.text; +import static com.demcha.compose.document.templates.cv.presets.TerracottaRailStyles.MARKER_DIAMETER; +import static com.demcha.compose.document.templates.cv.presets.TerracottaRailWidgets.divider; +import static com.demcha.compose.document.templates.cv.presets.TerracottaRailWidgets.heading; +import static com.demcha.compose.document.templates.cv.presets.TerracottaRailWidgets.headingWithDash; +import static com.demcha.compose.document.templates.cv.presets.TerracottaRailWidgets.inlineIcon; +import static com.demcha.compose.document.templates.cv.presets.TerracottaRailWidgets.layeredRow; +import static com.demcha.compose.document.templates.cv.presets.TerracottaRailWidgets.entryLine; +import static com.demcha.compose.document.templates.cv.presets.TerracottaRailWidgets.ringMarker; +import static com.demcha.compose.document.templates.cv.presets.TerracottaRailWidgets.titleAndDate; +import static com.demcha.compose.document.templates.cv.presets.TerracottaRailWidgets.trackedWide; + +/** + * The wide column: the masthead, the summary, the roles held on a rail, the + * projects grid and the degrees. + */ +final class TerracottaRailMain { + + private TerracottaRailMain() { + } + + static void compose(SectionBuilder main, CvIdentity identity, ParagraphSection summary, + EntriesSection experience, EntriesSection projects, + EntriesSection education) { + main.name("MainColumn"); + main.spacing(0); + main.padding((float) MAIN_PAD_TOP, (float) MAIN_PAD_RIGHT, + (float) COLUMN_PAD_BOTTOM, (float) MAIN_PAD_LEFT); + + renderMasthead(main, identity); + + if (hasBody(summary)) { + divider(main, "AfterMasthead", MASTHEAD_DIVIDER_TOP, + MASTHEAD_DIVIDER_BOTTOM); + renderSummary(main, summary); + } + if (hasEntries(experience)) { + mainDivider(main, "AfterSummary"); + renderExperience(main, experience); + } + if (hasEntries(projects)) { + mainDivider(main, "AfterExperience"); + renderProjects(main, projects); + } + if (hasEntries(education)) { + mainDivider(main, "AfterProjects"); + renderEducation(main, education); + } + } + + private static void mainDivider(SectionBuilder main, String name) { + divider(main, name, MAIN_DIVIDER_TOP, + MAIN_DIVIDER_BOTTOM); + } + + // -- masthead ---------------------------------------------------------- + + /** The name over its role, both letter-spaced, the role in terracotta. */ + private static void renderMasthead(SectionBuilder main, CvIdentity identity) { + main.addSection("Masthead", block -> { + block.spacing(0); + block.addParagraph(p -> p + .name("FullName") + .text(trackedWide(identity.name().full().toUpperCase(Locale.ROOT))) + .textStyle(text(NAME_SIZE, INK, true)) + .margin(0f, 0f, (float) NAME_TO_SUBTITLE_GAP, 0f)); + block.addParagraph(p -> p + .name("JobTitle") + .text(trackedWide(identity.jobTitle().toUpperCase(Locale.ROOT))) + .textStyle(text(SUBTITLE_SIZE, ACCENT, true)) + .margin(0f, 0f, (float) SUBTITLE_TO_RULE_GAP, 0f)); + }); + } + + // -- summary ----------------------------------------------------------- + + /** The summary, one paragraph per line the document wrote. */ + private static void renderSummary(SectionBuilder main, ParagraphSection summary) { + main.addSection("Summary", block -> { + block.spacing(0); + headingWithDash(block, summary.title(), MAIN_HEADING_SPACER, MAIN_DASH_WIDTH); + List paragraphs = lines(summary.body()); + for (int i = 0; i < paragraphs.size(); i++) { + int index = i; + block.addParagraph(p -> p + .name("SummaryPara_" + index) + .text(paragraphs.get(index)) + .textStyle(text(BODY_SIZE, INK, false)) + .lineSpacing(SUMMARY_LINE_SPACING) + .margin(0f, 0f, (float) SUMMARY_LINE_GAP, 0f)); + } + }); + } + + // -- experience -------------------------------------------------------- + + /** + * The roles held, each on a rail: a ringed marker on a grey hairline that + * runs from one entry to the next. The last entry carries no rail, so the + * line stops at the last marker instead of running past it. + */ + private static void renderExperience(SectionBuilder main, EntriesSection experience) { + main.addSection("Experience", block -> { + block.spacing(0); + heading(block, experience.title(), MAIN_HEADING_SPACER); + SectionBuilder holder = new SectionBuilder(); + holder.name("ExperienceRailHolder"); + holder.spacing(0); + // The axis column centres the rail on itself, so the timeline starts half a ring + // left of the edge the accents drew on and the line lands back on it. + holder.margin(new DocumentInsets(0, 0, 0, -MARKER_DIAMETER / 2.0)); + holder.addTimeline(timeline -> { + timeline.markerOnRail() + .rail(rail -> rail + .stroke(DocumentStroke.of(RULE, RULE_THICKNESS)) + .from(TimelineRailEnd.MARKER).to(TimelineRailEnd.MARKER)) + .axisWidth(MARKER_DIAMETER) + .markerGap(ENTRY_INDENT - MARKER_DIAMETER / 2.0) + .gutter(0) + .spacing(ENTRY_GAP); + List entries = experience.entries(); + for (int i = 0; i < entries.size(); i++) { + CvEntry entry = entries.get(i); + int index = i; + timeline.entry(ringMarker("Role", index), e -> e.content(body -> { + body.spacing(0); + entryLine(body, "Role", index, + titleAndDate("RoleTable_" + index, entry.title(), entry.date(), + ENTRY_WIDTH, ROLE_PERIOD_SHARE, entry.link())); + body.addParagraph(p -> p + .name("Employer_" + index) + .text(entry.subtitle()) + .textStyle(italic(BODY_SIZE, MUTED)) + .margin(0f, 0f, (float) EMPLOYER_GAP, 0f)); + List highlights = lines(entry.body()); + if (!highlights.isEmpty()) { + body.addList(list -> list + .name("Highlights_" + index) + .items(highlights) + .marker(ListMarker.bullet()) + .textStyle(text(BODY_SIZE, INK, false)) + .itemSpacing(HIGHLIGHT_ITEM_SPACING) + .lineSpacing(HIGHLIGHT_LINE_SPACING)); + } + })); + } + }); + DocumentNode experienceRail = holder.build(); + // The timeline goes in through a layer stack rather than straight into + // the block. A timeline publishes a row, a row cannot nest inside a row + // cell, and this block can sit in one — the stack is what insulates it. + // Unwinding the stacks that turn out not to need it buys no capability, + // so they stay until that is done as its own deliberate cleanup. + block.addLayerStack(stack -> stack + .name("ExperienceRail") + .layer(experienceRail, LayerAlign.TOP_LEFT, 0)); + }); + } + + // -- projects ---------------------------------------------------------- + + /** + * The projects grid: a sketch, the project's own three lines, and its + * description across a hairline. + * + *

    The mark is an inline run inside a paragraph rather than a block + * icon node: a block icon in a row cell makes the whole row lay its cells + * out vertically.

    + */ + private static void renderProjects(SectionBuilder main, EntriesSection projects) { + main.addSection("SelectedProjects", block -> { + block.spacing(0); + headingWithDash(block, projects.title(), MAIN_HEADING_SPACER, MAIN_DASH_WIDTH); + List entries = projects.entries(); + for (int i = 0; i < entries.size(); i++) { + CvEntry entry = entries.get(i); + boolean last = i == entries.size() - 1; + int index = i; + layeredRow(block, "Project_" + index, 0.0, PROJECT_ROW_GAP, row -> { + row.weights(PROJECT_WEIGHTS[0], PROJECT_WEIGHTS[1], PROJECT_WEIGHTS[2]); + row.addParagraph(p -> { + p.name("ProjIcon_" + index); + if (!entry.icon().isBlank()) { + inlineIcon(p, entry.icon(), TerracottaRailIcons.PROJECT_SIZE); + } + }); + row.addSection("ProjMeta_" + index, meta -> { + meta.spacing(0); + meta.padding(0f, (float) PROJECT_META_PAD_RIGHT, 0f, + (float) PROJECT_META_PAD_LEFT); + meta.addParagraph(p -> { + p.name("ProjTitle_" + index) + .text(entry.title()) + .textStyle(text(ITEM_TITLE_SIZE, INK, true)) + .margin(0f, 0f, 1.0f, 0f); + if (!entry.link().isBlank()) { + p.link(new DocumentLinkOptions(entry.link())); + } + }); + meta.addParagraph(p -> p + .name("ProjSub_" + index) + .text(entry.subtitle()) + .textStyle(italic(DETAIL_SIZE, MUTED)) + .margin(0f, 0f, 1.0f, 0f)); + meta.addParagraph(p -> p + .name("ProjLoc_" + index) + .text(entry.place()) + .textStyle(text(DETAIL_SIZE, INK, false))); + }); + row.addSection("ProjDesc_" + index, description -> { + description.spacing(0); + description.accentLeft(RULE, RULE_THICKNESS); + description.padding(0f, 0f, 0f, (float) PROJECT_DESCRIPTION_PAD_LEFT); + description.addParagraph(p -> p + .name("ProjDescText_" + index) + .text(entry.body()) + .textStyle(text(BODY_SIZE, INK, false)) + .lineSpacing(PROJECT_LINE_SPACING)); + }); + }); + if (!last) { + block.addLine(line -> line + .name("ProjDiv_" + index) + .fill() + .thickness(RULE_THICKNESS) + .color(RULE) + .margin(new DocumentInsets( + PROJECT_DIVIDER_GAP, 0, PROJECT_DIVIDER_GAP, 0))); + } + } + }); + } + + // -- education --------------------------------------------------------- + + /** + * The degrees, on a rail of their own. + * + *

    Their rail is terracotta where the roles' is grey, and the block + * stops short of the column: the design closes it with a hairline at + * about seven tenths of the main column, which is what the empty band + * beside it is for.

    + */ + private static void renderEducation(SectionBuilder main, EntriesSection education) { + main.addSection("Education", block -> { + block.spacing(0); + headingWithDash(block, education.title(), MAIN_HEADING_SPACER, MAIN_DASH_WIDTH); + layeredRow(block, "EducationBand", 0.0, 0.0, band -> { + band.weights(EDUCATION_BAND_WEIGHT, 1.0 - EDUCATION_BAND_WEIGHT); + band.addSection("EducationEntries", cell -> { + cell.spacing(0); + cell.accentRight(RULE, RULE_THICKNESS); + cell.padding(0f, (float) EDUCATION_BAND_PAD_RIGHT, 0f, 0f); + // A second rail, terracotta rather than grey, and its own timeline: two + // timelines on a page are two rail owners and never merge into one line. + SectionBuilder holder = new SectionBuilder(); + holder.name("EducationRailHolder"); + holder.spacing(0); + holder.margin(new DocumentInsets(0, 0, 0, -MARKER_DIAMETER / 2.0)); + holder.addTimeline(timeline -> { + timeline.markerOnRail() + .rail(rail -> rail + .stroke(DocumentStroke.of(ACCENT, RULE_THICKNESS)) + .from(TimelineRailEnd.MARKER).to(TimelineRailEnd.MARKER)) + .axisWidth(MARKER_DIAMETER) + .markerGap(ENTRY_INDENT - MARKER_DIAMETER / 2.0) + .gutter(0) + .spacing(ENTRY_GAP); + List entries = education.entries(); + for (int i = 0; i < entries.size(); i++) { + CvEntry entry = entries.get(i); + int index = i; + timeline.entry(ringMarker("Edu", index), e -> e.content(body -> { + body.spacing(0); + entryLine(body, "Edu", index, + titleAndDate("EduTable_" + index, entry.title(), entry.date(), + EDUCATION_ENTRY_WIDTH, EDUCATION_PERIOD_SHARE, + entry.link())); + body.addParagraph(p -> p + .name("Institution_" + index) + .text(entry.subtitle()) + .textStyle(italic(BODY_SIZE, MUTED))); + })); + } + }); + DocumentNode educationRail = holder.build(); + // The band's own stack wraps the band row, one level above this cell, so + // it does not insulate a timeline placed inside the cell: this is a row + // slot and needs a layer of its own. + cell.addLayerStack(stack -> stack + .name("EducationRail") + .layer(educationRail, LayerAlign.TOP_LEFT, 0)); + }); + band.addSection("EducationBandRight", empty -> empty.spacing(0)); + }); + }); + } + + // -- shared ------------------------------------------------------------ + + /** A body, one entry per line the document wrote. */ + private static List lines(String body) { + List out = new ArrayList<>(); + for (String line : body.split(String.valueOf((char) 10))) { + if (!line.isBlank()) { + out.add(line.strip()); + } + } + return out; + } + + private static boolean hasBody(ParagraphSection section) { + return section != null && !section.body().isBlank(); + } + + private static boolean hasEntries(EntriesSection section) { + return section != null && !section.entries().isEmpty(); + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/TerracottaRailStyles.java b/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/TerracottaRailStyles.java new file mode 100644 index 000000000..89be2b0a5 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/TerracottaRailStyles.java @@ -0,0 +1,175 @@ +package com.demcha.compose.document.templates.cv.presets; + +import com.demcha.compose.document.style.DocumentColor; +import com.demcha.compose.document.style.DocumentStroke; +import com.demcha.compose.document.style.DocumentTextDecoration; +import com.demcha.compose.document.style.DocumentTextStyle; +import com.demcha.compose.font.FontName; + +/** + * The measured geometry, palette and type scale of the Terracotta Rail CV. + * + *

    Every size here is derived from one body size and every width from the + * page's own, so the sheet is one edit rather than a walk through the + * regions that draw it.

    + */ +final class TerracottaRailStyles { + + private TerracottaRailStyles() { + } + + // -- the two columns -------------------------------------------------- + + /** + * The width every horizontal measure here is a share of: A4's, as the + * design drew it. It is a constant rather than the session's own page, + * because the sheet is a fixed composition — the columns and the bands + * inside them are shares of this number, and a caller who sets another + * page gets the same sheet on it rather than a rescaled one. + */ + static final double PAGE_WIDTH = 595.276; + + static final double SIDEBAR_WEIGHT = 0.308; + static final double MAIN_WEIGHT = 1.0 - SIDEBAR_WEIGHT; + + static final double SIDEBAR_PAD_LEFT = 25.4; + static final double SIDEBAR_PAD_RIGHT = 11.4; + static final double SIDEBAR_PAD_TOP = 20.0; + static final double COLUMN_PAD_BOTTOM = 5.0; + static final double MAIN_PAD_LEFT = 22.0; + static final double MAIN_PAD_RIGHT = 24.0; + static final double MAIN_PAD_TOP = 20.0; + + static final double MAIN_CONTENT_WIDTH = + PAGE_WIDTH * MAIN_WEIGHT - MAIN_PAD_LEFT - MAIN_PAD_RIGHT; + + // -- type ------------------------------------------------------------- + + static final FontName SANS = FontName.LATO; + static final FontName SERIF = FontName.PT_SERIF; + + static final double BODY_SIZE = 8.6; + static final double DETAIL_SIZE = BODY_SIZE * 0.98; + static final double ITEM_TITLE_SIZE = BODY_SIZE * 1.14; + static final double SECTION_HEADING_SIZE = BODY_SIZE * 1.02; + static final double MONOGRAM_SIZE = 36.0; + static final double NAME_SIZE = 28.0; + static final double SUBTITLE_SIZE = 13.0; + + // -- palette ---------------------------------------------------------- + + static final DocumentColor ACCENT = DocumentColor.rgb(201, 74, 41); + static final DocumentColor INK = DocumentColor.rgb(26, 29, 32); + static final DocumentColor MUTED = DocumentColor.rgb(85, 90, 96); + static final DocumentColor RULE = DocumentColor.rgb(216, 216, 216); + static final DocumentColor PAPER = DocumentColor.WHITE; + + static final DocumentStroke NO_BORDER = DocumentStroke.of(PAPER, 0.0); + + // -- rules, rails and the rhythm between blocks ------------------------ + + static final double RULE_THICKNESS = 0.6; + static final double ACCENT_RULE_THICKNESS = 1.4; + static final double MARKER_DIAMETER = 6.2; + + /** How far an entry sits from the rail its marker rides. */ + static final double ENTRY_INDENT = 13.0; + static final double ENTRY_GAP = 9.0; + + static final double HEADING_TO_BODY_GAP = 9.0; + static final double HEADING_TO_DASH_GAP = 3.5; + static final double SIDEBAR_DASH_WIDTH = 20.0; + static final double MAIN_DASH_WIDTH = 22.0; + + static final double SIDEBAR_DIVIDER_GAP = 8.0; + static final double MAIN_DIVIDER_TOP = 6.0; + static final double MAIN_DIVIDER_BOTTOM = 6.5; + static final double MASTHEAD_DIVIDER_TOP = 4.0; + static final double MASTHEAD_DIVIDER_BOTTOM = 6.0; + + static final double MONOGRAM_RULE_WIDTH = 48.0; + static final double MONOGRAM_TO_RULE_GAP = 8.0; + static final double MONOGRAM_RULE_TO_CONTACT_GAP = 10.0; + static final double NAME_TO_SUBTITLE_GAP = 8.0; + static final double SUBTITLE_TO_RULE_GAP = 5.0; + + static final double CONTACT_ROW_GAP = 4.5; + static final double BULLET_ROW_GAP = 3.0; + static final double INFO_BLOCK_GAP = 7.0; + static final double INFO_LINE_GAP = 0.5; + static final double PROJECT_ROW_GAP = 2.0; + static final double PROJECT_DIVIDER_GAP = 2.0; + static final double SUMMARY_LINE_GAP = 2.5; + static final double SUMMARY_LINE_SPACING = 1.30; + static final double EMPLOYER_GAP = 2.5; + static final double HIGHLIGHT_ITEM_SPACING = 1.2; + static final double HIGHLIGHT_LINE_SPACING = 1.15; + + // -- the bands the entries are laid on --------------------------------- + + /** + * The slack a fixed table needs inside the cell it spans. Without it a + * table exactly as wide as its cell is one rounding step too wide. + */ + static final double TABLE_SLACK = 0.01; + + static final double ENTRY_WIDTH = MAIN_CONTENT_WIDTH - ENTRY_INDENT - TABLE_SLACK; + static final double ROLE_PERIOD_SHARE = 0.30; + static final double EDUCATION_PERIOD_SHARE = 0.34; + + /** Education stops short of the column and closes on a hairline. */ + static final double EDUCATION_BAND_WEIGHT = 0.70; + static final double EDUCATION_BAND_PAD_RIGHT = 8.0; + static final double EDUCATION_ENTRY_WIDTH = + MAIN_CONTENT_WIDTH * EDUCATION_BAND_WEIGHT - EDUCATION_BAND_PAD_RIGHT + - ENTRY_INDENT - TABLE_SLACK; + + static final double[] PROJECT_WEIGHTS = {0.08, 0.36, 0.56}; + static final double PROJECT_META_PAD_RIGHT = 8.0; + static final double PROJECT_META_PAD_LEFT = 4.0; + static final double PROJECT_DESCRIPTION_PAD_LEFT = 10.0; + static final double PROJECT_LINE_SPACING = 1.24; + static final double[] INFO_WEIGHTS = {0.18, 0.82}; + + // -- tracking ---------------------------------------------------------- + + /** + * The gaps a heading is letter-spaced with. A text style carries no + * tracking, so a tracked line is written with a space character between + * the letters, and which space is the whole choice: the sidebar takes a + * hair space (a tenth of an em) because a full one would set ADDITIONAL + * INFORMATION wider than the column and wrap it, and the main column, + * being wider, takes a thin space (a fifth). + */ + static final char SIDEBAR_HEADING_SPACER = '\u200A'; + static final char MAIN_HEADING_SPACER = '\u2009'; + + // -- helpers ------------------------------------------------------------ + + static DocumentTextStyle text(double size, DocumentColor color, boolean bold) { + return DocumentTextStyle.builder() + .fontName(SANS) + .size(size) + .color(color) + .decoration(bold ? DocumentTextDecoration.BOLD : DocumentTextDecoration.DEFAULT) + .build(); + } + + static DocumentTextStyle italic(double size, DocumentColor color) { + return DocumentTextStyle.builder() + .fontName(SANS) + .size(size) + .color(color) + .decoration(DocumentTextDecoration.ITALIC) + .build(); + } + + static DocumentTextStyle serif(double size, DocumentColor color) { + return DocumentTextStyle.builder() + .fontName(SERIF) + .size(size) + .color(color) + .decoration(DocumentTextDecoration.DEFAULT) + .build(); + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/TerracottaRailWidgets.java b/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/TerracottaRailWidgets.java new file mode 100644 index 000000000..3e2e1d269 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/TerracottaRailWidgets.java @@ -0,0 +1,283 @@ +package com.demcha.compose.document.templates.cv.presets; + +import com.demcha.compose.document.dsl.EllipseBuilder; +import com.demcha.compose.document.dsl.ParagraphBuilder; +import com.demcha.compose.document.dsl.RowBuilder; +import com.demcha.compose.document.dsl.SectionBuilder; +import com.demcha.compose.document.dsl.TableBuilder; +import com.demcha.compose.document.dsl.TimelineMarker; +import com.demcha.compose.document.node.DocumentLinkOptions; +import com.demcha.compose.document.node.DocumentNode; +import com.demcha.compose.document.node.InlineImageAlignment; +import com.demcha.compose.document.node.LayerAlign; +import com.demcha.compose.document.node.TextAlign; +import com.demcha.compose.document.style.DocumentColor; +import com.demcha.compose.document.style.DocumentInsets; +import com.demcha.compose.document.style.DocumentStroke; +import com.demcha.compose.document.table.DocumentTableCell; +import com.demcha.compose.document.table.DocumentTableColumn; +import com.demcha.compose.document.table.DocumentTableStyle; +import com.demcha.compose.document.table.DocumentTableTextAnchor; + +import java.util.function.Consumer; + +import static com.demcha.compose.document.templates.cv.presets.TerracottaRailStyles.ACCENT; +import static com.demcha.compose.document.templates.cv.presets.TerracottaRailStyles.ACCENT_RULE_THICKNESS; +import static com.demcha.compose.document.templates.cv.presets.TerracottaRailStyles.BULLET_ROW_GAP; +import static com.demcha.compose.document.templates.cv.presets.TerracottaRailStyles.ENTRY_INDENT; +import static com.demcha.compose.document.templates.cv.presets.TerracottaRailStyles.HEADING_TO_BODY_GAP; +import static com.demcha.compose.document.templates.cv.presets.TerracottaRailStyles.HEADING_TO_DASH_GAP; +import static com.demcha.compose.document.templates.cv.presets.TerracottaRailStyles.INK; +import static com.demcha.compose.document.templates.cv.presets.TerracottaRailStyles.ITEM_TITLE_SIZE; +import static com.demcha.compose.document.templates.cv.presets.TerracottaRailStyles.MARKER_DIAMETER; +import static com.demcha.compose.document.templates.cv.presets.TerracottaRailStyles.NO_BORDER; +import static com.demcha.compose.document.templates.cv.presets.TerracottaRailStyles.PAPER; +import static com.demcha.compose.document.templates.cv.presets.TerracottaRailStyles.RULE; +import static com.demcha.compose.document.templates.cv.presets.TerracottaRailStyles.RULE_THICKNESS; +import static com.demcha.compose.document.templates.cv.presets.TerracottaRailStyles.SECTION_HEADING_SIZE; +import static com.demcha.compose.document.templates.cv.presets.TerracottaRailStyles.text; + +/** + * The idioms this sheet repeats: the tracked heading, the rule between + * blocks, the marker riding a rail, the title-and-date line, and the row a + * column has to wrap to hold. + */ +final class TerracottaRailWidgets { + + private TerracottaRailWidgets() { + } + + // -- headings ---------------------------------------------------------- + + /** + * A letter-spaced heading. + * + *

    A text style carries no tracking, so the gap between letters is a + * space character — which one is the whole choice, and the caller makes + * it. The word gap stays clearly wider than the letter gap, or a tracked + * heading reads as one long word.

    + * + * @param text the heading + * @param spacer the space to set between letters + * @return the tracked string + */ + static String tracked(String text, char spacer) { + if (text == null || text.isEmpty()) { + return ""; + } + StringBuilder out = new StringBuilder(text.length() * 2); + for (int index = 0; index < text.length(); index++) { + char letter = text.charAt(index); + if (letter == ' ') { + out.append(spacer).append(' ').append(spacer); + continue; + } + if (index > 0 && text.charAt(index - 1) != ' ') { + out.append(spacer); + } + out.append(letter); + } + return out.toString(); + } + + /** The masthead's own tracking: a full space between letters. */ + static String trackedWide(String text) { + if (text == null || text.isEmpty()) { + return ""; + } + StringBuilder out = new StringBuilder(text.length() * 2); + boolean startOfWord = true; + for (int index = 0; index < text.length(); index++) { + char letter = text.charAt(index); + if (letter == ' ') { + out.append(" "); + startOfWord = true; + continue; + } + if (!startOfWord) { + out.append(' '); + } + out.append(letter); + startOfWord = false; + } + return out.toString(); + } + + /** A heading with nothing under it. */ + static void heading(SectionBuilder block, String title, char spacer) { + block.addParagraph(p -> p + .name("Heading_" + compact(title)) + .text(tracked(title, spacer)) + .textStyle(text(SECTION_HEADING_SIZE, INK, true)) + .margin(0f, 0f, (float) HEADING_TO_BODY_GAP, 0f)); + } + + /** A heading over a short terracotta dash. */ + static void headingWithDash(SectionBuilder block, String title, char spacer, double dashWidth) { + block.addParagraph(p -> p + .name("Heading_" + compact(title)) + .text(tracked(title, spacer)) + .textStyle(text(SECTION_HEADING_SIZE, INK, true)) + .margin(0f, 0f, (float) HEADING_TO_DASH_GAP, 0f)); + block.addLine(line -> line + .name("AccentLine_" + compact(title)) + .horizontal(dashWidth) + .thickness(ACCENT_RULE_THICKNESS) + .color(ACCENT) + .margin(new DocumentInsets( + 0, 0, HEADING_TO_BODY_GAP - HEADING_TO_DASH_GAP, 0))); + } + + /** A node name built from a heading, with the spacing taken out of it. */ + static String compact(String value) { + StringBuilder out = new StringBuilder(value.length()); + for (int index = 0; index < value.length(); index++) { + char letter = value.charAt(index); + if (Character.isLetterOrDigit(letter)) { + out.append(letter); + } + } + return out.toString(); + } + + // -- rules ------------------------------------------------------------- + + /** The hairline that separates two blocks in a column. */ + static void divider(SectionBuilder column, String name, double top, double bottom) { + column.addLine(line -> line + .name("Divider_" + name) + .fill() + .thickness(RULE_THICKNESS) + .color(RULE) + .margin(new DocumentInsets(top, 0, bottom, 0))); + } + + // -- marks ------------------------------------------------------------- + + /** An inline mark, drawn at the size the preset gives its kind. */ + static void inlineIcon(ParagraphBuilder paragraph, String token, double size) { + paragraph.inlineSvgIcon(TerracottaRailIcons.icon(token), size, + InlineImageAlignment.CENTER); + } + + // -- the rail ---------------------------------------------------------- + + /** + * The marker an entry rides: a terracotta ring with the paper inside, declared as a + * timeline marker so the rail runs through the centre of the box it occupies. + */ + static TimelineMarker ringMarker(String prefix, int index) { + return TimelineMarker.custom(MARKER_DIAMETER, MARKER_DIAMETER, + column -> column.addEllipse(ellipse -> ellipse + .name(prefix + "Marker_" + index) + .circle(MARKER_DIAMETER) + .fillColor(PAPER) + .stroke(DocumentStroke.of(ACCENT, 1.0)))); + } + + /** + * An entry's first line, in the layer its table needs. + * + *

    The marker used to be positioned into this stack, nudged left by the entry's own + * indent plus half the ring to land on the rail; it sits in the timeline's axis column + * now. The layer stays because the line is a table and a row cell is no place for one.

    + */ + static void entryLine(SectionBuilder body, String prefix, int index, DocumentNode line) { + body.addLayerStack(stack -> stack + .name(prefix + "Line_" + index) + .layer(line, LayerAlign.TOP_LEFT, 0)); + } + + /** + * A title with its date flush right on the same line. + * + *

    A table, not a row: a row cannot nest in a row cell and a table can + * — it spans the cell and its columns lay out. Both cells hold a + * paragraph, because a table inside a row cell draws only leaf + * content.

    + * + *

    The title carries the entry's link when it has one. That costs the + * layout nothing — a link is an annotation rather than ink — so a linked + * title and a plain one are the same line.

    + * + * @param name the node name this line's parts are built from + * @param title the entry's title + * @param date the date, set flush right + * @param width the line's width + * @param dateShare how much of that width the date takes + * @param link the target the title points at, blank for none + * @return the line node + */ + static DocumentNode titleAndDate(String name, String title, String date, + double width, double dateShare, String link) { + double dateColumn = width * dateShare; + ParagraphBuilder titleCell = new ParagraphBuilder() + .name(name + "_Title") + .text(title) + .lineSpacing(0) + .textStyle(text(ITEM_TITLE_SIZE, INK, true)); + if (link != null && !link.isBlank()) { + titleCell.link(new DocumentLinkOptions(link)); + } + return new TableBuilder() + .name(name) + .width(width) + .columns(DocumentTableColumn.fixed(width - dateColumn), + DocumentTableColumn.fixed(dateColumn)) + .defaultCellStyle(DocumentTableStyle.builder() + .padding(DocumentInsets.zero()) + .textAnchor(DocumentTableTextAnchor.BOTTOM_LEFT) + .stroke(NO_BORDER) + .build()) + .rowCells( + DocumentTableCell.node(titleCell.build()), + DocumentTableCell.node(new ParagraphBuilder() + .name(name + "_Period") + .text(date) + .align(TextAlign.RIGHT) + .lineSpacing(0) + .textStyle(text(ITEM_TITLE_SIZE, ACCENT, false)) + .build())) + .build(); + } + + // -- rows -------------------------------------------------------------- + + /** + * A row inside a column. + * + *

    A row cannot nest in a row cell on this engine, and both of this + * sheet's columns are cells of the body row — so every horizontal pair + * inside one is wrapped in a layer stack, which can.

    + */ + static void layeredRow(SectionBuilder parent, String name, double marginTop, + double marginBottom, Consumer spec) { + SectionBuilder layer = new SectionBuilder(); + layer.name(name + "Layer"); + layer.spacing(0); + layer.addRow(name, spec); + parent.addLayerStack(stack -> stack + .name(name + "Stack") + .margin(new DocumentInsets(marginTop, 0, marginBottom, 0)) + .layer(layer.build(), LayerAlign.TOP_LEFT, 0)); + } + + /** + * A bullet line: a mark, a gap, and the item beside it — carrying a link + * when one is given. + */ + static void bulletLine(SectionBuilder block, String name, Consumer mark, + String item, DocumentColor color, double size, String link) { + block.addParagraph(p -> { + p.name(name); + mark.accept(p); + p.inlineText(" "); + if (link == null || link.isBlank()) { + p.inlineText(item, text(size, color, false)); + } else { + p.inlineText(item, text(size, color, false), new DocumentLinkOptions(link)); + } + p.margin(0f, 0f, (float) BULLET_ROW_GAP, 0f); + }); + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/TimelineMinimal.java b/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/TimelineMinimal.java index 2e0c2d2b7..82713ad00 100644 --- a/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/TimelineMinimal.java +++ b/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/TimelineMinimal.java @@ -25,6 +25,8 @@ import com.demcha.compose.document.templates.core.identity.SvgGlyph; import com.demcha.compose.document.templates.core.widgets.TimelineAxisWidget; +import com.demcha.compose.document.templates.core.identity.ContactUri; + import java.util.*; /** @@ -433,7 +435,8 @@ private List contactItems(CvIdentity identity) { addContactItem(items, "LOC", "location.svg", identity.contact().address(), null); addContactItem(items, "TEL", "phone.svg", - identity.contact().phone(), null); + identity.contact().phone(), + ContactUri.telLink(identity.contact().phone())); String email = identity.contact().email(); if (!email.isBlank()) { addContactItem(items, "@", "email.svg", email, @@ -538,12 +541,17 @@ private void addMainModule(SectionBuilder main, ColumnPagination.Block module) { .margin(DocumentInsets.zero())); } } else { + // No marker. A bulletOffset used to be set here, and it + // reached no page: the prefix is drawn only under an + // indent strategy that asks for it, and a paragraph's + // default asks for neither the first line nor the + // wrapped ones. What the design has always shown is + // these lines set plain, so that is what it now says. for (String line : lines) { block.addParagraph(paragraph -> paragraph .text(line) .textStyle(mainBulletStyle()) .lineSpacing(1.2) - .bulletOffset("-") .margin(DocumentInsets.zero())); } } diff --git a/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/VioletGrid.java b/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/VioletGrid.java new file mode 100644 index 000000000..4ed4f0b2e --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/VioletGrid.java @@ -0,0 +1,229 @@ +package com.demcha.compose.document.templates.cv.presets; + +import com.demcha.compose.document.api.DocumentSession; +import com.demcha.compose.document.style.DocumentInsets; +import com.demcha.compose.document.templates.api.DocumentTemplate; +import com.demcha.compose.document.templates.cv.components.SectionLookup; +import com.demcha.compose.document.templates.cv.data.CvDocument; +import com.demcha.compose.document.templates.cv.data.CvSection; +import com.demcha.compose.document.templates.cv.data.EntriesSection; +import com.demcha.compose.document.templates.cv.data.ParagraphSection; +import com.demcha.compose.document.templates.cv.data.SkillsSection; + +import java.util.List; +import java.util.Objects; + +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.PAGE; +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.PAGE_MARGIN_BOTTOM; +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.PAGE_MARGIN_TOP; +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.PAGE_MARGIN_X; + +/** + * Violet Grid — a one-page CV in a single column, banded rather than split: a + * two-tone name beside the contact list, three opening lines, a six-up grid of + * marked skills divided by dotted rules, a strip of tools on inline discs, the + * roles held on a dated timeline, the projects behind tinted tiles, education + * and languages side by side, and a tinted band closing on a quotation. + * + *

    Every split is local to one band

    + * + *

    Unlike a sidebar CV this page has no page-level grid. The masthead splits + * where the contact marks begin, the timeline into dates / rail / content, a + * project into tile / copy, and the credentials into two halves — and none of + * those four splits knows about any of the others. That is why every + * horizontal arrangement here is either a top-level row or a table, and why + * none of them nests: a row cannot sit inside a row's cell.

    + * + *

    Three marks that are relationships, not lengths

    + * + *

    A section rule starts where its heading ends, so it is a weighted column + * taking what an auto column leaves. The timeline rail is the left border of + * every entry but the last, so consecutive entries butt into one unbroken line + * whose length follows from how many entries there are. A project's hairline + * is the left border of its copy, so it is exactly as tall as the copy. None + * of the three is a measured height, and none has to be re-measured when the + * content changes.

    + * + *

    One page, and what happens past it

    + * + *

    The page is a stack of bands, so a CV with more content than the design + * holds runs onto a second page rather than losing anything — each experience + * entry is held together, so a role is never cut in half, but the sheet is + * drawn for one page and a longer one will look like a one-page design that + * ran over. {@link TimelineMinimal} is the preset in this package built to + * paginate.

    + * + *

    How a document reaches its berth

    + * + *

    The bands are fixed, so the preset assigns each section a berth by title + * rather than reading {@link + * com.demcha.compose.document.templates.cv.data.Slot}. It has seven:

    + * + *
      + *
    • the summary, a {@link ParagraphSection} of one line per line — set as + * written rather than wrapped, because the design's own breaks leave + * room no greedy wrap would;
    • + *
    • skills, an {@link EntriesSection} whose entries carry a mark, a label + * and a description — the six-up grid;
    • + *
    • tools, a {@link SkillsSection} of unlevelled names — the strip;
    • + *
    • experience and projects, {@link EntriesSection}s;
    • + *
    • education, an {@link EntriesSection} whose first entry is drawn, and + * languages, a {@link SkillsSection} — the closing band;
    • + *
    • the quotation, a {@link ParagraphSection} whose body is the line in + * the tinted band. Only its body is drawn: the title is how the document + * names the berth.
    • + *
    + * + *

    A section whose title matches no berth is not drawn, and a berth with no + * section takes its heading and rule with it.

    + * + *

    The marks, the ratings and the links

    + * + *

    A skill, a project and the degree each take the mark its entry names in + * {@code CvEntry.icon()}, from this preset's own vocabulary, and an unknown + * token is reported as a data error naming the set. An entry with no token is + * drawn without a mark.

    + * + *

    A language draws both channels of its skill: the discs from + * {@code CvSkill.level()} and the words beside them from + * {@code CvSkill.note()}.

    + * + *

    The email, the phone and each link are reachable from the PDF, with the + * {@code mailto:} and {@code tel:} targets built from the values. A link is + * drawn as its own label with the address behind it, so the contact column's + * width does not depend on how long an address happens to be, and it takes the + * mark of the network it points at or the globe. A role, a project title and + * the degree become links when their entry carries {@code CvEntry.link()}.

    + * + *

    Fonts

    + * + *

    The sheet is set in Lato, with Asap Condensed for the name, the headings + * and the skill labels. The templates artifact carries neither — register them + * on the session, or the engine substitutes and the measured geometry here no + * longer matches the type sitting in it.

    + * + *

    Usage:

    + *
    {@code
    + * DocumentTemplate template = VioletGrid.create();
    + * template.compose(session, cv);
    + * }
    + * + * @since 2.4.0 + */ +public final class VioletGrid { + + /** Stable identifier of this preset. */ + public static final String ID = "violet-grid"; + + /** Human-readable name of this preset. */ + public static final String DISPLAY_NAME = "Violet Grid"; + + private static final List SUMMARY_KEYS = + List.of("summary", "profile", "about"); + private static final List QUOTE_KEYS = + List.of("quote", "motto", "tagline", "closing"); + private static final List SKILL_KEYS = + List.of("skills", "expertise", "competencies"); + private static final List TOOL_KEYS = + List.of("tools", "stack", "software"); + private static final List LANGUAGE_KEYS = + List.of("languages", "language"); + private static final List EXPERIENCE_KEYS = + List.of("experience", "employment", "work history", "career"); + private static final List PROJECT_KEYS = + List.of("projects", "project", "portfolio", "selected work"); + private static final List EDUCATION_KEYS = + List.of("education", "qualifications"); + + private VioletGrid() { + } + + /** + * Creates the template. + * + * @return a template composing a {@link CvDocument} + */ + public static DocumentTemplate create() { + return new Template(); + } + + private record Template() implements DocumentTemplate { + + @Override + public String id() { + return ID; + } + + @Override + public String displayName() { + return DISPLAY_NAME; + } + + @Override + public void compose(DocumentSession document, CvDocument doc) { + Objects.requireNonNull(document, "document"); + Objects.requireNonNull(doc, "doc"); + List sections = doc.sections(); + + // The quotation is claimed before the summary: its own keys are + // narrow, but the summary's are broad enough to take a closing line + // titled loosely. + ParagraphSection quote = berth(sections, ParagraphSection.class, QUOTE_KEYS); + ParagraphSection summary = + berthExcept(sections, ParagraphSection.class, SUMMARY_KEYS, quote); + EntriesSection skills = berth(sections, EntriesSection.class, SKILL_KEYS); + SkillsSection tools = berth(sections, SkillsSection.class, TOOL_KEYS); + SkillsSection languages = + berthExcept(sections, SkillsSection.class, LANGUAGE_KEYS, tools); + EntriesSection experience = berth(sections, EntriesSection.class, EXPERIENCE_KEYS); + EntriesSection projects = + berthExcept(sections, EntriesSection.class, PROJECT_KEYS, skills); + EntriesSection education = berth(sections, EntriesSection.class, EDUCATION_KEYS); + + // Every length on the sheet is a share of the design's own grid, so + // the preset sets the page and its margins rather than following + // what the caller configured. + document.pageSize(PAGE).margin(new DocumentInsets( + PAGE_MARGIN_TOP, PAGE_MARGIN_X, PAGE_MARGIN_BOTTOM, PAGE_MARGIN_X)); + document.pageFlow(page -> { + page.name("VioletGridCv"); + page.spacing(0); + VioletGridMasthead.render(page, doc.identity(), summary); + VioletGridBody.renderSkills(page, skills); + VioletGridBody.renderTools(page, tools); + VioletGridBody.renderExperience(page, experience); + VioletGridClosing.renderProjects(page, projects); + VioletGridClosing.renderCredentials(page, education, languages); + VioletGridClosing.renderQuote(page, quote); + }); + } + + /** + * The first section of the wanted shape whose title names this berth, + * or {@code null} when the document fills no such berth. + */ + private static T berth(List sections, + Class type, + List keys) { + return berthExcept(sections, type, keys, null); + } + + /** The same, skipping a section another berth has already claimed. */ + private static T berthExcept(List sections, + Class type, + List keys, + CvSection taken) { + for (CvSection section : sections) { + if (!type.isInstance(section) || section == taken) { + continue; + } + for (String key : keys) { + if (SectionLookup.titleContains(section.title(), key)) { + return type.cast(section); + } + } + } + return null; + } + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/VioletGridBody.java b/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/VioletGridBody.java new file mode 100644 index 000000000..9e32317ed --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/VioletGridBody.java @@ -0,0 +1,371 @@ +package com.demcha.compose.document.templates.cv.presets; + +import com.demcha.compose.document.dsl.PageFlowBuilder; +import com.demcha.compose.document.dsl.ParagraphBuilder; +import com.demcha.compose.document.dsl.SectionBuilder; +import com.demcha.compose.document.dsl.TableBuilder; +import com.demcha.compose.document.dsl.TimelineMarker; +import com.demcha.compose.document.dsl.TimelineRailEnd; +import com.demcha.compose.document.node.DocumentLinkOptions; +import com.demcha.compose.document.node.DocumentNode; +import com.demcha.compose.document.node.HorizontalAlign; +import com.demcha.compose.document.node.TextAlign; +import com.demcha.compose.document.style.DocumentInsets; +import com.demcha.compose.document.style.DocumentRowColumn; +import com.demcha.compose.document.style.DocumentStroke; +import com.demcha.compose.document.style.DocumentTextStyle; +import com.demcha.compose.document.table.DocumentTableCell; +import com.demcha.compose.document.table.DocumentTableColumn; +import com.demcha.compose.document.table.DocumentTableTextAnchor; +import com.demcha.compose.document.templates.cv.data.CvEntry; +import com.demcha.compose.document.templates.cv.data.CvSkill; +import com.demcha.compose.document.templates.cv.data.EntriesSection; +import com.demcha.compose.document.templates.cv.data.SkillGroup; +import com.demcha.compose.document.templates.cv.data.SkillsSection; + +import java.util.ArrayList; +import java.util.List; + +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.ACCENT; +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.BODY; +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.BODY_FONT; +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.BULLET_COLUMN; +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.BULLET_DOT_DIAMETER; +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.BULLET_PITCH; +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.CONTENT_WIDTH; +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.DATE_COLUMN; +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.DATE_TO_MARKER; +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.DISPLAY_FONT; +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.EMPLOYER_SIZE; +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.ENTRY_GAP; +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.ENTRY_INDENT; +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.ENTRY_WIDTH; +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.HEADING_TO_ENTRIES; +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.HEADING_TO_SKILLS; +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.HEADING_TO_TOOLS; +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.HIGHLIGHT_SIZE; +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.ICON_TO_LABEL; +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.INK; +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.LABEL_SIZE; +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.LABEL_TO_DESC; +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.LINE_FACTOR; +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.LOCATION_COLUMN; +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.LOCATION_SIZE; +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.MARKER_DIAMETER; +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.MARKER_TO_COPY; +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.MUTED; +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.PERIOD_SIZE; +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.PIPE; +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.RAIL_INDENT; +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.RAIL_THICKNESS; +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.ROLE_COLUMN; +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.ROLE_SIZE; +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.RULE; +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.SKILLS_TO_TOOLS; +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.SKILL_COLUMN_INSET; +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.SKILL_DASH_OFF; +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.SKILL_DASH_ON; +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.SKILL_DESC_MEASURE; +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.SKILL_DESC_PITCH; +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.SKILL_DESC_SIZE; +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.SKILL_DIVIDER_THICKNESS; +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.SKILL_RAIL_HEIGHT; +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.SUMMARY_TO_SKILLS; +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.TITLE_TO_BULLETS; +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.TOOLS_TO_EXPERIENCE; +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.TOOL_DOT_DIAMETER; +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.TOOL_GAP; +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.TOOL_SIZE; +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.cellStyle; +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.centred; +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.gap; +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.leading; +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.style; +import static com.demcha.compose.document.templates.cv.presets.VioletGridWidgets.headingRow; +import static com.demcha.compose.document.templates.cv.presets.VioletGridWidgets.lines; +import static com.demcha.compose.document.templates.cv.presets.VioletGridWidgets.markerDot; +import static com.demcha.compose.document.templates.cv.presets.VioletGridWidgets.text; + +/** + * The middle of the sheet: the six-up skills grid, the tools strip and the + * dated timeline. + */ +final class VioletGridBody { + + private VioletGridBody() { + } + + // -- the skills grid --------------------------------------------------- + + /** + * The skills grid — one row with 2n−1 columns: n equal content columns + * interleaved with n−1 fixed hairline columns. + * + *

    Building the separators as cells of the same row as the content is + * what stops them drifting from the boundaries they mark, and dividing by + * the entry count is what lets a seventh skill be a data change.

    + */ + static void renderSkills(PageFlowBuilder page, EntriesSection skills) { + if (skills == null || skills.entries().isEmpty()) { + return; + } + page.add(headingRow("Skills", skills.title(), SUMMARY_TO_SKILLS)); + List entries = skills.entries(); + int count = entries.size(); + double columnWidth = (CONTENT_WIDTH - (count - 1) * SKILL_DIVIDER_THICKNESS) / count; + double innerWidth = columnWidth - 2.0 * SKILL_COLUMN_INSET; + double descriptionInset = Math.max(0.0, (innerWidth - SKILL_DESC_MEASURE) / 2.0); + page.addRow("SkillGrid", row -> { + row.spacing(0); + row.margin(new DocumentInsets(HEADING_TO_SKILLS, 0, 0, 0)); + DocumentRowColumn[] columns = new DocumentRowColumn[2 * count - 1]; + for (int index = 0; index < count; index++) { + columns[2 * index] = DocumentRowColumn.weight(1.0); + if (index < count - 1) { + columns[2 * index + 1] = DocumentRowColumn.fixed(SKILL_DIVIDER_THICKNESS); + } + } + row.columns(columns); + for (int index = 0; index < count; index++) { + CvEntry entry = entries.get(index); + int position = index; + row.addSection("Skill_" + position, + column -> renderSkillColumn(column, entry, position, descriptionInset)); + if (index < count - 1) { + row.addSection("SkillDivider_" + position, VioletGridBody::renderDivider); + } + } + }); + } + + /** One column: the mark, the label and the description, on its own axis. */ + private static void renderSkillColumn(SectionBuilder column, CvEntry entry, int index, + double descriptionInset) { + column.spacing(0); + column.padding(0f, (float) SKILL_COLUMN_INSET, 0f, (float) SKILL_COLUMN_INSET); + if (!entry.icon().isBlank()) { + column.addSvgIcon(VioletGridIcons.icon(entry.icon()), + VioletGridIcons.size(entry.icon()), HorizontalAlign.CENTER); + } + column.addParagraph(p -> p + .name("SkillLabel_" + index) + .text(entry.title()) + .align(TextAlign.CENTER) + .lineSpacing(0) + .textStyle(style(DISPLAY_FONT, LABEL_SIZE, INK, true)) + .margin((float) ICON_TO_LABEL, 0f, (float) LABEL_TO_DESC, 0f)); + column.addParagraph(p -> p + .name("SkillDescription_" + index) + .text(entry.body()) + .align(TextAlign.CENTER) + .lineSpacing(leading(SKILL_DESC_PITCH, SKILL_DESC_SIZE)) + .textStyle(style(BODY_FONT, SKILL_DESC_SIZE, MUTED, false)) + // The description wraps to a narrower measure than the label + // above it, which is why the inset is on this paragraph and not + // on the column. + .padding(0f, (float) descriptionInset, 0f, (float) descriptionInset)); + } + + /** + * The dotted rule between two columns. + * + *

    A dashed line is the only primitive that produces a dotted rule. An + * accent border would take its height from the column beside it, which is + * what the height constant has to state by hand instead — the trade the + * design's dotted rule costs.

    + */ + private static void renderDivider(SectionBuilder cell) { + cell.spacing(0); + cell.addLine(line -> line + .name("SkillDividerRule") + .vertical(SKILL_RAIL_HEIGHT) + .thickness(SKILL_DIVIDER_THICKNESS) + .color(RULE) + .dashed(SKILL_DASH_ON, SKILL_DASH_OFF)); + } + + // -- the tools strip --------------------------------------------------- + + /** + * The tools — one paragraph of names and inline discs. + * + *

    The discs are inline shape runs rather than a bullet glyph, so they + * cannot fall victim to whatever a fallback font ships, and the line is + * running text rather than a grid — which is what the design's constant gap + * either side of every disc says it is.

    + */ + static void renderTools(PageFlowBuilder page, SkillsSection tools) { + if (tools == null || tools.groups().stream().allMatch(g -> g.entries().isEmpty())) { + return; + } + page.add(headingRow("Tools", tools.title(), SKILLS_TO_TOOLS)); + DocumentTextStyle name = style(BODY_FONT, TOOL_SIZE, BODY, false); + List items = new ArrayList<>(); + for (SkillGroup group : tools.groups()) { + for (CvSkill skill : group.entries()) { + items.add(skill.name()); + } + } + page.addParagraph(p -> { + p.name("ToolsLine"); + p.lineSpacing(0); + p.textStyle(name); + for (int index = 0; index < items.size(); index++) { + if (index > 0) { + p.inlineText(TOOL_GAP, name); + p.dot(TOOL_DOT_DIAMETER, ACCENT); + p.inlineText(TOOL_GAP, name); + } + p.inlineText(items.get(index), name); + } + p.margin((float) HEADING_TO_TOOLS, 0f, 0f, 0f); + }); + } + + // -- the timeline ------------------------------------------------------ + + /** + * The dated timeline. + * + *

    Three columns, which is what the design has always been: the dates out at the page + * margin, the discs on the rail, the copy inside it. Each is stated once for the whole + * timeline, so the dates cannot drift the markers and a longer date cannot move the + * copy. The rail is the timeline's own, drawn from where the markers landed.

    + */ + static void renderExperience(PageFlowBuilder page, EntriesSection experience) { + if (experience == null || experience.entries().isEmpty()) { + return; + } + page.add(headingRow("Experience", experience.title(), TOOLS_TO_EXPERIENCE)); + List entries = experience.entries(); + page.addSection("ExperienceEntries", host -> { + host.spacing(0); + host.margin((float) HEADING_TO_ENTRIES, 0f, 0f, 0f); + host.addTimeline(timeline -> { + // Each gap is its own: the dates' column plus the air after it puts the + // disc's centre on the rail, and the air after the disc puts the copy one + // entry indent inside it. With a single gap for both, the dates' column + // would be whatever was left over — 55.86pt, which breaks the longest date + // across two lines. + timeline.markerOnRail() + .rail(rail -> rail + .stroke(DocumentStroke.of(RULE, RAIL_THICKNESS)) + .from(TimelineRailEnd.MARKER).to(TimelineRailEnd.MARKER)) + .leadingColumn(DocumentRowColumn.fixed(DATE_COLUMN)) + .leadingGap(DATE_TO_MARKER) + .axisWidth(MARKER_DIAMETER) + .markerGap(MARKER_TO_COPY) + .gutter(0) + .spacing(ENTRY_GAP) + .keepEntriesTogether(); + for (int i = 0; i < entries.size(); i++) { + CvEntry entry = entries.get(i); + int index = i; + timeline.entry(entryMarker(index), e -> e + .leading(column -> period(column, entry, index)) + .content(column -> renderEntry(column, entry, index))); + } + }); + }); + } + + /** + * The disc, in a box the height of the title line it belongs to. + * + *

    Declaring the line rather than the disc is what keeps the mark where it was: a + * timeline top-aligns a marker in its column, so a box the size of the disc would ride + * the line's top edge instead of its middle. A box the size of the line, with the disc + * centred in it, puts the disc where the layer stack used to hold it — and puts the + * rail's anchor, that box's own centre, through the disc rather than above it.

    + */ + private static TimelineMarker entryMarker(int index) { + return TimelineMarker.custom(MARKER_DIAMETER, ROLE_SIZE * LINE_FACTOR, column -> { + column.spacing(0); + column.padding((float) centred(MARKER_DIAMETER), 0f, 0f, 0f); + column.add(markerDot("Marker_" + index, MARKER_DIAMETER, + MARKER_DIAMETER / LINE_FACTOR, ACCENT)); + }); + } + + /** The dates, in their own column, dropped onto the title line's setting. */ + private static void period(SectionBuilder column, CvEntry entry, int index) { + column.spacing(0); + column.padding((float) centred(PERIOD_SIZE * LINE_FACTOR), 0f, 0f, 0f); + column.add(text("Period_" + index, entry.date(), + style(BODY_FONT, PERIOD_SIZE, ACCENT, false))); + } + + /** + * One entry: the title line, then the bullets. + * + *

    The title is a two-column table — a row here would be a row inside a row's + * cell.

    + */ + private static void renderEntry(SectionBuilder body, CvEntry entry, int index) { + body.spacing(0); + body.add(titleLine(entry, index)); + List highlights = lines(entry.body()); + if (highlights.isEmpty()) { + return; + } + body.addTable(table -> { + table.name("Highlights_" + index); + table.margin(new DocumentInsets(TITLE_TO_BULLETS, 0, 0, 0)); + table.width(ENTRY_WIDTH); + table.columns( + DocumentTableColumn.fixed(BULLET_COLUMN), + DocumentTableColumn.fixed(ENTRY_WIDTH - BULLET_COLUMN)); + table.defaultCellStyle(cellStyle( + new DocumentInsets(0, 0, + gap(BULLET_PITCH, HIGHLIGHT_SIZE * LINE_FACTOR), 0), + DocumentTableTextAnchor.TOP_LEFT)); + for (int i = 0; i < highlights.size(); i++) { + table.rowCells( + DocumentTableCell.node(markerDot("HighlightDot_" + index + "_" + i, + BULLET_DOT_DIAMETER, HIGHLIGHT_SIZE, ACCENT)), + DocumentTableCell.node(text("Highlight_" + index + "_" + i, + highlights.get(i), + style(BODY_FONT, HIGHLIGHT_SIZE, BODY, false)))); + } + // The pitch is a gap BETWEEN bullets; left on the last one it + // becomes a gap the entry below already owns. + table.rowStyle(highlights.size() - 1, + cellStyle(DocumentInsets.zero(), DocumentTableTextAnchor.TOP_LEFT)); + }); + } + + /** The role, the employer and the location, as one line of three runs. */ + private static DocumentNode titleLine(CvEntry entry, int index) { + DocumentTextStyle role = style(BODY_FONT, ROLE_SIZE, INK, true); + DocumentTextStyle employer = style(BODY_FONT, EMPLOYER_SIZE, MUTED, false); + ParagraphBuilder title = new ParagraphBuilder() + .name("Role_" + index) + .lineSpacing(0) + .textStyle(role) + .inlineText(entry.title(), role); + if (!entry.subtitle().isBlank()) { + title.inlineText(PIPE, employer).inlineText(entry.subtitle(), employer); + } + if (!entry.link().isBlank()) { + title.link(new DocumentLinkOptions(entry.link())); + } + return new TableBuilder() + .name("TitleTable_" + index) + .width(ENTRY_WIDTH) + .columns( + DocumentTableColumn.fixed(ROLE_COLUMN), + DocumentTableColumn.fixed(LOCATION_COLUMN)) + .defaultCellStyle(cellStyle(DocumentInsets.zero(), + DocumentTableTextAnchor.TOP_LEFT)) + .rowCells( + DocumentTableCell.node(title.build()), + DocumentTableCell.node(new ParagraphBuilder() + .name("Location_" + index) + .text(entry.place()) + .align(TextAlign.RIGHT) + .lineSpacing(0) + .textStyle(style(BODY_FONT, LOCATION_SIZE, MUTED, false)) + .build())) + .build(); + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/VioletGridClosing.java b/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/VioletGridClosing.java new file mode 100644 index 000000000..56dfceb63 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/VioletGridClosing.java @@ -0,0 +1,466 @@ +package com.demcha.compose.document.templates.cv.presets; + +import com.demcha.compose.document.dsl.PageFlowBuilder; +import com.demcha.compose.document.dsl.ParagraphBuilder; +import com.demcha.compose.document.dsl.SectionBuilder; +import com.demcha.compose.document.dsl.ShapeContainerBuilder; +import com.demcha.compose.document.dsl.TableBuilder; +import com.demcha.compose.document.node.DocumentLinkOptions; +import com.demcha.compose.document.node.DocumentNode; +import com.demcha.compose.document.node.LayerAlign; +import com.demcha.compose.document.node.TextAlign; +import com.demcha.compose.document.style.DocumentInsets; +import com.demcha.compose.document.style.DocumentRowColumn; +import com.demcha.compose.document.style.DocumentTextStyle; +import com.demcha.compose.document.table.DocumentTableCell; +import com.demcha.compose.document.table.DocumentTableColumn; +import com.demcha.compose.document.table.DocumentTableTextAnchor; +import com.demcha.compose.document.templates.cv.data.CvEntry; +import com.demcha.compose.document.templates.cv.data.CvSkill; +import com.demcha.compose.document.templates.cv.data.EntriesSection; +import com.demcha.compose.document.templates.cv.data.ParagraphSection; +import com.demcha.compose.document.templates.cv.data.SkillGroup; +import com.demcha.compose.document.templates.cv.data.SkillsSection; + +import java.util.ArrayList; +import java.util.List; + +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.ACCENT; +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.ACCENT_RULE; +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.BADGE_COLUMN; +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.BADGE_DIAMETER; +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.BODY; +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.BODY_FONT; +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.CREDENTIALS_TO_QUOTE; +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.CREDENTIAL_GUTTER; +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.DEGREE_SIZE; +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.DEGREE_TO_INSTITUTION; +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.DISPLAY_FONT; +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.EDUCATION_COLUMN; +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.EDUCATION_RULE_WEIGHT; +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.EDUCATION_TEXT_TOP; +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.EDU_DETAIL_SIZE; +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.EXPERIENCE_TO_PROJECTS; +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.HEADING_RULE_OFFSET; +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.HEADING_RULE_THICKNESS; +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.HEADING_SIZE; +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.HEADING_TO_EDUCATION; +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.HEADING_TO_PROJECTS; +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.HEADING_TO_RULE; +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.HEADING_TRACKING; +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.INK; +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.INSTITUTION_SIZE; +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.INSTITUTION_TO_DETAIL; +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.LANGUAGES_BODY_OFFSET; +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.LANGUAGES_COLUMN; +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.LANGUAGES_RULE_WEIGHT; +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.LANGUAGE_LEVEL_COLUMN; +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.LANGUAGE_NAME_COLUMN; +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.LANGUAGE_PITCH; +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.LANGUAGE_RATING_COLUMN; +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.LANGUAGE_SIZE; +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.LEVEL_SIZE; +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.LINE_FACTOR; +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.MUTED; +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.PIPE; +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.PROJECTS_TO_CREDENTIALS; +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.PROJECT_BODY_PITCH; +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.PROJECT_BODY_SIZE; +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.PROJECT_COPY_INDENT; +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.PROJECT_COPY_WIDTH; +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.PROJECT_GAP; +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.PROJECT_RULE_THICKNESS; +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.PROJECT_SUB_SIZE; +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.PROJECT_TITLE_COLUMN; +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.PROJECT_TITLE_SIZE; +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.PROJECT_YEAR_COLUMN; +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.PROJECT_YEAR_SIZE; +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.QUOTE_GAP; +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.QUOTE_LINE_BOX; +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.QUOTE_PAD_H; +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.QUOTE_PAD_V; +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.QUOTE_RADIUS; +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.QUOTE_SIZE; +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.RATING_DOT_DIAMETER; +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.RATING_EMPTY; +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.RATING_GAP; +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.RATING_SCALE; +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.RULE; +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.TABLE_SLACK; +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.THUMB_COLUMN; +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.THUMB_HEIGHT; +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.THUMB_PAD_LEFT; +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.THUMB_RADIUS; +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.THUMB_WIDTH; +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.TINT; +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.TITLE_TO_BODY; +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.cellStyle; +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.gap; +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.leading; +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.style; +import static com.demcha.compose.document.templates.cv.presets.VioletGridWidgets.headingRow; +import static com.demcha.compose.document.templates.cv.presets.VioletGridWidgets.inlineIcon; +import static com.demcha.compose.document.templates.cv.presets.VioletGridWidgets.text; +import static com.demcha.compose.document.templates.cv.presets.VioletGridWidgets.tracked; + +/** + * What closes the sheet: the projects, the credentials band and the quote. + */ +final class VioletGridClosing { + + private VioletGridClosing() { + } + + // -- the projects ------------------------------------------------------ + + /** One top-level row per project: the tile, then the copy beside it. */ + static void renderProjects(PageFlowBuilder page, EntriesSection projects) { + if (projects == null || projects.entries().isEmpty()) { + return; + } + page.add(headingRow("Projects", projects.title(), EXPERIENCE_TO_PROJECTS)); + List entries = projects.entries(); + for (int i = 0; i < entries.size(); i++) { + CvEntry entry = entries.get(i); + int index = i; + double top = i == 0 ? HEADING_TO_PROJECTS : PROJECT_GAP; + page.addRow("Project_" + index, row -> { + row.spacing(0); + row.margin(new DocumentInsets(top, 0, 0, 0)); + row.columns( + DocumentRowColumn.fixed(THUMB_COLUMN), + DocumentRowColumn.weight(1.0)); + row.addSection("ProjectThumb_" + index, + cell -> renderThumb(cell, entry, index)); + row.addSection("ProjectCopy_" + index, + cell -> renderProjectCopy(cell, entry, index)); + }); + } + } + + /** + * The tinted tile and the glyph it owns — a shape container rather than a + * fill with a mark beside it: the tile IS the thumbnail, and centring the + * glyph in the container is what keeps the two together when either size + * changes. + */ + private static void renderThumb(SectionBuilder cell, CvEntry entry, int index) { + cell.spacing(0); + cell.padding(0f, 0f, 0f, (float) THUMB_PAD_LEFT); + if (entry.icon().isBlank()) { + return; + } + SectionBuilder glyph = new SectionBuilder(); + glyph.name("ProjectGlyph_" + index); + glyph.addSvgIcon(VioletGridIcons.icon(entry.icon()), VioletGridIcons.size(entry.icon())); + cell.addContainer(tile -> tile + .name("ProjectTile_" + index) + .roundedRect(THUMB_WIDTH, THUMB_HEIGHT, THUMB_RADIUS) + .fillColor(TINT) + .center(glyph.build())); + } + + /** + * The copy beside a thumbnail. The hairline is this section's own left + * border, so it runs the copy's height — which is what the design shows, + * and what a line with a measured height would get wrong the first time + * the copy ran to three lines. + */ + private static void renderProjectCopy(SectionBuilder cell, CvEntry entry, int index) { + DocumentTextStyle title = style(BODY_FONT, PROJECT_TITLE_SIZE, INK, true); + DocumentTextStyle subtitle = style(BODY_FONT, PROJECT_SUB_SIZE, MUTED, false); + cell.spacing(0); + cell.accentLeft(RULE, PROJECT_RULE_THICKNESS); + cell.padding(0f, 0f, 0f, (float) PROJECT_COPY_INDENT); + ParagraphBuilder name = new ParagraphBuilder() + .name("ProjectName_" + index) + .lineSpacing(0) + .textStyle(title) + .inlineText(entry.title(), title); + if (!entry.subtitle().isBlank()) { + name.inlineText(PIPE, subtitle).inlineText(entry.subtitle(), subtitle); + } + if (!entry.link().isBlank()) { + name.link(new DocumentLinkOptions(entry.link())); + } + cell.addTable(table -> table + .name("ProjectTitle_" + index) + .width(PROJECT_COPY_WIDTH) + .columns( + DocumentTableColumn.fixed(PROJECT_TITLE_COLUMN), + DocumentTableColumn.fixed(PROJECT_YEAR_COLUMN)) + .defaultCellStyle(cellStyle(DocumentInsets.zero(), + DocumentTableTextAnchor.TOP_LEFT)) + .rowCells( + DocumentTableCell.node(name.build()), + DocumentTableCell.node(new ParagraphBuilder() + .name("ProjectYear_" + index) + .text(entry.date()) + .align(TextAlign.RIGHT) + .lineSpacing(0) + .textStyle(style(BODY_FONT, PROJECT_YEAR_SIZE, MUTED, false)) + .build()))); + cell.addParagraph(p -> p + .name("ProjectBody_" + index) + .text(entry.body().replace(String.valueOf((char) 10), " ")) + .lineSpacing(leading(PROJECT_BODY_PITCH, PROJECT_BODY_SIZE)) + .textStyle(style(BODY_FONT, PROJECT_BODY_SIZE, BODY, false)) + .margin((float) TITLE_TO_BODY, 0f, 0f, 0f)); + } + + // -- the credentials band ---------------------------------------------- + + /** + * The credentials band: education and languages side by side. + * + *

    TWO top-level rows rather than one, and that is not a stylistic + * choice. The obvious shape — one row whose two cells each hold a heading + * and a body — cannot be built: a heading is a text and a rule side by + * side, so a cell holding one would need a nested row, which the layout + * compiler refuses. So the band is flattened: one row carries both headings + * and both rules as direct children, and a second carries the two bodies. + * Nothing is nested, and the two rows share the same column arithmetic, so + * the halves cannot drift apart.

    + */ + static void renderCredentials(PageFlowBuilder page, EntriesSection education, + SkillsSection languages) { + boolean hasEducation = education != null && !education.entries().isEmpty(); + boolean hasLanguages = languages != null + && languages.groups().stream().anyMatch(group -> !group.entries().isEmpty()); + if (!hasEducation && !hasLanguages) { + return; + } + renderCredentialHeadings(page, + hasEducation ? education.title() : "", + hasLanguages ? languages.title() : ""); + page.addRow("CredentialBodies", row -> { + row.spacing(0); + row.margin(new DocumentInsets(HEADING_TO_EDUCATION, 0, 0, 0)); + row.columns( + DocumentRowColumn.fixed(EDUCATION_COLUMN), + DocumentRowColumn.fixed(CREDENTIAL_GUTTER), + DocumentRowColumn.weight(1.0)); + row.addSection("Education", cell -> renderEducation(cell, education)); + row.addSection("CredentialGutter", cell -> cell.spacer(CREDENTIAL_GUTTER, 1.0)); + row.addSection("Languages", cell -> renderLanguages(cell, languages)); + }); + } + + /** + * Both headings and both rules, as children of one row. The two text + * columns take what their words need and the two weighted ones split what + * is left in the ratio the design's rules measure, so each rule still + * begins where its own heading ends. + */ + private static void renderCredentialHeadings(PageFlowBuilder page, String educationTitle, + String languagesTitle) { + page.addRow("CredentialHeadings", row -> { + row.spacing(0); + row.margin(new DocumentInsets(PROJECTS_TO_CREDENTIALS, 0, 0, 0)); + row.columns( + DocumentRowColumn.auto(), + DocumentRowColumn.weight(EDUCATION_RULE_WEIGHT), + DocumentRowColumn.fixed(CREDENTIAL_GUTTER), + DocumentRowColumn.auto(), + DocumentRowColumn.weight(LANGUAGES_RULE_WEIGHT)); + row.add(tracked("EducationHeadingLabel", educationTitle, DISPLAY_FONT, + HEADING_SIZE, ACCENT, true, HEADING_TRACKING).build()); + row.addLine(line -> line + .name("EducationHeadingRule") + .fill() + .thickness(HEADING_RULE_THICKNESS) + .color(ACCENT_RULE) + .margin(new DocumentInsets(HEADING_RULE_OFFSET, 0, 0, HEADING_TO_RULE))); + row.addSpacer(CREDENTIAL_GUTTER); + row.add(tracked("LanguagesHeadingLabel", languagesTitle, DISPLAY_FONT, + HEADING_SIZE, ACCENT, true, HEADING_TRACKING).build()); + row.addLine(line -> line + .name("LanguagesHeadingRule") + .fill() + .thickness(HEADING_RULE_THICKNESS) + .color(ACCENT_RULE) + .margin(new DocumentInsets(HEADING_RULE_OFFSET, 0, 0, HEADING_TO_RULE))); + }); + } + + /** + * The badge beside the degree, the institution and the detail line. + * + *

    A layer stack rather than a table: the badge is a shape container and + * the three lines are a stack, and neither survives a table cell here. The + * lines carry a left margin the width of the badge column, so the two + * layers do not overlap and the badge needs no offset.

    + */ + private static void renderEducation(SectionBuilder cell, EntriesSection education) { + cell.spacing(0); + if (education == null || education.entries().isEmpty()) { + return; + } + CvEntry entry = education.entries().get(0); + cell.addLayerStack(stack -> { + stack.name("EducationBody"); + stack.layer(educationLines(entry), LayerAlign.TOP_LEFT, 0); + if (!entry.icon().isBlank()) { + stack.position(badge(entry), 0.0, 0.0, LayerAlign.TOP_LEFT, 1); + } + }); + } + + /** The tinted circle and the glyph it owns. */ + private static DocumentNode badge(CvEntry entry) { + SectionBuilder glyph = new SectionBuilder(); + glyph.name("EducationGlyph"); + glyph.addSvgIcon(VioletGridIcons.icon(entry.icon()), VioletGridIcons.size(entry.icon())); + return new ShapeContainerBuilder() + .name("EducationBadge") + .circle(BADGE_DIAMETER) + .fillColor(TINT) + .center(glyph.build()) + .build(); + } + + /** The degree, the institution and the place-and-years line, as one column. */ + private static DocumentNode educationLines(CvEntry entry) { + DocumentTextStyle place = style(BODY_FONT, EDU_DETAIL_SIZE, MUTED, false); + DocumentTextStyle years = style(BODY_FONT, EDU_DETAIL_SIZE, ACCENT, false); + double width = EDUCATION_COLUMN - BADGE_COLUMN - TABLE_SLACK; + ParagraphBuilder degree = new ParagraphBuilder() + .name("Degree") + .text(entry.title()) + .lineSpacing(0) + .textStyle(style(BODY_FONT, DEGREE_SIZE, INK, true)); + if (!entry.link().isBlank()) { + degree.link(new DocumentLinkOptions(entry.link())); + } + ParagraphBuilder detail = new ParagraphBuilder() + .name("EducationDetail") + .lineSpacing(0) + .textStyle(place) + .inlineText(entry.place(), place); + if (!entry.date().isBlank()) { + detail.inlineText(PIPE, place).inlineText(entry.date(), years); + } + return new TableBuilder() + .name("EducationLines") + .margin(new DocumentInsets(EDUCATION_TEXT_TOP, 0, 0, BADGE_COLUMN)) + .width(width) + .columns(DocumentTableColumn.fixed(width)) + .defaultCellStyle(cellStyle(DocumentInsets.zero(), + DocumentTableTextAnchor.TOP_LEFT)) + .rowCells(DocumentTableCell.node(degree.build()) + .withStyle(cellStyle( + new DocumentInsets(0, 0, DEGREE_TO_INSTITUTION, 0), + DocumentTableTextAnchor.TOP_LEFT))) + .rowCells(DocumentTableCell.node(text("Institution", entry.subtitle(), + style(BODY_FONT, INSTITUTION_SIZE, BODY, false))) + .withStyle(cellStyle( + new DocumentInsets(0, 0, INSTITUTION_TO_DETAIL, 0), + DocumentTableTextAnchor.TOP_LEFT))) + .rowCells(DocumentTableCell.node(detail.build())) + .build(); + } + + /** + * The languages — a name, the level as the document words it, and a + * five-disc rating per row. + * + *

    The rating paragraph is styled although it carries no words: a + * paragraph takes its line box from its type whether or not a glyph uses + * it, and the default box would set the whole row's pitch.

    + */ + private static void renderLanguages(SectionBuilder block, SkillsSection languages) { + block.spacing(0); + if (languages == null) { + return; + } + List entries = new ArrayList<>(); + for (SkillGroup group : languages.groups()) { + entries.addAll(group.entries()); + } + if (entries.isEmpty()) { + return; + } + block.addTable(table -> { + table.name("LanguageTable"); + table.margin(new DocumentInsets(LANGUAGES_BODY_OFFSET, 0, 0, 0)); + table.width(LANGUAGES_COLUMN - TABLE_SLACK); + table.columns( + DocumentTableColumn.fixed(LANGUAGE_NAME_COLUMN), + DocumentTableColumn.fixed(LANGUAGE_LEVEL_COLUMN), + DocumentTableColumn.fixed(LANGUAGE_RATING_COLUMN - TABLE_SLACK)); + table.defaultCellStyle(cellStyle( + new DocumentInsets(0, 0, + gap(LANGUAGE_PITCH, LANGUAGE_SIZE * LINE_FACTOR), 0), + DocumentTableTextAnchor.CENTER_LEFT)); + for (int index = 0; index < entries.size(); index++) { + CvSkill skill = entries.get(index); + table.rowCells( + DocumentTableCell.node(text("LanguageName_" + index, skill.name(), + style(BODY_FONT, LANGUAGE_SIZE, BODY, false))), + DocumentTableCell.node(text("LanguageLevel_" + index, skill.note(), + style(BODY_FONT, LEVEL_SIZE, MUTED, false))), + DocumentTableCell.node(rating("LanguageRating_" + index, skill))); + } + table.rowStyle(entries.size() - 1, + cellStyle(DocumentInsets.zero(), DocumentTableTextAnchor.CENTER_LEFT)); + }); + } + + /** One language's discs, filled up to its rating and grey after it. */ + private static DocumentNode rating(String name, CvSkill skill) { + DocumentTextStyle box = style(BODY_FONT, LANGUAGE_SIZE, BODY, false); + ParagraphBuilder paragraph = new ParagraphBuilder(); + paragraph.name(name); + paragraph.lineSpacing(0); + paragraph.textStyle(box); + if (skill.level().isEmpty()) { + return paragraph.build(); + } + int filled = (int) Math.round(skill.level().getAsDouble() * RATING_SCALE); + for (int index = 0; index < RATING_SCALE; index++) { + paragraph.dot(RATING_DOT_DIAMETER, index < filled ? ACCENT : RATING_EMPTY); + if (index < RATING_SCALE - 1) { + paragraph.inlineText(RATING_GAP, box); + } + } + return paragraph.build(); + } + + // -- the closing band -------------------------------------------------- + + /** + * The closing band. + * + *

    A fill with a corner radius and its own padding rather than a soft + * panel, whose padding is a single uniform value: this band's side padding + * is several times its vertical padding.

    + * + *

    The quotation mark is a packaged glyph rather than a typographic one. + * At the size the design draws it a real quotation mark would need about + * forty points of type, and its line box alone would be nearly twice the + * band's height.

    + */ + static void renderQuote(PageFlowBuilder page, ParagraphSection quote) { + if (quote == null || quote.body().isBlank()) { + return; + } + DocumentTextStyle line = style(BODY_FONT, QUOTE_SIZE, BODY, false); + DocumentTextStyle box = style(BODY_FONT, QUOTE_LINE_BOX / LINE_FACTOR, BODY, false); + page.addSection("QuoteBand", band -> { + band.spacing(0); + band.margin((float) CREDENTIALS_TO_QUOTE, 0f, 0f, 0f); + band.fillColor(TINT); + band.cornerRadius(QUOTE_RADIUS); + band.padding((float) QUOTE_PAD_V, (float) QUOTE_PAD_H, + (float) QUOTE_PAD_V, (float) QUOTE_PAD_H); + band.addParagraph(p -> { + p.name("QuoteLine"); + p.lineSpacing(0); + p.textStyle(box); + inlineIcon(p, VioletGridIcons.QUOTE); + p.inlineText(QUOTE_GAP, line); + p.inlineText(quote.body().replace(String.valueOf((char) 10), " "), line); + }); + }); + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/VioletGridIcons.java b/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/VioletGridIcons.java new file mode 100644 index 000000000..823b328c9 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/VioletGridIcons.java @@ -0,0 +1,112 @@ +package com.demcha.compose.document.templates.cv.presets; + +import com.demcha.compose.document.svg.SvgIcon; + +import java.io.IOException; +import java.io.InputStream; +import java.io.UncheckedIOException; +import java.nio.charset.StandardCharsets; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Classpath loader for the Violet Grid CV marks: the five contact glyphs, the + * six skill marks, the two project glyphs, the graduation cap and the + * quotation mark that opens the closing band. + * + *

    They ship inside the templates artifact under + * {@code templates/cv/violet-grid/icons/} as SVG, so they scale with the box + * they are drawn into rather than being resampled. Parsed icons are cached + * per token, so a sheet that repeats a mark reads each file once per JVM.

    + * + *

    {@link #ENTRY_TOKENS} is the vocabulary a document names through + * {@code CvEntry.icon()}. It is scoped to this preset — another preset + * packages its own set — and each mark carries the width it is drawn at, + * because an icon's box is not its glyph and the sizes are per-kind.

    + */ +final class VioletGridIcons { + + static final String EMAIL = "email"; + static final String PHONE = "phone"; + static final String LOCATION = "location"; + static final String WEBSITE = "website"; + static final String LINKEDIN = "linkedin"; + static final String GRADUATION = "graduation"; + static final String QUOTE = "quote"; + + private static final String ICON_ROOT = "/templates/cv/violet-grid/icons/"; + + /** The width each mark is drawn at, as the asset resolver chose it. */ + private static final Map SIZES = Map.ofEntries( + Map.entry(EMAIL, 11.5), + Map.entry(PHONE, 11.5), + Map.entry(LOCATION, 11.5), + Map.entry(WEBSITE, 11.5), + Map.entry(LINKEDIN, 11.5), + Map.entry("ux-research", 20.5), + Map.entry("information-architecture", 20.5), + Map.entry("wireframing", 20.5), + Map.entry("prototyping", 20.5), + Map.entry("usability-testing", 20.5), + Map.entry("design-systems", 20.5), + Map.entry("project-wallet", 28.0), + Map.entry("project-health", 28.0), + Map.entry(GRADUATION, 26.0), + Map.entry(QUOTE, 14.1)); + + /** The marks a document may name on a skill, a project or a degree. */ + static final Set ENTRY_TOKENS = Set.of( + "ux-research", "information-architecture", "wireframing", "prototyping", + "usability-testing", "design-systems", "project-wallet", "project-health", + GRADUATION); + + private static final Map CACHE = new ConcurrentHashMap<>(); + + private VioletGridIcons() { + } + + /** + * The width one mark is drawn at. + * + * @param token one of the packaged tokens + * @return the drawn width in points + * @throws IllegalArgumentException when the token names no packaged mark, + * listing the ones a document may choose — an icon token is data, + * so a wrong one is a data error + */ + static double size(String token) { + Double size = SIZES.get(token); + if (size == null) { + throw new IllegalArgumentException( + "Unknown violet grid CV icon token '" + token + + "'. This preset packages " + ENTRY_TOKENS.stream().sorted().toList() + + " for an entry mark."); + } + return size; + } + + /** + * Reads one mark. + * + * @param token one of the packaged tokens + * @return the parsed icon + */ + static SvgIcon icon(String token) { + size(token); + return CACHE.computeIfAbsent(token, VioletGridIcons::read); + } + + private static SvgIcon read(String token) { + String resourcePath = ICON_ROOT + token + ".svg"; + try (InputStream input = VioletGridIcons.class.getResourceAsStream(resourcePath)) { + if (input == null) { + throw new IllegalStateException("Missing violet grid CV icon: " + resourcePath); + } + return SvgIcon.parse(new String(input.readAllBytes(), StandardCharsets.UTF_8)); + } catch (IOException e) { + throw new UncheckedIOException( + "Failed to read violet grid CV icon: " + resourcePath, e); + } + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/VioletGridMasthead.java b/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/VioletGridMasthead.java new file mode 100644 index 000000000..e7f0dcbf0 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/VioletGridMasthead.java @@ -0,0 +1,202 @@ +package com.demcha.compose.document.templates.cv.presets; + +import com.demcha.compose.document.dsl.PageFlowBuilder; +import com.demcha.compose.document.dsl.SectionBuilder; +import com.demcha.compose.document.node.DocumentLinkOptions; +import com.demcha.compose.document.style.DocumentRowColumn; +import com.demcha.compose.document.style.DocumentTextStyle; +import com.demcha.compose.document.templates.core.identity.Contact; +import com.demcha.compose.document.templates.core.identity.ContactUri; +import com.demcha.compose.document.templates.core.identity.Link; +import com.demcha.compose.document.templates.cv.data.CvIdentity; +import com.demcha.compose.document.templates.cv.data.ParagraphSection; + +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; + +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.ACCENT; +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.ACCENT_LIGHT; +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.BODY; +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.BODY_FONT; +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.CONTACT_COLUMN_X; +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.CONTACT_ICON_GAP; +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.CONTACT_PITCH; +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.CONTACT_SIZE; +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.CONTACT_TOP; +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.DISCIPLINE_SIZE; +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.DISCIPLINE_TO_RULE; +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.DISCIPLINE_TRACKING; +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.DISPLAY_FONT; +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.HEADER_TO_SUMMARY; +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.IDENTITY_RULE_THICKNESS; +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.IDENTITY_RULE_WIDTH; +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.INK; +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.LINE_FACTOR; +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.NAME_SIZE; +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.SUMMARY_PITCH; +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.SUMMARY_SIZE; +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.gap; +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.style; +import static com.demcha.compose.document.templates.cv.presets.VioletGridWidgets.inlineIcon; +import static com.demcha.compose.document.templates.cv.presets.VioletGridWidgets.lines; +import static com.demcha.compose.document.templates.cv.presets.VioletGridWidgets.tracked; + +/** + * The top of the sheet: the two-tone name beside the contact column, and the + * opening lines under them. + */ +final class VioletGridMasthead { + + private VioletGridMasthead() { + } + + /** One line of the contact column: a mark, a value and where it points. */ + private record Channel(String token, String value, String href) { + } + + /** + * The masthead, as a top-level row split where the contact marks begin — + * the column is left-aligned on its marks rather than right-aligned to the + * margin, so that x is the split. + */ + static void render(PageFlowBuilder page, CvIdentity identity, ParagraphSection summary) { + page.addRow("Header", row -> { + row.spacing(0); + row.columns( + DocumentRowColumn.fixed(CONTACT_COLUMN_X), + DocumentRowColumn.weight(1.0)); + row.addSection("Identity", block -> renderIdentity(block, identity)); + row.addSection("Contact", block -> renderContact(block, identity)); + }); + renderSummary(page, summary); + } + + // -- the name ---------------------------------------------------------- + + /** + * The name, the discipline line and the accent rule under it. + * + *

    Both names are inline runs of ONE paragraph, so the gap between them + * is a real word space that follows the type size rather than a measured + * offset that would not.

    + */ + private static void renderIdentity(SectionBuilder block, CvIdentity identity) { + block.spacing(0); + DocumentTextStyle given = style(DISPLAY_FONT, NAME_SIZE, INK, false); + DocumentTextStyle family = style(DISPLAY_FONT, NAME_SIZE, ACCENT_LIGHT, false); + block.addParagraph(p -> p + .name("Name") + .lineSpacing(0) + .textStyle(given) + .inlineText(identity.name().first().toUpperCase(Locale.ROOT), given) + .inlineText(" ", given) + .inlineText(identity.name().last().toUpperCase(Locale.ROOT), family)); + block.add(tracked("Discipline", identity.jobTitle().toUpperCase(Locale.ROOT), + DISPLAY_FONT, DISCIPLINE_SIZE, ACCENT, true, DISCIPLINE_TRACKING) + .margin(0f, 0f, (float) DISCIPLINE_TO_RULE, 0f) + .build()); + block.addLine(line -> line + .name("IdentityRule") + .horizontal(IDENTITY_RULE_WIDTH) + .thickness(IDENTITY_RULE_THICKNESS) + .color(ACCENT)); + } + + // -- the contact column ------------------------------------------------ + + /** + * The contact lines. + * + *

    Each is one paragraph rather than a two-column table: the mark is an + * inline run measured into the line box, so it shares a baseline with its + * value by construction, and nothing in this column has to line up with + * anything outside it. Where the value points somewhere the run becomes an + * inline link, so the target reaches the PDF as a real annotation.

    + * + *

    A link shows its own label and carries the address behind it, so the + * column's width does not depend on how long an address happens to be.

    + */ + private static void renderContact(SectionBuilder block, CvIdentity identity) { + block.spacing(0); + block.padding((float) CONTACT_TOP, 0f, 0f, 0f); + DocumentTextStyle value = style(BODY_FONT, CONTACT_SIZE, BODY, false); + // The line box comes from the paragraph's own style, and an inline run + // taller than it is drawn clipped: take whichever of the mark and the + // text needs more room. + double boxSize = Math.max(CONTACT_SIZE, + VioletGridIcons.size(VioletGridIcons.EMAIL) / LINE_FACTOR); + DocumentTextStyle box = style(BODY_FONT, boxSize, BODY, false); + List channels = channels(identity); + for (int i = 0; i < channels.size(); i++) { + Channel channel = channels.get(i); + boolean last = i == channels.size() - 1; + int index = i; + block.addParagraph(p -> { + p.name("Contact_" + index + "_" + channel.token()); + p.lineSpacing(0); + p.textStyle(box); + inlineIcon(p, channel.token()); + p.inlineText(CONTACT_ICON_GAP, value); + if (channel.href() == null || channel.href().isBlank()) { + p.inlineText(channel.value(), value); + } else { + p.inlineText(channel.value(), value, new DocumentLinkOptions(channel.href())); + } + p.margin(0f, 0f, + last ? 0f : (float) gap(CONTACT_PITCH, boxSize * LINE_FACTOR), 0f); + }); + } + } + + /** The column's order: how to write, how to call, where, then the links. */ + private static List channels(CvIdentity identity) { + Contact contact = identity.contact(); + List channels = new ArrayList<>(); + channels.add(new Channel(VioletGridIcons.EMAIL, contact.email(), + "mailto:" + contact.email())); + channels.add(new Channel(VioletGridIcons.PHONE, contact.phone(), + ContactUri.tel(contact.phone()))); + channels.add(new Channel(VioletGridIcons.LOCATION, contact.address(), null)); + for (Link link : identity.links()) { + channels.add(new Channel(markFor(link), link.label(), link.url())); + } + return channels; + } + + /** A link takes the mark of the network it points at, or the globe. */ + private static String markFor(Link link) { + String target = (link.url() + " " + link.label()).toLowerCase(Locale.ROOT); + return target.contains("linkedin") ? VioletGridIcons.LINKEDIN : VioletGridIcons.WEBSITE; + } + + // -- the opening lines ------------------------------------------------- + + /** + * The opening block — one paragraph per line the document wrote. + * + *

    Not a wrapped paragraph, and that is a reading of the design rather + * than a convenience: its first line ends with room for the next word + * several times over, so no greedy wrap could produce it. The lines are + * sentences, set as lines, and the document says so.

    + */ + private static void renderSummary(PageFlowBuilder page, ParagraphSection summary) { + if (summary == null || summary.body().isBlank()) { + return; + } + DocumentTextStyle line = style(BODY_FONT, SUMMARY_SIZE, BODY, false); + List body = lines(summary.body()); + for (int i = 0; i < body.size(); i++) { + boolean last = i == body.size() - 1; + int index = i; + page.addParagraph(p -> p + .name("SummaryLine_" + index) + .text(body.get(index)) + .lineSpacing(0) + .textStyle(line) + .margin(index == 0 ? (float) HEADER_TO_SUMMARY : 0f, 0f, + last ? 0f : (float) gap(SUMMARY_PITCH, SUMMARY_SIZE * LINE_FACTOR), + 0f)); + } + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/VioletGridStyles.java b/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/VioletGridStyles.java new file mode 100644 index 000000000..e417c7997 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/VioletGridStyles.java @@ -0,0 +1,433 @@ +package com.demcha.compose.document.templates.cv.presets; + +import com.demcha.compose.document.api.DocumentPageSize; +import com.demcha.compose.document.style.DocumentColor; +import com.demcha.compose.document.style.DocumentInsets; +import com.demcha.compose.document.style.DocumentStroke; +import com.demcha.compose.document.style.DocumentTextDecoration; +import com.demcha.compose.document.style.DocumentTextStyle; +import com.demcha.compose.document.table.DocumentTableStyle; +import com.demcha.compose.document.table.DocumentTableTextAnchor; +import com.demcha.compose.font.FontName; + +/** + * The measured geometry, palette and type scale of the Violet Grid CV. + * + *

    Every length is a measurement, converted once

    + * + *

    The design was drawn on a 1055-pixel-wide raster, so each length here is + * written as {@link #px} of a pixel measured off it. That keeps the source + * auditable — a reader can put a ruler on the drawing and find the number — + * and keeps one conversion factor rather than a page of pre-multiplied + * decimals. Widths that are shares of the page derive from + * {@link #CONTENT_WIDTH}; only genuinely independent marks (a disc's + * diameter, a rule's thickness) are their own constants.

    + * + *

    The type sizes are solved, not estimated

    + * + *

    Each is the drawing's measured ink width for one specific string divided + * by that string's width in ems in the face that sets it. An estimate from an + * average advance came out several per cent small everywhere, which is enough + * to move a line break — and a moved break is the difference a pixel gate can + * see.

    + */ +final class VioletGridStyles { + + private VioletGridStyles() { + } + + // -- the grid ---------------------------------------------------------- + + /** The drawing's raster width against A4's, in points per pixel. */ + static final double PX_TO_PT = 595.276 / 1055.0; + + static final DocumentPageSize PAGE = DocumentPageSize.A4; + + /** + * One measurement off the drawing, in points. + * + * @param pixels the pixel measured on the 1055-wide raster + * @return the same distance on the page + */ + static double px(double pixels) { + return pixels * PX_TO_PT; + } + + /** The engine's line box as a multiple of the type size. */ + static final double LINE_FACTOR = 1.2; + + /** + * A hundredth of a point of slack under every table. The engine compares a + * table's declared width against its container's to the last bit, and a + * sum of derived doubles lands a ten-thousandth of a point over — enough + * to be refused. + */ + static final double TABLE_SLACK = 0.01; + + /** + * No cell borders. A table draws its rules per cell from that cell's own + * style, and the default style has one; every table here is a layout + * mechanism rather than a grid the reader should see, so each states the + * absence explicitly. + */ + static final DocumentStroke NO_BORDER = + DocumentStroke.of(DocumentColor.rgba(0, 0, 0, 0), 0.0); + + // -- palette ----------------------------------------------------------- + + /** The violet everything but the family name is set in. */ + static final DocumentColor ACCENT = DocumentColor.rgb(118, 78, 175); + + /** + * The lighter violet the family name takes. Two tones, not one: the + * drawing sets them measurably apart, and antialiasing only lightens — so + * the difference is a decision rather than noise. + */ + static final DocumentColor ACCENT_LIGHT = DocumentColor.rgb(141, 105, 193); + + /** The hairline that follows every section heading — the accent at half. */ + static final DocumentColor ACCENT_RULE = DocumentColor.rgb(190, 172, 220); + + /** The tint behind the project tiles, the education badge and the band. */ + static final DocumentColor TINT = DocumentColor.rgb(237, 231, 245); + + static final DocumentColor INK = DocumentColor.rgb(28, 26, 32); + static final DocumentColor BODY = DocumentColor.rgb(58, 56, 72); + static final DocumentColor MUTED = DocumentColor.rgb(96, 94, 110); + static final DocumentColor RULE = DocumentColor.rgb(219, 219, 219); + static final DocumentColor RATING_EMPTY = DocumentColor.rgb(226, 226, 226); + + // -- type -------------------------------------------------------------- + + /** + * The display face: the name, the discipline line, the section headings + * and the skill labels. Chosen by measurement: the drawing sets the name + * at a width-to-cap ratio this face matches within a few per cent, where + * the nearest-looking alternative is a quarter too wide. + */ + static final FontName DISPLAY_FONT = FontName.ASAP_CONDENSED; + + /** The body face: everything the display face does not set. */ + static final FontName BODY_FONT = FontName.LATO; + + static final double NAME_SIZE = 46.55; + static final double DISCIPLINE_SIZE = 17.73; + static final double HEADING_SIZE = 12.90; + + /** + * The skill labels, sized so the longest one clears its column's gutter. + * + *

    Solved from the drawing's own ink widths the size comes out larger — + * but at that size the longest label is wider than its column less the + * gutter, and the binding constraint is the gutter rather than the ink. All + * six are set at one size even though only one is constrained: six labels + * of a set at five sizes is a worse document than six one step small.

    + */ + static final double LABEL_SIZE = 6.8; + + static final double CONTACT_SIZE = 8.93; + static final double SUMMARY_SIZE = 9.31; + static final double SKILL_DESC_SIZE = 6.35; + static final double TOOL_SIZE = 8.11; + static final double PERIOD_SIZE = 8.67; + static final double ROLE_SIZE = 9.42; + static final double EMPLOYER_SIZE = 8.09; + static final double LOCATION_SIZE = 8.76; + static final double HIGHLIGHT_SIZE = 8.02; + static final double PROJECT_TITLE_SIZE = 9.62; + static final double PROJECT_SUB_SIZE = 8.35; + static final double PROJECT_YEAR_SIZE = 8.2; + static final double PROJECT_BODY_SIZE = 8.47; + static final double DEGREE_SIZE = 8.93; + static final double INSTITUTION_SIZE = 8.96; + static final double EDU_DETAIL_SIZE = 8.55; + static final double LANGUAGE_SIZE = 8.49; + static final double LEVEL_SIZE = 7.85; + static final double QUOTE_SIZE = 8.64; + + // -- tracking ---------------------------------------------------------- + + /** + * A space's advance in the body face, as a fraction of the em. + * + *

    Deliberately the BODY face's number even though tracking is only ever + * applied to display text: the display face's space is narrow enough that + * a gap of the wanted width would need a space run taller than the type it + * separates, and an inline run sets the line box. The body face's space is + * nearly twice as wide, so the same gap keeps the line box the type's — and + * a space has no glyph to give the substitution away.

    + */ + static final double SPACE_RATIO = 0.256; + + /** + * Tracking in points between one letter and the next. The headings' figure + * is an average over all six: solved one at a time they range too widely + * for any single typeface setting, because the drawing is a raster whose + * glyph advances are not internally consistent — six per-heading constants + * would be fitting noise rather than reproducing a design. + */ + static final double DISCIPLINE_TRACKING = 3.15; + static final double HEADING_TRACKING = 0.77; + + /** Gaps measured as constants whatever the words are, written as spaces. */ + static final String CONTACT_ICON_GAP = " "; + static final String TOOL_GAP = " "; + static final String PIPE = " | "; + static final String RATING_GAP = " "; + static final String QUOTE_GAP = " "; + + // -- the page ---------------------------------------------------------- + + static final double PAGE_MARGIN_X = px(53); + + /** + * Back-calculated rather than measured: the first ink on the page is a cap, + * and a line box starts above its caps, so the margin is where the name's + * box has to begin for its caps to land where the drawing puts them. + */ + static final double PAGE_MARGIN_TOP = px(24); + + /** + * Measured under the quote band, then taken in by three pixels. Nothing on + * this page sits at the bottom margin except the closing band, and the + * drawing puts its lower edge exactly ON the margin — so at the measured + * value the flow has no slack and the band's last points spill onto a + * second page. Three pixels of headroom is the difference between a + * one-page CV and a two-page one. + */ + static final double PAGE_MARGIN_BOTTOM = px(18); + + static final double CONTENT_WIDTH = 595.276 - 2.0 * PAGE_MARGIN_X; + + // -- the masthead ------------------------------------------------------ + + /** Where the contact marks begin, which is where the masthead row splits. */ + static final double CONTACT_COLUMN_X = px(653); + static final double CONTACT_TOP = px(8); + static final double CONTACT_PITCH = px(34.3); + static final double DISCIPLINE_TO_RULE = px(23.3); + static final double IDENTITY_RULE_WIDTH = px(70); + static final double IDENTITY_RULE_THICKNESS = px(4); + + /** + * Measured ink to ink, then corrected by two pixels against the render: + * the measurement is a cap-to-cap distance and what the engine wants is a + * margin under a line BOX, which grew when the name's size was solved. + */ + static final double HEADER_TO_SUMMARY = px(26); + static final double SUMMARY_PITCH = px(22); + + // -- headings ---------------------------------------------------------- + + static final double HEADING_TO_RULE = px(20); + static final double HEADING_RULE_THICKNESS = px(1.5); + + /** + * How far below the heading's line top the rule sits. The drawing centres + * it on the cap band rather than on the line box, and the two differ by + * the leading above the caps. + */ + static final double HEADING_RULE_OFFSET = px(13.5); + + // -- the skills grid --------------------------------------------------- + + static final double SUMMARY_TO_SKILLS = px(33.8); + static final double HEADING_TO_SKILLS = px(19.5); + static final double ICON_TO_LABEL = px(17.4); + static final double LABEL_TO_DESC = px(6); + static final double SKILL_DESC_PITCH = px(18); + static final double SKILL_DIVIDER_THICKNESS = px(2); + + /** + * The gutter between a column's content and the separators either side. + * + *

    The drawing gives its widest column more room than six equal columns + * leave, so the label that just fits there is flush here. This is that + * column's own narrowest gutter, applied to every column so the grid keeps + * one rhythm.

    + */ + static final double SKILL_COLUMN_INSET = px(11); + + /** + * The measure the descriptions wrap to, which is narrower than the column. + * + *

    Solved from the drawing's own line breaks rather than measured off + * it: greedy-wrapping all six descriptions, each column's breaks are + * reproduced by an interval of measures, and this is the value inside the + * most of them. No single measure reproduces all six — the drawing's own + * metrics disagree with any one face — so five of six is the best + * available.

    + */ + static final double SKILL_DESC_MEASURE = 72.5; + + /** + * The dotted separator's height — the one length on this page that is not + * derived from what it sits beside, and a known limit: a skill whose + * description ran to four lines would outgrow its separator. The + * alternative primitive stretches to the content but cannot be dashed, and + * the dotted rule is a visible feature of the design. + */ + static final double SKILL_RAIL_HEIGHT = px(136); + static final double SKILL_DASH_ON = px(2); + static final double SKILL_DASH_OFF = px(3.15); + + // -- the tools strip --------------------------------------------------- + + static final double SKILLS_TO_TOOLS = px(27.3); + static final double HEADING_TO_TOOLS = px(18.7); + static final double TOOL_DOT_DIAMETER = px(5.2); + + // -- the timeline ------------------------------------------------------ + + static final double TOOLS_TO_EXPERIENCE = px(35.7); + static final double HEADING_TO_ENTRIES = px(19.3); + + /** Where the rail stands, as an indent from the page margin. */ + static final double RAIL_INDENT = px(134.5); + + /** The gap between the rail and an entry's own content. */ + static final double ENTRY_INDENT = px(35.5); + static final double RAIL_THICKNESS = px(2); + static final double MARKER_DIAMETER = px(13.5); + static final double ENTRY_WIDTH = + CONTENT_WIDTH - RAIL_INDENT - ENTRY_INDENT - TABLE_SLACK; + static final double LOCATION_COLUMN = px(200); + static final double ROLE_COLUMN = ENTRY_WIDTH - LOCATION_COLUMN; + + /** Air between the dates and the disc. */ + static final double DATE_TO_MARKER = px(18); + + /** + * The dates' column: from the page margin to the disc, less that air. + * + *

    Everything before the rail belongs to the dates and the air after them, and the + * rail runs through the disc's centre — so the column is the rail indent less half a + * disc less the air. It comes to 61.93pt, and the longest date this design sets, + * {@code 2022 - Present}, measures 57.01, so it stays on one line with room to spare. + * The measurement is why the air is stated and the column derived rather than the other + * way round: a column named directly would be a number chosen to clear one string.

    + */ + static final double DATE_COLUMN = RAIL_INDENT - MARKER_DIAMETER / 2.0 - DATE_TO_MARKER; + + /** The disc's own column, and the air between it and the copy. */ + static final double MARKER_TO_COPY = ENTRY_INDENT - MARKER_DIAMETER / 2.0; + + static final double BULLET_COLUMN = px(16); + static final double BULLET_DOT_DIAMETER = px(6); + static final double BULLET_PITCH = px(20.5); + static final double TITLE_TO_BULLETS = px(6.1); + static final double ENTRY_GAP = px(24); + + // -- the projects ------------------------------------------------------ + + static final double EXPERIENCE_TO_PROJECTS = px(27.6); + static final double HEADING_TO_PROJECTS = px(15.1); + static final double PROJECT_GAP = px(17.4); + static final double THUMB_COLUMN = px(138.5); + static final double THUMB_PAD_LEFT = px(21); + static final double THUMB_WIDTH = px(80); + static final double THUMB_HEIGHT = px(76); + static final double THUMB_RADIUS = px(10); + static final double PROJECT_COPY_INDENT = px(34.5); + static final double PROJECT_RULE_THICKNESS = px(2); + static final double PROJECT_COPY_WIDTH = + CONTENT_WIDTH - THUMB_COLUMN - PROJECT_COPY_INDENT - TABLE_SLACK; + static final double PROJECT_YEAR_COLUMN = px(120); + static final double PROJECT_TITLE_COLUMN = PROJECT_COPY_WIDTH - PROJECT_YEAR_COLUMN; + static final double TITLE_TO_BODY = px(3.6); + static final double PROJECT_BODY_PITCH = px(19.5); + + // -- the credentials band ---------------------------------------------- + + static final double PROJECTS_TO_CREDENTIALS = px(15.7); + static final double EDUCATION_COLUMN = px(430); + static final double CREDENTIAL_GUTTER = px(70); + static final double LANGUAGES_COLUMN = + CONTENT_WIDTH - EDUCATION_COLUMN - CREDENTIAL_GUTTER - TABLE_SLACK; + + /** How the two heading rules split what the two heading texts leave. */ + static final double EDUCATION_RULE_WEIGHT = 335.0 / 680.0; + static final double LANGUAGES_RULE_WEIGHT = 345.0 / 680.0; + + static final double HEADING_TO_EDUCATION = px(13.5); + static final double BADGE_DIAMETER = px(72); + static final double BADGE_COLUMN = px(91); + static final double EDUCATION_TEXT_TOP = px(4.1); + static final double DEGREE_TO_INSTITUTION = px(3); + static final double INSTITUTION_TO_DETAIL = px(8.7); + + /** + * The languages rows start a shade below where the education badge does. + * The bodies row already carries the heading gap; this is only the + * difference between the two halves. + */ + static final double LANGUAGES_BODY_OFFSET = px(15.8 - 13.5); + + static final double LANGUAGE_NAME_COLUMN = px(162); + static final double LANGUAGE_LEVEL_COLUMN = px(194); + static final double LANGUAGE_RATING_COLUMN = + LANGUAGES_COLUMN - LANGUAGE_NAME_COLUMN - LANGUAGE_LEVEL_COLUMN; + static final double LANGUAGE_PITCH = px(27.5); + static final double RATING_DOT_DIAMETER = px(10); + + /** How many discs a rating is drawn out of. */ + static final int RATING_SCALE = 5; + + // -- the closing band -------------------------------------------------- + + static final double CREDENTIALS_TO_QUOTE = px(24); + static final double QUOTE_RADIUS = px(6); + static final double QUOTE_PAD_H = px(40); + + /** + * The line box the band has to hold. The quotation mark is taller than the + * type beside it, and an inline run taller than its paragraph's line box is + * drawn clipped — so the box is the mark's height. + */ + static final double QUOTE_LINE_BOX = + Math.max(QUOTE_SIZE * LINE_FACTOR, VioletGridIcons.size(VioletGridIcons.QUOTE)); + + /** The band's measured height less what it holds, halved. */ + static final double QUOTE_PAD_V = (px(48) - QUOTE_LINE_BOX) / 2.0; + + // -- helpers ----------------------------------------------------------- + + static DocumentTextStyle style(FontName font, double size, DocumentColor color, + boolean bold) { + return DocumentTextStyle.builder() + .fontName(font) + .size(size) + .color(color) + .decoration(bold ? DocumentTextDecoration.BOLD : DocumentTextDecoration.DEFAULT) + .build(); + } + + /** A cell's padding and how its content sits in it. */ + static DocumentTableStyle cellStyle(DocumentInsets padding, + DocumentTableTextAnchor anchor) { + return DocumentTableStyle.builder() + .padding(padding) + .textAnchor(anchor) + .stroke(NO_BORDER) + .build(); + } + + /** A measured pitch, minus the content it has to clear, is the gap that produces it. */ + static double gap(double pitch, double contentHeight) { + return Math.max(0.0, pitch - contentHeight); + } + + /** + * A measured pitch as the additive value {@code lineSpacing} actually is: + * the line box is {@link #LINE_FACTOR} times the type size and the setting + * adds points on top of it. + */ + static double leading(double pitch, double size) { + return Math.max(0.0, pitch - LINE_FACTOR * size); + } + + /** How far to drop a mark of this height to centre it on a title's line box. */ + static double centred(double markHeight) { + return Math.max(0.0, (ROLE_SIZE * LINE_FACTOR - markHeight) / 2.0); + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/VioletGridWidgets.java b/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/VioletGridWidgets.java new file mode 100644 index 000000000..6b215eeab --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/VioletGridWidgets.java @@ -0,0 +1,147 @@ +package com.demcha.compose.document.templates.cv.presets; + +import com.demcha.compose.document.dsl.ParagraphBuilder; +import com.demcha.compose.document.dsl.RowBuilder; +import com.demcha.compose.document.node.DocumentNode; +import com.demcha.compose.document.node.InlineImageAlignment; +import com.demcha.compose.document.style.DocumentColor; +import com.demcha.compose.document.style.DocumentInsets; +import com.demcha.compose.document.style.DocumentRowColumn; +import com.demcha.compose.document.style.DocumentTextStyle; +import com.demcha.compose.font.FontName; + +import java.util.ArrayList; +import java.util.List; + +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.ACCENT; +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.ACCENT_RULE; +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.BODY_FONT; +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.DISPLAY_FONT; +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.HEADING_RULE_OFFSET; +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.HEADING_RULE_THICKNESS; +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.HEADING_SIZE; +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.HEADING_TRACKING; +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.HEADING_TO_RULE; +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.SPACE_RATIO; +import static com.demcha.compose.document.templates.cv.presets.VioletGridStyles.style; + +/** + * The idioms this sheet repeats: the letter-spaced run, the heading followed + * by its hairline, a disc on its own, and a line of text as a node. + */ +final class VioletGridWidgets { + + private VioletGridWidgets() { + } + + /** + * Letter-spaced text, as one inline run per character. + * + *

    A text style carries no tracking, so the gap between two letters is a + * space run set at whatever size makes that space the wanted number of + * points wide — which is why the tracking is a number rather than a fixed + * word space. The gap is set in the BODY face even under display text: the + * display face's space is narrow enough that the wanted gap would need a + * run taller than the type it separates, and an inline run sets the line + * box. A space has no glyph to give the substitution away.

    + * + * @param name the node name + * @param text the text to space out + * @param font the face the letters are set in + * @param size the size the letters are set at + * @param color the colour of the letters + * @param bold whether the letters are bold + * @param tracking the gap in points; zero or less writes the text plainly + * @return the paragraph, unbuilt, so a caller can still give it a margin + */ + static ParagraphBuilder tracked(String name, String text, FontName font, double size, + DocumentColor color, boolean bold, double tracking) { + DocumentTextStyle glyph = style(font, size, color, bold); + ParagraphBuilder paragraph = new ParagraphBuilder(); + paragraph.name(name); + paragraph.lineSpacing(0); + paragraph.textStyle(glyph); + if (tracking <= 0.0) { + paragraph.text(text); + return paragraph; + } + DocumentTextStyle gap = style(BODY_FONT, tracking / SPACE_RATIO, color, bold); + for (int index = 0; index < text.length(); index++) { + paragraph.inlineText(String.valueOf(text.charAt(index)), glyph); + if (index < text.length() - 1) { + paragraph.inlineText(" ", gap); + } + } + return paragraph; + } + + /** + * A tracked caps heading followed by a hairline to the right margin. + * + *

    The text column takes what its words need and the rule column takes + * what is left, which is what makes the rule start where the text ends + * without anything having to know how wide the text is. Returned as a node + * rather than added to a builder, so one method serves callers holding + * different builder types.

    + */ + static DocumentNode headingRow(String name, String text, double topMargin) { + RowBuilder row = new RowBuilder(); + row.name(name + "Heading"); + row.spacing(0); + row.margin(new DocumentInsets(topMargin, 0, 0, 0)); + row.columns(DocumentRowColumn.auto(), DocumentRowColumn.weight(1.0)); + row.addSection(name + "HeadingText", cell -> { + cell.spacing(0); + cell.add(tracked(name + "HeadingLabel", text, DISPLAY_FONT, HEADING_SIZE, + ACCENT, true, HEADING_TRACKING).build()); + }); + row.addSection(name + "HeadingRule", cell -> { + cell.spacing(0); + cell.padding((float) HEADING_RULE_OFFSET, 0f, 0f, (float) HEADING_TO_RULE); + cell.addLine(line -> line + .name(name + "HeadingRuleLine") + .fill() + .thickness(HEADING_RULE_THICKNESS) + .color(ACCENT_RULE)); + }); + return row.build(); + } + + /** A disc on its own, in a paragraph whose line box is set by its type. */ + static DocumentNode markerDot(String name, double diameter, double boxSize, + DocumentColor fill) { + ParagraphBuilder paragraph = new ParagraphBuilder(); + paragraph.name(name); + paragraph.lineSpacing(0); + paragraph.textStyle(style(BODY_FONT, boxSize, fill, false)); + paragraph.dot(diameter, fill); + return paragraph.build(); + } + + /** One line of text as a node. */ + static DocumentNode text(String name, String value, DocumentTextStyle textStyle) { + return new ParagraphBuilder() + .name(name) + .text(value) + .lineSpacing(0) + .textStyle(textStyle) + .build(); + } + + /** Places a mark inline, on the baseline of the paragraph it opens. */ + static void inlineIcon(ParagraphBuilder paragraph, String token) { + paragraph.inlineSvgIcon(VioletGridIcons.icon(token), VioletGridIcons.size(token), + InlineImageAlignment.CENTER); + } + + /** A body, one entry per line the document wrote. */ + static List lines(String body) { + List out = new ArrayList<>(); + for (String line : body.split(String.valueOf((char) 10))) { + if (!line.isBlank()) { + out.add(line.strip()); + } + } + return out; + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/package-info.java b/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/package-info.java index 40321b1ae..f07954f04 100644 --- a/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/package-info.java +++ b/templates/src/main/java/com/demcha/compose/document/templates/cv/presets/package-info.java @@ -19,5 +19,14 @@ * a new sealed subtype in {@code cv/data} plus a renderer in * {@code cv/components}; a new preset is this file copied and * fed a different theme.

    + * + *

    The package holds a second shape as well. A preset that reproduces a + * specific drawn sheet — {@code ProfessionalSidebar}, {@code NavySidebar}, + * {@code SerifHeadline} and {@code CharcoalGold} are the ones here — takes + * no {@code BrandTheme} and calls no shared + * renderer: its geometry, palette and type scale are measured constants of + * its own, and it sets its own page. The data model is the same + * {@code CvDocument}; what differs is that the cosmetics cannot be swapped, + * because moving them would stop it being the sheet it reproduces.

    */ package com.demcha.compose.document.templates.cv.presets; diff --git a/templates/src/main/java/com/demcha/compose/document/templates/data/invoice/InvoiceBrand.java b/templates/src/main/java/com/demcha/compose/document/templates/data/invoice/InvoiceBrand.java new file mode 100644 index 000000000..758044b8e --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/data/invoice/InvoiceBrand.java @@ -0,0 +1,77 @@ +package com.demcha.compose.document.templates.data.invoice; + +import com.demcha.compose.document.image.DocumentImageData; + +import java.util.Objects; + +/** + * The sender's brand lockup on a structured invoice: the logo image and + * the wordmark lines beside it. + * + *

    The logo is caller-supplied content, not template chrome — the caller + * hands over a {@link DocumentImageData} built from its own bytes or path, + * the way it would for any other image it owns. It is optional: with no + * logo a preset falls back to the wordmark alone, so an invoice composes + * without one.

    + * + * @param logo the logo image, or {@code null} for the wordmark-only + * lockup + * @param name the brand name, set as the wordmark + * @param qualifier the second wordmark line under the name (e.g. the + * business type) + * @param tagline the tagline under the lockup + * @param monogramTop the first line of a drawn monogram, for the lockups + * that set initials rather than an image; blank when + * absent + * @param monogramBottom the second monogram line; blank when absent + */ +public record InvoiceBrand( + DocumentImageData logo, + String name, + String qualifier, + String tagline, + String monogramTop, + String monogramBottom) { + + /** + * Normalizes the optional text fields; the logo stays nullable. + */ + public InvoiceBrand { + name = Objects.requireNonNullElse(name, ""); + qualifier = Objects.requireNonNullElse(qualifier, ""); + tagline = Objects.requireNonNullElse(tagline, ""); + monogramTop = Objects.requireNonNullElse(monogramTop, ""); + monogramBottom = Objects.requireNonNullElse(monogramBottom, ""); + } + + /** + * Backward-compatible constructor for callers that predate the monogram. + * + * @param logo the logo image, or {@code null} + * @param name the brand name + * @param qualifier the second wordmark line + * @param tagline the tagline under the lockup + */ + public InvoiceBrand(DocumentImageData logo, String name, String qualifier, + String tagline) { + this(logo, name, qualifier, tagline, "", ""); + } + + /** + * Whether this lockup carries a drawn monogram. + * + * @return {@code true} when either monogram line was supplied + */ + public boolean hasMonogram() { + return !monogramTop.isBlank() || !monogramBottom.isBlank(); + } + + /** + * Whether this lockup carries a logo image. + * + * @return {@code true} when a logo was supplied + */ + public boolean hasLogo() { + return logo != null; + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/data/invoice/InvoiceContactBlock.java b/templates/src/main/java/com/demcha/compose/document/templates/data/invoice/InvoiceContactBlock.java new file mode 100644 index 000000000..e9bc1c8fb --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/data/invoice/InvoiceContactBlock.java @@ -0,0 +1,100 @@ +package com.demcha.compose.document.templates.data.invoice; + +import java.util.List; +import java.util.Objects; + +/** + * The sender's address and contact channels on a structured invoice. + * + *

    Distinct from {@link InvoiceParty}, the narrative model's party + * record: this block carries a website channel and a labelled business + * registration (ABN, VAT or company number — the label is content because + * it differs by jurisdiction), which a structured invoice prints in its + * contact band and its footer.

    + * + * @param legalName the registered business name + * @param addressLines the address lines, in order + * @param phone the phone channel + * @param email the email channel + * @param website the website channel + * @param registrationLabel the label of the registration number + * (e.g. {@code "ABN"}) + * @param registrationNumber the registration number itself + * @param taxRegistrationLabel the label of a second registration a + * jurisdiction requires beside the first + * (e.g. {@code "VAT No."}); blank when absent + * @param taxRegistrationNumber that second number itself; blank when absent + * @param legalFootnote a legal disclosure the sheet prints beside the + * name — a parent-company or regulator statement + * such as {@code "… is a subsidiary of …"}; blank + * when the jurisdiction requires none + */ +public record InvoiceContactBlock( + String legalName, + List addressLines, + String phone, + String email, + String website, + String registrationLabel, + String registrationNumber, + String taxRegistrationLabel, + String taxRegistrationNumber, + String legalFootnote) { + + /** + * Normalizes optional fields and freezes the address lines. + */ + public InvoiceContactBlock { + legalName = Objects.requireNonNullElse(legalName, ""); + addressLines = List.copyOf(Objects.requireNonNullElse(addressLines, List.of())); + phone = Objects.requireNonNullElse(phone, ""); + email = Objects.requireNonNullElse(email, ""); + website = Objects.requireNonNullElse(website, ""); + registrationLabel = Objects.requireNonNullElse(registrationLabel, ""); + registrationNumber = Objects.requireNonNullElse(registrationNumber, ""); + taxRegistrationLabel = Objects.requireNonNullElse(taxRegistrationLabel, ""); + taxRegistrationNumber = Objects.requireNonNullElse(taxRegistrationNumber, ""); + legalFootnote = Objects.requireNonNullElse(legalFootnote, ""); + } + + /** + * Backward-compatible constructor for callers that predate the legal + * footnote. + * + * @param legalName the registered business name + * @param addressLines the address lines, in order + * @param phone the phone channel + * @param email the email channel + * @param website the website channel + * @param registrationLabel the label of the registration number + * @param registrationNumber the registration number itself + * @param taxRegistrationLabel the label of the second registration + * @param taxRegistrationNumber that second number itself + */ + public InvoiceContactBlock(String legalName, List addressLines, String phone, + String email, String website, String registrationLabel, + String registrationNumber, String taxRegistrationLabel, + String taxRegistrationNumber) { + this(legalName, addressLines, phone, email, website, registrationLabel, + registrationNumber, taxRegistrationLabel, taxRegistrationNumber, ""); + } + + /** + * Backward-compatible constructor for callers that predate the second + * registration. + * + * @param legalName the registered business name + * @param addressLines the address lines, in order + * @param phone the phone channel + * @param email the email channel + * @param website the website channel + * @param registrationLabel the label of the registration number + * @param registrationNumber the registration number itself + */ + public InvoiceContactBlock(String legalName, List addressLines, String phone, + String email, String website, String registrationLabel, + String registrationNumber) { + this(legalName, addressLines, phone, email, website, + registrationLabel, registrationNumber, "", "", ""); + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/data/invoice/InvoiceMasthead.java b/templates/src/main/java/com/demcha/compose/document/templates/data/invoice/InvoiceMasthead.java new file mode 100644 index 000000000..6f86da5d5 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/data/invoice/InvoiceMasthead.java @@ -0,0 +1,48 @@ +package com.demcha.compose.document.templates.data.invoice; + +import java.util.List; +import java.util.Objects; + +/** + * The masthead of a structured invoice: the document title and the + * label / value metadata printed beside it. + * + *

    The metadata is an authored list rather than fixed fields, so an + * invoice prints exactly the rows its business needs — number, dates, + * project, purchase order, contract reference — in the order and wording + * it uses. That is why the labels live in the data: they differ per + * sender, and a preset cannot know them.

    + * + * @param title the document title (e.g. {@code "INVOICE"}) + * @param entries the metadata rows, in print order + */ +public record InvoiceMasthead(String title, List entries) { + + /** + * Normalizes the title and freezes the entry list. + */ + public InvoiceMasthead { + title = Objects.requireNonNullElse(title, ""); + entries = List.copyOf(Objects.requireNonNullElse(entries, List.of())); + } + + /** + * One metadata row. + * + * @param label the row label + * @param value the row value + * @param emphasized whether the value carries emphasis — the due date + * is the usual case; presets are expected to set an + * emphasized value apart + */ + public record Entry(String label, String value, boolean emphasized) { + + /** + * Normalizes optional fields to empty strings. + */ + public Entry { + label = Objects.requireNonNullElse(label, ""); + value = Objects.requireNonNullElse(value, ""); + } + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/data/invoice/InvoiceNotesBlock.java b/templates/src/main/java/com/demcha/compose/document/templates/data/invoice/InvoiceNotesBlock.java new file mode 100644 index 000000000..9ab469ef3 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/data/invoice/InvoiceNotesBlock.java @@ -0,0 +1,30 @@ +package com.demcha.compose.document.templates.data.invoice; + +import java.util.List; +import java.util.Objects; + +/** + * The closing notes of a structured invoice, with the channels to raise a + * query on. + * + * @param heading the block heading (e.g. {@code "NOTES"}) + * @param paragraphs the note paragraphs, in order + * @param contactEmail the email address a query goes to + * @param contactPhone the phone number a query goes to + */ +public record InvoiceNotesBlock( + String heading, + List paragraphs, + String contactEmail, + String contactPhone) { + + /** + * Normalizes optional fields and freezes the paragraph list. + */ + public InvoiceNotesBlock { + heading = Objects.requireNonNullElse(heading, ""); + paragraphs = List.copyOf(Objects.requireNonNullElse(paragraphs, List.of())); + contactEmail = Objects.requireNonNullElse(contactEmail, ""); + contactPhone = Objects.requireNonNullElse(contactPhone, ""); + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/data/invoice/InvoicePaymentBlock.java b/templates/src/main/java/com/demcha/compose/document/templates/data/invoice/InvoicePaymentBlock.java new file mode 100644 index 000000000..8b5ae46b8 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/data/invoice/InvoicePaymentBlock.java @@ -0,0 +1,85 @@ +package com.demcha.compose.document.templates.data.invoice; + +import java.util.List; +import java.util.Objects; + +/** + * The payment-information block of a structured invoice: where to send the + * money, and by when. + * + *

    Distinct from the narrative model's + * {@link InvoiceData#paymentTerms()}, a list of prose bullets: the fields + * here are labelled values printed as a table (bank, account name, sort + * code or BSB, account number, payment reference), because a payer reads + * them one field at a time. The labels are content — they differ by + * banking system.

    + * + * @param heading the block heading + * (e.g. {@code "PAYMENT INFORMATION"}) + * @param fields the labelled payment fields, in print order + * @param instruction the sentence under the fields + * @param dueNotice the short due-by notice set apart from the rest + * @param dueNoticeEmphasis the run inside {@code dueNotice} that carries + * the emphasis — usually the term itself, as in + * {@code "30 days"}; empty leaves the notice + * evenly set + * @param accountHolder who the money is paid to, set above the fields + * where a design names the account before listing + * it; blank when absent + * @param signOff the closing line a design sets beside the due + * notice, usually a thank-you; blank when absent + */ +public record InvoicePaymentBlock( + String heading, + List fields, + String instruction, + String dueNotice, + String dueNoticeEmphasis, + String accountHolder, + String signOff) { + + /** + * Normalizes optional fields and freezes the field list. + */ + public InvoicePaymentBlock { + heading = Objects.requireNonNullElse(heading, ""); + fields = List.copyOf(Objects.requireNonNullElse(fields, List.of())); + instruction = Objects.requireNonNullElse(instruction, ""); + dueNotice = Objects.requireNonNullElse(dueNotice, ""); + dueNoticeEmphasis = Objects.requireNonNullElse(dueNoticeEmphasis, ""); + accountHolder = Objects.requireNonNullElse(accountHolder, ""); + signOff = Objects.requireNonNullElse(signOff, ""); + } + + /** + * Backward-compatible constructor for callers that predate the account + * holder and the sign-off. + * + * @param heading the block heading + * @param fields the labelled payment fields, in print order + * @param instruction the sentence under the fields + * @param dueNotice the short due-by notice + * @param dueNoticeEmphasis the emphasised run inside the notice + */ + public InvoicePaymentBlock(String heading, List fields, String instruction, + String dueNotice, String dueNoticeEmphasis) { + this(heading, fields, instruction, dueNotice, dueNoticeEmphasis, "", ""); + } + + /** + * One labelled payment field. + * + * @param label the field label + * @param value the field value + */ + public record Field(String label, String value) { + + /** + * Normalizes optional fields to empty strings. + */ + public Field { + label = Objects.requireNonNullElse(label, ""); + value = Objects.requireNonNullElse(value, ""); + } + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/data/invoice/InvoiceRecipient.java b/templates/src/main/java/com/demcha/compose/document/templates/data/invoice/InvoiceRecipient.java new file mode 100644 index 000000000..ae63c70a5 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/data/invoice/InvoiceRecipient.java @@ -0,0 +1,76 @@ +package com.demcha.compose.document.templates.data.invoice; + +import java.util.List; +import java.util.Objects; + +/** + * The billed-to block of a structured invoice. + * + *

    The block owns its heading, and the lines under the recipient's name + * come in two groups because a preset sets them apart: the {@code subline} + * is the single attention line — a department, a contact, a cost centre — + * that sits directly under the name, and {@code addressLines} is the + * address block under that.

    + * + * @param heading the block heading (e.g. {@code "BILLED TO"}) + * @param name the recipient's name, set apart from the lines below + * @param subline the attention line under the name; empty when the + * recipient has none + * @param addressLines the address lines, in order + * @param emailLabel the label printed before the email address + * @param email the recipient's email address + * @param registrationLabel the label a printed registration is named by + * (e.g. {@code "VAT Number"}); blank when absent + * @param registrationNumber the registration itself, printed under the + * address rather than under the name; blank when + * absent + */ +public record InvoiceRecipient( + String heading, + String name, + String subline, + List addressLines, + String emailLabel, + String email, + String registrationLabel, + String registrationNumber) { + + /** + * Normalizes optional fields and freezes the address lines. + */ + public InvoiceRecipient { + heading = Objects.requireNonNullElse(heading, ""); + name = Objects.requireNonNullElse(name, ""); + subline = Objects.requireNonNullElse(subline, ""); + addressLines = List.copyOf(Objects.requireNonNullElse(addressLines, List.of())); + emailLabel = Objects.requireNonNullElse(emailLabel, ""); + email = Objects.requireNonNullElse(email, ""); + registrationLabel = Objects.requireNonNullElse(registrationLabel, ""); + registrationNumber = Objects.requireNonNullElse(registrationNumber, ""); + } + + /** + * Backward-compatible constructor for callers that predate the printed + * registration. The recipient simply carries none. + * + * @param heading the block heading + * @param name the recipient's name + * @param subline the attention line under the name + * @param addressLines the address lines, in order + * @param emailLabel the label printed before the email address + * @param email the recipient's email address + */ + public InvoiceRecipient(String heading, String name, String subline, + List addressLines, String emailLabel, String email) { + this(heading, name, subline, addressLines, emailLabel, email, "", ""); + } + + /** + * Whether this recipient prints a registration under its address. + * + * @return {@code true} when the number is set + */ + public boolean hasRegistration() { + return !registrationNumber.isBlank(); + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/data/invoice/InvoiceServiceLines.java b/templates/src/main/java/com/demcha/compose/document/templates/data/invoice/InvoiceServiceLines.java new file mode 100644 index 000000000..c5d773133 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/data/invoice/InvoiceServiceLines.java @@ -0,0 +1,224 @@ +package com.demcha.compose.document.templates.data.invoice; + +import java.math.BigDecimal; +import java.util.List; +import java.util.Objects; + +/** + * The line-items table of a structured invoice: its column labels and its + * priced service lines. + * + *

    Distinct from the narrative model's {@link InvoiceLineItem}, whose + * quantity, unit price and amount are pre-formatted strings: a service + * line carries {@link BigDecimal} figures plus the unit they are counted + * in, so the preset formats them against the document's currency and the + * caller keeps the arithmetic. The column labels are content because the + * currency code and the wording appear inside them.

    + * + * @param columns the column labels + * @param lines the priced service lines, in print order + */ +public record InvoiceServiceLines(Columns columns, List lines) { + + /** + * Normalizes an absent column set and freezes the line list. + */ + public InvoiceServiceLines { + columns = columns == null + ? new Columns(null, null, null, null, null, null, null) + : columns; + lines = List.copyOf(Objects.requireNonNullElse(lines, List.of())); + } + + /** + * The table's column labels, in column order. + * + * @param index label of the line-number column + * @param description label of the description column + * @param servicePeriod label of the service-period column + * @param quantity label of the quantity column + * @param unitPrice label of the unit-price column + * @param amount label of the amount column + * @param vat label of the tax-rate column, for the + * jurisdictions that print the rate per line; + * blank leaves the column out + * @param region label of the region column, for the designs that + * bill the same service in more than one location + * and print where beside what; blank leaves the + * column out + */ + public record Columns( + String index, + String description, + String servicePeriod, + String quantity, + String unitPrice, + String amount, + String vat, + String region) { + + /** + * Normalizes optional labels to empty strings. + */ + public Columns { + index = Objects.requireNonNullElse(index, ""); + description = Objects.requireNonNullElse(description, ""); + servicePeriod = Objects.requireNonNullElse(servicePeriod, ""); + quantity = Objects.requireNonNullElse(quantity, ""); + unitPrice = Objects.requireNonNullElse(unitPrice, ""); + amount = Objects.requireNonNullElse(amount, ""); + vat = Objects.requireNonNullElse(vat, ""); + region = Objects.requireNonNullElse(region, ""); + } + + /** + * Backward-compatible constructor for callers that predate the region + * column. + * + * @param index label of the line-number column + * @param description label of the description column + * @param servicePeriod label of the service-period column + * @param quantity label of the quantity column + * @param unitPrice label of the unit-price column + * @param amount label of the amount column + * @param vat label of the tax-rate column + */ + public Columns(String index, String description, String servicePeriod, + String quantity, String unitPrice, String amount, String vat) { + this(index, description, servicePeriod, quantity, unitPrice, amount, vat, ""); + } + + /** + * Backward-compatible constructor for callers that predate the + * tax-rate column. + * + * @param index label of the line-number column + * @param description label of the description column + * @param servicePeriod label of the service-period column + * @param quantity label of the quantity column + * @param unitPrice label of the unit-price column + * @param amount label of the amount column + */ + public Columns(String index, String description, String servicePeriod, + String quantity, String unitPrice, String amount) { + this(index, description, servicePeriod, quantity, unitPrice, amount, ""); + } + } + + /** + * One priced service line. + * + * @param lineNumber the printed line number + * @param title the service title, set apart from the description + * @param description the description under the title + * @param servicePeriod the period this line covers + * @param quantity how much was delivered; {@code null} normalizes + * to {@link BigDecimal#ZERO} + * @param unit what the quantity counts (e.g. {@code "hrs"}) + * @param unitPrice the price per unit; {@code null} normalizes to + * {@link BigDecimal#ZERO} + * @param amount the line total; {@code null} normalizes to + * {@link BigDecimal#ZERO} + * @param vatRate the tax rate printed for this line, written as + * the design shows it (e.g. {@code "20%"}) rather + * than as a number, because the wording differs by + * jurisdiction; blank when absent + * @param icon the mark a preset draws for this line; the token + * means something only to the preset that packages + * it, and a preset that draws no marks ignores it. + * Blank when absent + * @param region where this line was delivered, for the designs that + * bill the same service in more than one location and + * print where beside what; blank when absent + */ + public record Line( + int lineNumber, + String title, + String description, + String servicePeriod, + BigDecimal quantity, + String unit, + BigDecimal unitPrice, + BigDecimal amount, + String vatRate, + String icon, + String region) { + + /** + * Normalizes optional fields. + */ + public Line { + title = Objects.requireNonNullElse(title, ""); + description = Objects.requireNonNullElse(description, ""); + servicePeriod = Objects.requireNonNullElse(servicePeriod, ""); + quantity = Objects.requireNonNullElse(quantity, BigDecimal.ZERO); + unit = Objects.requireNonNullElse(unit, ""); + unitPrice = Objects.requireNonNullElse(unitPrice, BigDecimal.ZERO); + amount = Objects.requireNonNullElse(amount, BigDecimal.ZERO); + vatRate = Objects.requireNonNullElse(vatRate, ""); + icon = Objects.requireNonNullElse(icon, ""); + region = Objects.requireNonNullElse(region, ""); + } + + /** + * Backward-compatible constructor for callers that predate the region. + * + * @param lineNumber the printed line number + * @param title the service title + * @param description the description under the title + * @param servicePeriod the period this line covers + * @param quantity how much was delivered + * @param unit what the quantity counts + * @param unitPrice the price per unit + * @param amount the line total + * @param vatRate the tax rate printed for this line + * @param icon the mark a preset draws for this line + */ + public Line(int lineNumber, String title, String description, String servicePeriod, + BigDecimal quantity, String unit, BigDecimal unitPrice, + BigDecimal amount, String vatRate, String icon) { + this(lineNumber, title, description, servicePeriod, quantity, unit, + unitPrice, amount, vatRate, icon, ""); + } + + /** + * Backward-compatible constructor for callers that predate the mark. + * + * @param lineNumber the printed line number + * @param title the service title + * @param description the description under the title + * @param servicePeriod the period this line covers + * @param quantity how much was delivered + * @param unit what the quantity counts + * @param unitPrice the price per unit + * @param amount the line total + * @param vatRate the tax rate printed for this line + */ + public Line(int lineNumber, String title, String description, String servicePeriod, + BigDecimal quantity, String unit, BigDecimal unitPrice, + BigDecimal amount, String vatRate) { + this(lineNumber, title, description, servicePeriod, quantity, unit, + unitPrice, amount, vatRate, "", ""); + } + + /** + * Backward-compatible constructor for callers that predate the + * per-line tax rate. + * + * @param lineNumber the printed line number + * @param title the service title + * @param description the description under the title + * @param servicePeriod the period this line covers + * @param quantity how much was delivered + * @param unit what the quantity counts + * @param unitPrice the price per unit + * @param amount the line total + */ + public Line(int lineNumber, String title, String description, String servicePeriod, + BigDecimal quantity, String unit, BigDecimal unitPrice, + BigDecimal amount) { + this(lineNumber, title, description, servicePeriod, quantity, unit, + unitPrice, amount, "", "", ""); + } + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/data/invoice/InvoiceSummaryBlock.java b/templates/src/main/java/com/demcha/compose/document/templates/data/invoice/InvoiceSummaryBlock.java new file mode 100644 index 000000000..e857c2e10 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/data/invoice/InvoiceSummaryBlock.java @@ -0,0 +1,23 @@ +package com.demcha.compose.document.templates.data.invoice; + +import java.util.Objects; + +/** + * The summary block of a structured invoice: what the invoice covers and + * the period it covers it for. + * + * @param heading the block heading (e.g. {@code "INVOICE SUMMARY"}) + * @param intro the prose sentence introducing the work billed + * @param servicePeriod the billed period, set apart from the intro + */ +public record InvoiceSummaryBlock(String heading, String intro, String servicePeriod) { + + /** + * Normalizes optional fields to empty strings. + */ + public InvoiceSummaryBlock { + heading = Objects.requireNonNullElse(heading, ""); + intro = Objects.requireNonNullElse(intro, ""); + servicePeriod = Objects.requireNonNullElse(servicePeriod, ""); + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/data/invoice/InvoiceTotalsBlock.java b/templates/src/main/java/com/demcha/compose/document/templates/data/invoice/InvoiceTotalsBlock.java new file mode 100644 index 000000000..ccab355ac --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/data/invoice/InvoiceTotalsBlock.java @@ -0,0 +1,51 @@ +package com.demcha.compose.document.templates.data.invoice; + +import java.math.BigDecimal; +import java.util.List; +import java.util.Objects; + +/** + * The totals stack of a structured invoice: the rows leading up to the + * grand total, and the total band itself. + * + *

    Distinct from the narrative model's {@link InvoiceSummaryRow} list, + * where the grand total is the last row and is recognised by convention: + * here the total is its own labelled band with its own amount, so a preset + * renders it without inspecting the list.

    + * + * @param rows the rows above the total (subtotal, tax, discounts), + * in print order + * @param totalLabel the label of the total band + * @param totalAmount the amount of the total band; {@code null} normalizes + * to {@link BigDecimal#ZERO} + */ +public record InvoiceTotalsBlock(List rows, String totalLabel, BigDecimal totalAmount) { + + /** + * Normalizes optional fields and freezes the row list. + */ + public InvoiceTotalsBlock { + rows = List.copyOf(Objects.requireNonNullElse(rows, List.of())); + totalLabel = Objects.requireNonNullElse(totalLabel, ""); + totalAmount = Objects.requireNonNullElse(totalAmount, BigDecimal.ZERO); + } + + /** + * One row above the total band. + * + * @param label the row label, carrying its own wording (e.g. a tax + * name and rate) + * @param amount the row amount; {@code null} normalizes to + * {@link BigDecimal#ZERO} + */ + public record Row(String label, BigDecimal amount) { + + /** + * Normalizes optional fields. + */ + public Row { + label = Objects.requireNonNullElse(label, ""); + amount = Objects.requireNonNullElse(amount, BigDecimal.ZERO); + } + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/data/invoice/StructuredInvoiceData.java b/templates/src/main/java/com/demcha/compose/document/templates/data/invoice/StructuredInvoiceData.java new file mode 100644 index 000000000..21d964bd7 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/data/invoice/StructuredInvoiceData.java @@ -0,0 +1,259 @@ +package com.demcha.compose.document.templates.data.invoice; + +import java.util.Objects; + +/** + * Structured input for business invoice documents. + * + *

    The display model ({@link InvoiceData}) carries an invoice as + * pre-formatted strings: one address block per party, line items whose + * quantity and money are already rendered, and a flat list of summary + * rows. This model is the structured business invoice: a brand lockup with + * the sender's own logo, labelled masthead metadata, a contact block with + * a business registration, priced service lines carrying + * {@link java.math.BigDecimal} figures and the unit they are counted in, a + * totals stack with its own total band, bank payment fields, and a footer + * line. Each block owns its heading and labels, so the wording is content + * rather than a preset choice.

    + * + *

    Both models stay: a preset consumes the one whose shape it renders. + * Every component normalizes {@code null} to its empty form, so a partial + * document composes without null checks in preset code. The section + * records construct positionally; this builder is where a document is + * assembled.

    + * + * @param brand the sender's brand lockup + * @param supplier the sender's address and contact channels + * @param masthead the document title and its metadata rows + * @param billTo the billed-to block + * @param shipTo the shipped-to block, for the designs that print a + * delivery address beside the billing one; empty when + * the two are the same or the design shows only one + * @param summary what the invoice covers, and for which period + * @param serviceLines the line-items table + * @param totals the totals stack and its total band + * @param payment where to send the money, and by when + * @param notes the closing notes and query channels + * @param currencyCode the ISO currency code the figures are stated in + */ +public record StructuredInvoiceData( + InvoiceBrand brand, + InvoiceContactBlock supplier, + InvoiceMasthead masthead, + InvoiceRecipient billTo, + InvoiceRecipient shipTo, + InvoiceSummaryBlock summary, + InvoiceServiceLines serviceLines, + InvoiceTotalsBlock totals, + InvoicePaymentBlock payment, + InvoiceNotesBlock notes, + String currencyCode) { + + /** + * Normalizes absent components to their empty forms. + */ + public StructuredInvoiceData { + brand = brand == null ? new InvoiceBrand(null, null, null, null) : brand; + supplier = supplier == null + ? new InvoiceContactBlock(null, null, null, null, null, null, null) : supplier; + masthead = masthead == null ? new InvoiceMasthead(null, null) : masthead; + billTo = billTo == null + ? new InvoiceRecipient(null, null, null, null, null, null) : billTo; + shipTo = shipTo == null + ? new InvoiceRecipient(null, null, null, null, null, null) : shipTo; + summary = summary == null ? new InvoiceSummaryBlock(null, null, null) : summary; + serviceLines = serviceLines == null + ? new InvoiceServiceLines(null, null) : serviceLines; + totals = totals == null ? new InvoiceTotalsBlock(null, null, null) : totals; + payment = payment == null + ? new InvoicePaymentBlock(null, null, null, null, null) : payment; + notes = notes == null ? new InvoiceNotesBlock(null, null, null, null) : notes; + currencyCode = Objects.requireNonNullElse(currencyCode, ""); + } + + /** + * Backward-compatible constructor for callers that predate the + * shipped-to block. + * + * @param brand the sender's brand lockup + * @param supplier the sender's address and contact channels + * @param masthead the document title and its metadata rows + * @param billTo the billed-to block + * @param summary what the invoice covers, and for which period + * @param serviceLines the line-items table + * @param totals the totals stack and its total band + * @param payment where to send the money, and by when + * @param notes the closing notes and query channels + * @param currencyCode the ISO currency code the figures are stated in + */ + public StructuredInvoiceData(InvoiceBrand brand, InvoiceContactBlock supplier, + InvoiceMasthead masthead, InvoiceRecipient billTo, + InvoiceSummaryBlock summary, + InvoiceServiceLines serviceLines, + InvoiceTotalsBlock totals, InvoicePaymentBlock payment, + InvoiceNotesBlock notes, String currencyCode) { + this(brand, supplier, masthead, billTo, null, summary, serviceLines, + totals, payment, notes, currencyCode); + } + + /** + * Starts a fluent structured invoice data builder. + * + * @return structured invoice data builder + */ + public static Builder builder() { + return new Builder(); + } + + /** + * Fluent builder for complete structured invoice content. + */ + public static final class Builder { + private InvoiceBrand brand; + private InvoiceContactBlock supplier; + private InvoiceMasthead masthead; + private InvoiceRecipient billTo; + private InvoiceRecipient shipTo; + private InvoiceSummaryBlock summary; + private InvoiceServiceLines serviceLines; + private InvoiceTotalsBlock totals; + private InvoicePaymentBlock payment; + private InvoiceNotesBlock notes; + private String currencyCode; + + private Builder() { + } + + /** + * Sets the sender's brand lockup. + * + * @param brand brand lockup + * @return this builder + */ + public Builder brand(InvoiceBrand brand) { + this.brand = brand; + return this; + } + + /** + * Sets the sender's address and contact channels. + * + * @param supplier contact block + * @return this builder + */ + public Builder supplier(InvoiceContactBlock supplier) { + this.supplier = supplier; + return this; + } + + /** + * Sets the document title and its metadata rows. + * + * @param masthead masthead + * @return this builder + */ + public Builder masthead(InvoiceMasthead masthead) { + this.masthead = masthead; + return this; + } + + /** + * Sets the billed-to block. + * + * @param billTo recipient block + * @return this builder + */ + public Builder billTo(InvoiceRecipient billTo) { + this.billTo = billTo; + return this; + } + + /** + * Sets the shipped-to block, for the designs that print a delivery + * address beside the billing one. + * + * @param shipTo recipient block + * @return this builder + */ + public Builder shipTo(InvoiceRecipient shipTo) { + this.shipTo = shipTo; + return this; + } + + /** + * Sets what the invoice covers, and for which period. + * + * @param summary summary block + * @return this builder + */ + public Builder summary(InvoiceSummaryBlock summary) { + this.summary = summary; + return this; + } + + /** + * Sets the line-items table. + * + * @param serviceLines service lines + * @return this builder + */ + public Builder serviceLines(InvoiceServiceLines serviceLines) { + this.serviceLines = serviceLines; + return this; + } + + /** + * Sets the totals stack and its total band. + * + * @param totals totals block + * @return this builder + */ + public Builder totals(InvoiceTotalsBlock totals) { + this.totals = totals; + return this; + } + + /** + * Sets where the money goes, and by when. + * + * @param payment payment block + * @return this builder + */ + public Builder payment(InvoicePaymentBlock payment) { + this.payment = payment; + return this; + } + + /** + * Sets the closing notes and query channels. + * + * @param notes notes block + * @return this builder + */ + public Builder notes(InvoiceNotesBlock notes) { + this.notes = notes; + return this; + } + + /** + * Sets the ISO currency code the figures are stated in. + * + * @param currencyCode currency code + * @return this builder + */ + public Builder currencyCode(String currencyCode) { + this.currencyCode = currencyCode; + return this; + } + + /** + * Builds the normalized structured invoice data. + * + * @return structured invoice data + */ + public StructuredInvoiceData build() { + return new StructuredInvoiceData(brand, supplier, masthead, billTo, shipTo, + summary, serviceLines, totals, payment, notes, currencyCode); + } + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/data/invoice/StructuredInvoiceDocumentSpec.java b/templates/src/main/java/com/demcha/compose/document/templates/data/invoice/StructuredInvoiceDocumentSpec.java new file mode 100644 index 000000000..854d16464 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/data/invoice/StructuredInvoiceDocumentSpec.java @@ -0,0 +1,35 @@ +package com.demcha.compose.document.templates.data.invoice; + +/** + * Public compose-first input for structured invoice templates. + * + *

    Authoring role: the document-level object structured invoice + * presets are parameterised on, the way the display-oriented presets are + * parameterised on {@link InvoiceDocumentSpec}. Presets that render the + * structured shape — brand lockup, labelled masthead metadata, priced + * service lines with a numeric totals stack, bank payment fields — + * consume this spec; presets that render pre-formatted display strings + * stay on the other one.

    + * + * @param invoice normalized structured invoice content + */ +public record StructuredInvoiceDocumentSpec(StructuredInvoiceData invoice) { + + /** + * Creates a normalized structured invoice document spec. + */ + public StructuredInvoiceDocumentSpec { + invoice = invoice == null ? StructuredInvoiceData.builder().build() : invoice; + } + + /** + * Wraps existing structured invoice data in the document-level spec + * expected by structured invoice templates. + * + * @param invoice structured invoice data + * @return document spec + */ + public static StructuredInvoiceDocumentSpec from(StructuredInvoiceData invoice) { + return new StructuredInvoiceDocumentSpec(invoice); + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/data/package-info.java b/templates/src/main/java/com/demcha/compose/document/templates/data/package-info.java index bc0c92ede..fc84a62df 100644 --- a/templates/src/main/java/com/demcha/compose/document/templates/data/package-info.java +++ b/templates/src/main/java/com/demcha/compose/document/templates/data/package-info.java @@ -3,6 +3,7 @@ * {@code document.templates} layer. * *

    Concrete public data types live in child packages such as - * {@code data.invoice}, {@code data.proposal}, and {@code data.schedule}.

    + * {@code data.invoice}, {@code data.proposal}, and {@code data.rota}. + * {@code data.schedule} is deprecated in favour of {@code data.rota}.

    */ package com.demcha.compose.document.templates.data; diff --git a/templates/src/main/java/com/demcha/compose/document/templates/data/proposal/ProposalAcceptance.java b/templates/src/main/java/com/demcha/compose/document/templates/data/proposal/ProposalAcceptance.java new file mode 100644 index 000000000..5c24f4220 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/data/proposal/ProposalAcceptance.java @@ -0,0 +1,31 @@ +package com.demcha.compose.document.templates.data.proposal; + +import java.util.List; +import java.util.Objects; + +/** + * The signing card that closes a structured proposal: an icon-badged + * heading, the acceptance statement, and the labelled signature fields. + * + * @param heading the section heading + * @param icon the icon token of the section badge + * @param statement the acceptance statement paragraph + * @param fields the signature field labels, in order (e.g. name, date, + * signature) + */ +public record ProposalAcceptance( + String heading, + String icon, + String statement, + List fields) { + + /** + * Normalizes optional fields and freezes the field list. + */ + public ProposalAcceptance { + heading = Objects.requireNonNullElse(heading, ""); + icon = Objects.requireNonNullElse(icon, ""); + statement = Objects.requireNonNullElse(statement, ""); + fields = List.copyOf(Objects.requireNonNullElse(fields, List.of())); + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/data/proposal/ProposalAttention.java b/templates/src/main/java/com/demcha/compose/document/templates/data/proposal/ProposalAttention.java new file mode 100644 index 000000000..90bb27384 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/data/proposal/ProposalAttention.java @@ -0,0 +1,44 @@ +package com.demcha.compose.document.templates.data.proposal; + +import java.util.Objects; + +/** + * The person at the recipient a proposal is addressed to. + * + *

    A sales proposal names a company and then a person inside it, with the + * person's role and the two ways to reach them. Designs differ in how much of + * that they set apart — some print the name in the caption, some give the role, + * the address and the number their own lines — so each is carried separately and + * a preset draws the ones its design has.

    + * + * @param label the caption above the block, as the sheet prints it (e.g. + * {@code "ATTN"}); some designs fold the name into it, which is why + * this is the design's own string rather than a fixed word + * @param name the person addressed; blank when the label already names them + * @param role their role at the recipient; blank when the design omits it + * @param email their address, as the sheet prints it; blank when absent + * @param phone their number, as the sheet prints it; blank when absent + */ +public record ProposalAttention(String label, String name, String role, + String email, String phone) { + + /** + * Normalizes optional fields. + */ + public ProposalAttention { + label = Objects.requireNonNullElse(label, ""); + name = Objects.requireNonNullElse(name, ""); + role = Objects.requireNonNullElse(role, ""); + email = Objects.requireNonNullElse(email, ""); + phone = Objects.requireNonNullElse(phone, ""); + } + + /** + * Whether there is anything to draw. + * + * @return {@code true} when the block names a person or a way to reach them + */ + public boolean isPresent() { + return !name.isBlank() || !role.isBlank() || !email.isBlank() || !phone.isBlank(); + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/data/proposal/ProposalBrand.java b/templates/src/main/java/com/demcha/compose/document/templates/data/proposal/ProposalBrand.java new file mode 100644 index 000000000..ebec53e4f --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/data/proposal/ProposalBrand.java @@ -0,0 +1,39 @@ +package com.demcha.compose.document.templates.data.proposal; + +import java.util.Objects; + +/** + * The sender's brand marks for a structured proposal: the logo monogram, + * the two wordmark lines, the tracked document label, and the footer line. + * + *

    These are display strings, not identity data — the narrative model's + * {@link ProposalParty} carries the sender's address and contact details; + * this record carries only what the page chrome draws.

    + * + * @param monogram the one- or two-letter logo mark + * @param nameLine1 first wordmark line + * @param nameLine2 second wordmark line + * @param documentLabel the document label, set in tracked capitals + * @param website the website line shown in the footer + * @param footerName the brand name shown in the footer + */ +public record ProposalBrand( + String monogram, + String nameLine1, + String nameLine2, + String documentLabel, + String website, + String footerName) { + + /** + * Normalizes optional fields to empty strings. + */ + public ProposalBrand { + monogram = Objects.requireNonNullElse(monogram, ""); + nameLine1 = Objects.requireNonNullElse(nameLine1, ""); + nameLine2 = Objects.requireNonNullElse(nameLine2, ""); + documentLabel = Objects.requireNonNullElse(documentLabel, ""); + website = Objects.requireNonNullElse(website, ""); + footerName = Objects.requireNonNullElse(footerName, ""); + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/data/proposal/ProposalDeliverables.java b/templates/src/main/java/com/demcha/compose/document/templates/data/proposal/ProposalDeliverables.java new file mode 100644 index 000000000..e492d92ce --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/data/proposal/ProposalDeliverables.java @@ -0,0 +1,34 @@ +package com.demcha.compose.document.templates.data.proposal; + +import java.util.List; +import java.util.Objects; + +/** + * The deliverables band of a structured proposal: an icon-badged heading and + * two authored bullet columns. + * + *

    The left/right split is authored rather than computed so it can be + * tuned per proposal — moving one long bullet between columns is a data + * revision, not a layout change.

    + * + * @param heading the section heading + * @param icon the icon token of the section badge + * @param leftColumn the bullets of the left column, in order + * @param rightColumn the bullets of the right column, in order + */ +public record ProposalDeliverables( + String heading, + String icon, + List leftColumn, + List rightColumn) { + + /** + * Normalizes optional fields and freezes both columns. + */ + public ProposalDeliverables { + heading = Objects.requireNonNullElse(heading, ""); + icon = Objects.requireNonNullElse(icon, ""); + leftColumn = List.copyOf(Objects.requireNonNullElse(leftColumn, List.of())); + rightColumn = List.copyOf(Objects.requireNonNullElse(rightColumn, List.of())); + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/data/proposal/ProposalFooter.java b/templates/src/main/java/com/demcha/compose/document/templates/data/proposal/ProposalFooter.java new file mode 100644 index 000000000..ab1c8eab0 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/data/proposal/ProposalFooter.java @@ -0,0 +1,44 @@ +package com.demcha.compose.document.templates.data.proposal; + +import java.util.List; +import java.util.Objects; + +/** + * The issuer's own identity, as the foot of a proposal states it. + * + *

    Distinct from {@link ProposalBrand#footerName()}, which is one name: a + * sales proposal's foot carries the legal entity, its registered address, the + * channels a reader can follow, and — where the jurisdiction or the deal asks + * for it — a confidentiality line. A design that prints only the name leaves the + * rest blank.

    + * + * @param name the legal entity the proposal is issued by + * @param addressLines its registered address, in the order it is printed + * @param contacts the channels the foot lists, as the sheet prints them — + * a site, a number, an address — in order + * @param confidentiality the confidentiality or validity line, when the design + * carries one; blank otherwise + */ +public record ProposalFooter(String name, List addressLines, + List contacts, String confidentiality) { + + /** + * Normalizes optional fields. + */ + public ProposalFooter { + name = Objects.requireNonNullElse(name, ""); + addressLines = List.copyOf(Objects.requireNonNullElse(addressLines, List.of())); + contacts = List.copyOf(Objects.requireNonNullElse(contacts, List.of())); + confidentiality = Objects.requireNonNullElse(confidentiality, ""); + } + + /** + * Whether there is anything to draw. + * + * @return {@code true} when the foot states any of its four parts + */ + public boolean isPresent() { + return !name.isBlank() || !addressLines.isEmpty() + || !contacts.isEmpty() || !confidentiality.isBlank(); + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/data/proposal/ProposalGlance.java b/templates/src/main/java/com/demcha/compose/document/templates/data/proposal/ProposalGlance.java new file mode 100644 index 000000000..4bd099b3c --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/data/proposal/ProposalGlance.java @@ -0,0 +1,59 @@ +package com.demcha.compose.document.templates.data.proposal; + +import java.util.List; +import java.util.Objects; + +/** + * The at-a-glance fact card of a structured proposal. + * + * @param heading the card heading + * @param facts the fact rows, in order + * @param intro the paragraph that opens the block, above its items; every + * one-page sales proposal measured for this sets one, most of + * them twice, and a block that opens straight into its list + * leaves it blank + */ +public record ProposalGlance(String heading, List facts, String intro) { + + /** + * Normalizes optional fields and freezes the fact list. + */ + public ProposalGlance { + heading = Objects.requireNonNullElse(heading, ""); + facts = List.copyOf(Objects.requireNonNullElse(facts, List.of())); + intro = Objects.requireNonNullElse(intro, ""); + } + + /** + * Backward-compatible constructor for callers that predate the opening + * paragraph. + * + * @param heading the block's heading + * @param facts the facts the block sets + */ + public ProposalGlance(String heading, List facts) { + this(heading, facts, ""); + } + + /** + * One fact row: an icon, a quiet label, the value line, and an optional + * parenthetical note. + * + * @param icon the icon token of the fact + * @param label the quiet label above the value + * @param value the emphasized value line + * @param note the optional second value line; empty when the row has none + */ + public record Fact(String icon, String label, String value, String note) { + + /** + * Normalizes optional fields to empty strings. + */ + public Fact { + icon = Objects.requireNonNullElse(icon, ""); + label = Objects.requireNonNullElse(label, ""); + value = Objects.requireNonNullElse(value, ""); + note = Objects.requireNonNullElse(note, ""); + } + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/data/proposal/ProposalGoals.java b/templates/src/main/java/com/demcha/compose/document/templates/data/proposal/ProposalGoals.java new file mode 100644 index 000000000..7678f230c --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/data/proposal/ProposalGoals.java @@ -0,0 +1,59 @@ +package com.demcha.compose.document.templates.data.proposal; + +import java.util.List; +import java.util.Objects; + +/** + * The project-goals band of a structured proposal: an icon-badged heading + * and a horizontal run of icon-beside-text goal cells. + * + * @param heading the section heading + * @param icon the icon token of the section badge + * @param items the goal cells, in order + * @param intro the paragraph that opens the block, above its items; every + * one-page sales proposal measured for this sets one, most of + * them twice, and a block that opens straight into its list + * leaves it blank + */ +public record ProposalGoals(String heading, String icon, List items, + String intro) { + + /** + * Normalizes optional fields and freezes the goal list. + */ + public ProposalGoals { + heading = Objects.requireNonNullElse(heading, ""); + icon = Objects.requireNonNullElse(icon, ""); + items = List.copyOf(Objects.requireNonNullElse(items, List.of())); + intro = Objects.requireNonNullElse(intro, ""); + } + + /** + * Backward-compatible constructor for callers that predate the opening + * paragraph. + * + * @param heading the block's heading + * @param icon the mark the heading opens with + * @param items the block's items + */ + public ProposalGoals(String heading, String icon, List items) { + this(heading, icon, items, ""); + } + + /** + * One goal cell. + * + * @param icon the icon token of the goal + * @param text the goal statement + */ + public record Goal(String icon, String text) { + + /** + * Normalizes optional fields to empty strings. + */ + public Goal { + icon = Objects.requireNonNullElse(icon, ""); + text = Objects.requireNonNullElse(text, ""); + } + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/data/proposal/ProposalInvestment.java b/templates/src/main/java/com/demcha/compose/document/templates/data/proposal/ProposalInvestment.java new file mode 100644 index 000000000..5df8593a9 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/data/proposal/ProposalInvestment.java @@ -0,0 +1,78 @@ +package com.demcha.compose.document.templates.data.proposal; + +import java.util.List; +import java.util.Objects; + +/** + * The priced block of a structured proposal: an icon-badged heading, the + * authored table headers, the priced rows with their roles, and the total + * band. + * + *

    Distinct from the narrative model's {@link ProposalPricingRow}: a row + * here names its role through {@link Role} — presets are expected to set + * subtotal and optional rows apart — and the grand total is its own + * labelled band rather than the last row of the list.

    + * + * @param heading the section heading + * @param icon the icon token of the section badge + * @param itemHeader the header label of the item column + * @param amountHeader the header label of the amount column + * @param rows the priced rows, in order + * @param totalLabel the label of the total band + * @param totalAmount the amount of the total band + */ +public record ProposalInvestment( + String heading, + String icon, + String itemHeader, + String amountHeader, + List rows, + String totalLabel, + String totalAmount) { + + /** + * Normalizes optional fields and freezes the row list. + */ + public ProposalInvestment { + heading = Objects.requireNonNullElse(heading, ""); + icon = Objects.requireNonNullElse(icon, ""); + itemHeader = Objects.requireNonNullElse(itemHeader, ""); + amountHeader = Objects.requireNonNullElse(amountHeader, ""); + rows = List.copyOf(Objects.requireNonNullElse(rows, List.of())); + totalLabel = Objects.requireNonNullElse(totalLabel, ""); + totalAmount = Objects.requireNonNullElse(totalAmount, ""); + } + + /** + * One priced row. + * + * @param label the row label + * @param amount the row amount text + * @param role the row's role; {@code null} normalizes to + * {@link Role#NONE} + */ + public record Row(String label, String amount, Role role) { + + /** + * Normalizes optional fields. + */ + public Row { + label = Objects.requireNonNullElse(label, ""); + amount = Objects.requireNonNullElse(amount, ""); + role = Objects.requireNonNullElse(role, Role.NONE); + } + } + + /** + * The role of a priced row — named for what the row is, not for how it + * looks, so a theme change moves the styling without touching the data. + */ + public enum Role { + /** An ordinary priced row. */ + NONE, + /** A subtotal row; presets are expected to set it apart. */ + SUBTOTAL, + /** An optional line item; presets are expected to mark its amount. */ + OPTIONAL + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/data/proposal/ProposalMetaLine.java b/templates/src/main/java/com/demcha/compose/document/templates/data/proposal/ProposalMetaLine.java new file mode 100644 index 000000000..4a2131ff1 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/data/proposal/ProposalMetaLine.java @@ -0,0 +1,80 @@ +package com.demcha.compose.document.templates.data.proposal; + +import java.util.List; +import java.util.Objects; + +/** + * A proposal's header facts. + * + *

    Two designs' worth of shape, and they are not the same statement. A running + * header names three things by role — who it is for, who prepared it, when — and + * {@link #preparedFor()}, {@link #preparedBy()} and {@link #date()} carry those. + * A sales proposal instead sets a row of marked tiles whose captions are the + * document's own — a proposal number, a date, a validity, a team — and + * {@link #entries()} carries those.

    + * + *

    Neither derives from the other: turning three roles into tiles would mean + * inventing their captions, and reading roles back out of arbitrary tiles would + * mean guessing which is which. A preset draws the one its design has, and a + * document states the one it is written for.

    + * + * @param preparedFor who the proposal is for, as a running header states it + * @param preparedBy who prepared it + * @param date when it was prepared + * @param entries the marked tiles a sales proposal sets instead; empty when + * the design has none + */ +public record ProposalMetaLine(String preparedFor, String preparedBy, String date, + List entries) { + + /** + * Normalizes optional fields. + */ + public ProposalMetaLine { + preparedFor = Objects.requireNonNullElse(preparedFor, ""); + preparedBy = Objects.requireNonNullElse(preparedBy, ""); + date = Objects.requireNonNullElse(date, ""); + entries = List.copyOf(Objects.requireNonNullElse(entries, List.of())); + } + + /** + * Backward-compatible constructor for callers that predate the tiles. + * + * @param preparedFor who the proposal is for + * @param preparedBy who prepared it + * @param date when it was prepared + */ + public ProposalMetaLine(String preparedFor, String preparedBy, String date) { + this(preparedFor, preparedBy, date, List.of()); + } + + /** + * The tiles alone, for a design that sets no running header. + * + * @param entries the marked tiles, in order + */ + public ProposalMetaLine(List entries) { + this("", "", "", entries); + } + + /** + * One marked tile. + * + * @param icon the mark the tile opens with; the token means something only + * to the preset that packages it, and a preset that draws no + * marks ignores it. Blank when absent + * @param label the caption, as the sheet prints it + * @param value the fact itself + */ + public record Entry(String icon, String label, String value) { + + /** + * Normalizes optional fields. + */ + public Entry { + icon = Objects.requireNonNullElse(icon, ""); + label = Objects.requireNonNullElse(label, ""); + value = Objects.requireNonNullElse(value, ""); + } + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/data/proposal/ProposalPhaseGrid.java b/templates/src/main/java/com/demcha/compose/document/templates/data/proposal/ProposalPhaseGrid.java new file mode 100644 index 000000000..2ba78d402 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/data/proposal/ProposalPhaseGrid.java @@ -0,0 +1,70 @@ +package com.demcha.compose.document.templates.data.proposal; + +import java.util.List; +import java.util.Objects; + +/** + * The phase grid of a structured proposal: an icon-badged heading, the + * authored column headers, and one row per phase. + * + *

    Distinct from the narrative model's {@link ProposalTimelineItem}: a + * phase carries a number, a focus column and an output column, and the grid + * owns its header labels — the narrative timeline is a phase/duration/details + * triple with preset-owned headers.

    + * + *

    Unlike {@link ProposalInvestment}, whose two columns are fixed by the + * concept and therefore named fields, the phase grid's column set belongs + * to the preset that draws it — so the headers stay an authored list in + * column order. A preset validates that the list carries one label per + * column it renders; the data layer does not fix the count.

    + * + * @param heading the section heading + * @param icon the icon token of the section badge + * @param columnHeaders the grid's header labels, one per rendered column, + * in column order + * @param phases the phase rows, in order + */ +public record ProposalPhaseGrid( + String heading, + String icon, + List columnHeaders, + List phases) { + + /** + * Normalizes optional fields and freezes both lists. + */ + public ProposalPhaseGrid { + heading = Objects.requireNonNullElse(heading, ""); + icon = Objects.requireNonNullElse(icon, ""); + columnHeaders = List.copyOf(Objects.requireNonNullElse(columnHeaders, List.of())); + phases = List.copyOf(Objects.requireNonNullElse(phases, List.of())); + } + + /** + * One phase row of the grid. + * + * @param number the phase number, as authored (e.g. {@code "1"}) + * @param name the phase name + * @param focus what the phase concentrates on + * @param duration the phase duration text + * @param output what the phase delivers + */ + public record Phase( + String number, + String name, + String focus, + String duration, + String output) { + + /** + * Normalizes optional fields to empty strings. + */ + public Phase { + number = Objects.requireNonNullElse(number, ""); + name = Objects.requireNonNullElse(name, ""); + focus = Objects.requireNonNullElse(focus, ""); + duration = Objects.requireNonNullElse(duration, ""); + output = Objects.requireNonNullElse(output, ""); + } + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/data/proposal/ProposalRecipient.java b/templates/src/main/java/com/demcha/compose/document/templates/data/proposal/ProposalRecipient.java new file mode 100644 index 000000000..4b0a58e27 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/data/proposal/ProposalRecipient.java @@ -0,0 +1,39 @@ +package com.demcha.compose.document.templates.data.proposal; + +import java.util.List; +import java.util.Objects; + +/** + * The organisation a proposal is addressed to. + * + *

    Distinct from {@link ProposalMetaLine#preparedFor()}, which is one line of + * a header strip: this is the addressed block a sales proposal opens with — a + * caption, the company, and its address — and a design that prints it needs all + * three separately rather than as one string.

    + * + * @param label the caption above the block, as the sheet prints it + * (e.g. {@code "PREPARED FOR"}); blank when the design draws + * the block without one + * @param name the addressed organisation + * @param addressLines the address, in the order it is printed + */ +public record ProposalRecipient(String label, String name, List addressLines) { + + /** + * Normalizes optional fields. + */ + public ProposalRecipient { + label = Objects.requireNonNullElse(label, ""); + name = Objects.requireNonNullElse(name, ""); + addressLines = List.copyOf(Objects.requireNonNullElse(addressLines, List.of())); + } + + /** + * Whether there is anything to draw. + * + * @return {@code true} when the block names an organisation or an address + */ + public boolean isPresent() { + return !name.isBlank() || !addressLines.isEmpty(); + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/data/proposal/ProposalScope.java b/templates/src/main/java/com/demcha/compose/document/templates/data/proposal/ProposalScope.java new file mode 100644 index 000000000..20e35f428 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/data/proposal/ProposalScope.java @@ -0,0 +1,64 @@ +package com.demcha.compose.document.templates.data.proposal; + +import java.util.List; +import java.util.Objects; + +/** + * The numbered scope-of-work list of a structured proposal. + * + *

    Distinct from the narrative model's {@link ProposalSection}: a scope + * item is a numbered title-plus-description row, not a titled run of + * paragraphs.

    + * + * @param heading the section heading + * @param icon the icon token of the section badge + * @param items the numbered scope rows, in order + * @param intro the paragraph that opens the block, above its items; every + * one-page sales proposal measured for this sets one, most of + * them twice, and a block that opens straight into its list + * leaves it blank + */ +public record ProposalScope(String heading, String icon, List items, + String intro) { + + /** + * Normalizes optional fields and freezes the item list. + */ + public ProposalScope { + heading = Objects.requireNonNullElse(heading, ""); + icon = Objects.requireNonNullElse(icon, ""); + items = List.copyOf(Objects.requireNonNullElse(items, List.of())); + intro = Objects.requireNonNullElse(intro, ""); + } + + /** + * Backward-compatible constructor for callers that predate the opening + * paragraph. + * + * @param heading the block's heading + * @param icon the mark the heading opens with + * @param items the block's items + */ + public ProposalScope(String heading, String icon, List items) { + this(heading, icon, items, ""); + } + + /** + * One numbered scope row. + * + * @param number the step number, as authored (e.g. {@code "01"}) + * @param title the step title + * @param description the step description + */ + public record Item(String number, String title, String description) { + + /** + * Normalizes optional fields to empty strings. + */ + public Item { + number = Objects.requireNonNullElse(number, ""); + title = Objects.requireNonNullElse(title, ""); + description = Objects.requireNonNullElse(description, ""); + } + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/data/proposal/ProposalSummaryBlock.java b/templates/src/main/java/com/demcha/compose/document/templates/data/proposal/ProposalSummaryBlock.java new file mode 100644 index 000000000..0a713576d --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/data/proposal/ProposalSummaryBlock.java @@ -0,0 +1,28 @@ +package com.demcha.compose.document.templates.data.proposal; + +import java.util.List; +import java.util.Objects; + +/** + * The executive-summary block of a structured proposal: an icon-badged + * heading and its body paragraphs. + * + *

    The narrative model's {@link ProposalData#executiveSummary()} is one + * paragraph with a preset-owned heading; this block owns its heading, its + * icon token, and as many paragraphs as the author wrote.

    + * + * @param heading the section heading, as authored + * @param icon the icon token of the section badge + * @param paragraphs the summary paragraphs, in order + */ +public record ProposalSummaryBlock(String heading, String icon, List paragraphs) { + + /** + * Normalizes optional fields and freezes the paragraph list. + */ + public ProposalSummaryBlock { + heading = Objects.requireNonNullElse(heading, ""); + icon = Objects.requireNonNullElse(icon, ""); + paragraphs = List.copyOf(Objects.requireNonNullElse(paragraphs, List.of())); + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/data/proposal/ProposalTermsBlock.java b/templates/src/main/java/com/demcha/compose/document/templates/data/proposal/ProposalTermsBlock.java new file mode 100644 index 000000000..28bcfaa92 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/data/proposal/ProposalTermsBlock.java @@ -0,0 +1,27 @@ +package com.demcha.compose.document.templates.data.proposal; + +import java.util.List; +import java.util.Objects; + +/** + * The bulleted terms block of a structured proposal. + * + *

    Distinct from the narrative model's {@link ProposalData#acceptanceTerms()}: + * this block owns its heading and icon and is its own section, not part of + * the acceptance card.

    + * + * @param heading the section heading + * @param icon the icon token of the section badge + * @param items the term bullets, in order + */ +public record ProposalTermsBlock(String heading, String icon, List items) { + + /** + * Normalizes optional fields and freezes the item list. + */ + public ProposalTermsBlock { + heading = Objects.requireNonNullElse(heading, ""); + icon = Objects.requireNonNullElse(icon, ""); + items = List.copyOf(Objects.requireNonNullElse(items, List.of())); + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/data/proposal/ProposalTitleLines.java b/templates/src/main/java/com/demcha/compose/document/templates/data/proposal/ProposalTitleLines.java new file mode 100644 index 000000000..69f3b2d18 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/data/proposal/ProposalTitleLines.java @@ -0,0 +1,98 @@ +package com.demcha.compose.document.templates.data.proposal; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +/** + * A proposal's title, as the sheet sets it. + * + *

    Designs break a title across a different number of lines — two, three or + * four — and some set a short phrase above it and a standfirst under it. The + * lines are therefore a list, and {@link #lead()}, {@link #second()} and + * {@link #third()} are the first three of that list: one statement, two ways of + * reading it, kept consistent by construction rather than by the caller.

    + * + *

    A title breaks where the design breaks it. The lines are the document's, + * not a paragraph the engine wraps, because where a headline turns is a + * typographic decision and not a consequence of the column it happens to sit + * in.

    + * + * @param lead the first line, or blank when the title has none + * @param second the second line, or blank + * @param third the third line, or blank + * @param eyebrow the short phrase some designs set above the title (e.g. + * {@code "Proposal for"}); blank when the title carries it + * inside its own first line instead + * @param lines every line of the title, in order — the canonical reading + * @param standfirst the paragraph under the title, one entry per printed line; + * empty when the design sets none + */ +public record ProposalTitleLines(String lead, String second, String third, + String eyebrow, List lines, + List standfirst) { + + /** + * Normalizes optional fields and keeps the list and the first three lines + * saying the same thing. + * + *

    Given lines, the three are read from it. Given no lines, the lines are + * those of the three that are set. Given both — which only the canonical + * constructor allows, and which neither convenience form can produce — the + * list wins and the three are read from it, because the list is the one that + * can hold a title of any length and reading it back is the only resolution + * that loses nothing the caller stated.

    + */ + public ProposalTitleLines { + eyebrow = Objects.requireNonNullElse(eyebrow, ""); + standfirst = List.copyOf(Objects.requireNonNullElse(standfirst, List.of())); + List stated = Objects.requireNonNullElse(lines, List.of()); + if (stated.isEmpty()) { + lead = Objects.requireNonNullElse(lead, ""); + second = Objects.requireNonNullElse(second, ""); + third = Objects.requireNonNullElse(third, ""); + List derived = new ArrayList<>(); + for (String line : List.of(lead, second, third)) { + if (!line.isBlank()) { + derived.add(line); + } + } + lines = List.copyOf(derived); + } else { + lines = List.copyOf(stated); + lead = lines.size() > 0 ? lines.get(0) : ""; + second = lines.size() > 1 ? lines.get(1) : ""; + third = lines.size() > 2 ? lines.get(2) : ""; + } + } + + /** + * A title of up to three lines. + * + * @param lead the first line + * @param second the second line + * @param third the third line + */ + public ProposalTitleLines(String lead, String second, String third) { + this(lead, second, third, "", List.of(), List.of()); + } + + /** + * A title of any number of lines, with the phrase above it and the paragraph + * under it. + * + *

    A factory rather than a second three-argument constructor: one taking + * {@code (String, List, List)} beside one taking three strings is ambiguous + * for a caller passing nulls, and a title is not the place to make someone + * cast an argument to find out which they meant.

    + * + * @param eyebrow the phrase above the title, or blank + * @param lines every line of the title, in order + * @param standfirst the paragraph under it, one entry per printed line + * @return the title + */ + public static ProposalTitleLines of(String eyebrow, List lines, + List standfirst) { + return new ProposalTitleLines("", "", "", eyebrow, lines, standfirst); + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/data/proposal/StructuredProposalData.java b/templates/src/main/java/com/demcha/compose/document/templates/data/proposal/StructuredProposalData.java new file mode 100644 index 000000000..4631e05ef --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/data/proposal/StructuredProposalData.java @@ -0,0 +1,330 @@ +package com.demcha.compose.document.templates.data.proposal; + +/** + * Display-oriented input for structured proposal documents. + * + *

    The narrative model ({@link ProposalData}) is a titled run of prose + * sections; this model is the structured business proposal: brand marks, an + * authored multi-line title, an at-a-glance fact card, goal cells, a + * numbered scope list, two deliverable columns, a phase grid, a priced + * investment block with row roles, a terms block, and a signing card. The + * sections own their headings and icon tokens (the glance card carries a + * heading only — its icons live on the individual facts), so the section + * wording is content, not a preset choice.

    + * + *

    Both models stay: a preset consumes the one whose shape it renders. + * Every component normalizes {@code null} to its empty form, so a partial + * document composes without null checks in preset code. The section records + * construct positionally; this builder is where a document is assembled.

    + * + * @param brand the sender's brand marks + * @param title the authored title lines + * @param meta the prepared-for / prepared-by / date line + * @param executiveSummary the executive-summary block + * @param glance the at-a-glance fact card + * @param goals the project-goals band + * @param scope the numbered scope-of-work list + * @param deliverables the two-column deliverables band + * @param timeline the phase grid (the structured counterpart of the + * narrative model's {@code timeline()}) + * @param investment the priced block + * @param terms the bulleted terms block + * @param acceptance the signing card + * @param recipient the addressed organisation, for the designs that open + * with one; blank when the design names it in its header + * line instead + * @param attention the person at the recipient the proposal is addressed + * to; blank when the design addresses no one + * @param footer the issuer's own identity as the foot states it; + * blank when the design's foot carries only a name + */ +public record StructuredProposalData( + ProposalBrand brand, + ProposalTitleLines title, + ProposalMetaLine meta, + ProposalSummaryBlock executiveSummary, + ProposalGlance glance, + ProposalGoals goals, + ProposalScope scope, + ProposalDeliverables deliverables, + ProposalPhaseGrid timeline, + ProposalInvestment investment, + ProposalTermsBlock terms, + ProposalAcceptance acceptance, + ProposalRecipient recipient, + ProposalAttention attention, + ProposalFooter footer) { + + /** + * Normalizes absent components to their empty forms. + */ + public StructuredProposalData { + brand = brand == null + ? new ProposalBrand(null, null, null, null, null, null) : brand; + title = title == null + ? new ProposalTitleLines(null, null, null) : title; + meta = meta == null + ? new ProposalMetaLine(null, null, null) : meta; + executiveSummary = executiveSummary == null + ? new ProposalSummaryBlock(null, null, null) : executiveSummary; + glance = glance == null + ? new ProposalGlance(null, null) : glance; + goals = goals == null + ? new ProposalGoals(null, null, null) : goals; + scope = scope == null + ? new ProposalScope(null, null, null) : scope; + deliverables = deliverables == null + ? new ProposalDeliverables(null, null, null, null) : deliverables; + timeline = timeline == null + ? new ProposalPhaseGrid(null, null, null, null) : timeline; + investment = investment == null + ? new ProposalInvestment(null, null, null, null, null, null, null) : investment; + terms = terms == null + ? new ProposalTermsBlock(null, null, null) : terms; + acceptance = acceptance == null + ? new ProposalAcceptance(null, null, null, null) : acceptance; + recipient = recipient == null + ? new ProposalRecipient(null, null, null) : recipient; + attention = attention == null + ? new ProposalAttention(null, null, null, null, null) : attention; + footer = footer == null + ? new ProposalFooter(null, null, null, null) : footer; + } + + /** + * Backward-compatible constructor for callers that predate the addressed + * block, the attention line and the foot's own identity. + * + * @param brand the issuing brand + * @param title the proposal's title + * @param meta the header facts + * @param executiveSummary the summary block + * @param glance the at-a-glance facts + * @param goals the goals block + * @param scope the scope block + * @param deliverables the deliverables block + * @param timeline the phase grid + * @param investment the investment table + * @param terms the terms block + * @param acceptance the signing card + */ + public StructuredProposalData(ProposalBrand brand, ProposalTitleLines title, + ProposalMetaLine meta, + ProposalSummaryBlock executiveSummary, + ProposalGlance glance, ProposalGoals goals, + ProposalScope scope, ProposalDeliverables deliverables, + ProposalPhaseGrid timeline, ProposalInvestment investment, + ProposalTermsBlock terms, ProposalAcceptance acceptance) { + this(brand, title, meta, executiveSummary, glance, goals, scope, deliverables, + timeline, investment, terms, acceptance, null, null, null); + } + + /** + * Starts a fluent structured proposal data builder. + * + * @return structured proposal data builder + */ + public static Builder builder() { + return new Builder(); + } + + /** + * Fluent builder for complete structured proposal content. + */ + public static final class Builder { + private ProposalBrand brand; + private ProposalTitleLines title; + private ProposalMetaLine meta; + private ProposalSummaryBlock executiveSummary; + private ProposalGlance glance; + private ProposalGoals goals; + private ProposalScope scope; + private ProposalDeliverables deliverables; + private ProposalPhaseGrid timeline; + private ProposalInvestment investment; + private ProposalTermsBlock terms; + private ProposalAcceptance acceptance; + private ProposalRecipient recipient; + private ProposalAttention attention; + private ProposalFooter footer; + + private Builder() { + } + + /** + * Sets the sender's brand marks. + * + * @param brand brand marks + * @return this builder + */ + public Builder brand(ProposalBrand brand) { + this.brand = brand; + return this; + } + + /** + * Sets the authored title lines. + * + * @param title title lines + * @return this builder + */ + public Builder title(ProposalTitleLines title) { + this.title = title; + return this; + } + + /** + * Sets the prepared-for / prepared-by / date line. + * + * @param meta meta line + * @return this builder + */ + public Builder meta(ProposalMetaLine meta) { + this.meta = meta; + return this; + } + + /** + * Sets the executive-summary block. + * + * @param executiveSummary summary block + * @return this builder + */ + public Builder executiveSummary(ProposalSummaryBlock executiveSummary) { + this.executiveSummary = executiveSummary; + return this; + } + + /** + * Sets the at-a-glance fact card. + * + * @param glance fact card + * @return this builder + */ + public Builder glance(ProposalGlance glance) { + this.glance = glance; + return this; + } + + /** + * Sets the project-goals band. + * + * @param goals goals band + * @return this builder + */ + public Builder goals(ProposalGoals goals) { + this.goals = goals; + return this; + } + + /** + * Sets the numbered scope-of-work list. + * + * @param scope scope list + * @return this builder + */ + public Builder scope(ProposalScope scope) { + this.scope = scope; + return this; + } + + /** + * Sets the two-column deliverables band. + * + * @param deliverables deliverables band + * @return this builder + */ + public Builder deliverables(ProposalDeliverables deliverables) { + this.deliverables = deliverables; + return this; + } + + /** + * Sets the phase grid. + * + * @param timeline phase grid + * @return this builder + */ + public Builder timeline(ProposalPhaseGrid timeline) { + this.timeline = timeline; + return this; + } + + /** + * Sets the priced block. + * + * @param investment priced block + * @return this builder + */ + public Builder investment(ProposalInvestment investment) { + this.investment = investment; + return this; + } + + /** + * Sets the bulleted terms block. + * + * @param terms terms block + * @return this builder + */ + public Builder terms(ProposalTermsBlock terms) { + this.terms = terms; + return this; + } + + /** + * Sets the signing card. + * + * @param acceptance signing card + * @return this builder + */ + public Builder acceptance(ProposalAcceptance acceptance) { + this.acceptance = acceptance; + return this; + } + + /** + * Sets the addressed organisation. + * + * @param recipient the addressed organisation + * @return this builder + */ + public Builder recipient(ProposalRecipient recipient) { + this.recipient = recipient; + return this; + } + + /** + * Sets the person at the recipient the proposal is addressed to. + * + * @param attention the addressed person + * @return this builder + */ + public Builder attention(ProposalAttention attention) { + this.attention = attention; + return this; + } + + /** + * Sets the issuer's own identity, as the foot states it. + * + * @param footer the issuer's identity + * @return this builder + */ + public Builder footer(ProposalFooter footer) { + this.footer = footer; + return this; + } + + /** + * Builds the normalized structured proposal data. + * + * @return structured proposal data + */ + public StructuredProposalData build() { + return new StructuredProposalData(brand, title, meta, executiveSummary, + glance, goals, scope, deliverables, timeline, investment, + terms, acceptance, recipient, attention, footer); + } + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/data/proposal/StructuredProposalDocumentSpec.java b/templates/src/main/java/com/demcha/compose/document/templates/data/proposal/StructuredProposalDocumentSpec.java new file mode 100644 index 000000000..72384d5c2 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/data/proposal/StructuredProposalDocumentSpec.java @@ -0,0 +1,34 @@ +package com.demcha.compose.document.templates.data.proposal; + +/** + * Public compose-first input for structured proposal templates. + * + *

    Authoring role: the document-level object structured proposal + * presets are parameterised on, the way narrative presets are parameterised + * on {@link ProposalDocumentSpec}. Presets that render the structured shape + * — brand marks, fact card, goal cells, phase grid, priced block, signing + * card — consume this spec; presets that render prose sections stay on the + * narrative one.

    + * + * @param proposal normalized structured proposal content + */ +public record StructuredProposalDocumentSpec(StructuredProposalData proposal) { + + /** + * Creates a normalized structured proposal document spec. + */ + public StructuredProposalDocumentSpec { + proposal = proposal == null ? StructuredProposalData.builder().build() : proposal; + } + + /** + * Wraps existing structured proposal data in the document-level spec + * expected by structured proposal templates. + * + * @param proposal structured proposal data + * @return document spec + */ + public static StructuredProposalDocumentSpec from(StructuredProposalData proposal) { + return new StructuredProposalDocumentSpec(proposal); + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/data/proposal/package-info.java b/templates/src/main/java/com/demcha/compose/document/templates/data/proposal/package-info.java index 81af11751..62ae78a5d 100644 --- a/templates/src/main/java/com/demcha/compose/document/templates/data/proposal/package-info.java +++ b/templates/src/main/java/com/demcha/compose/document/templates/data/proposal/package-info.java @@ -1,5 +1,15 @@ /** * Shared, render-neutral proposal document specs and supporting data * records, consumed by the layered {@code proposal.presets} presets. + * + *

    Two document models live here, and a preset consumes the one whose + * shape it renders: the narrative model + * ({@link com.demcha.compose.document.templates.data.proposal.ProposalDocumentSpec} + * — a titled run of prose sections with a flat timeline and pricing list) + * and the structured model + * ({@link com.demcha.compose.document.templates.data.proposal.StructuredProposalDocumentSpec} + * — brand marks, fact card, goal cells, numbered scope, phase grid, priced + * block with row roles, and a signing card, the sections owning their + * headings and icon tokens).

    */ package com.demcha.compose.document.templates.data.proposal; diff --git a/templates/src/main/java/com/demcha/compose/document/templates/data/receipt/ReceiptData.java b/templates/src/main/java/com/demcha/compose/document/templates/data/receipt/ReceiptData.java new file mode 100644 index 000000000..3ee04ef0e --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/data/receipt/ReceiptData.java @@ -0,0 +1,418 @@ +package com.demcha.compose.document.templates.data.receipt; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.function.Consumer; + +/** + * Display-oriented payment receipt input — a transfer confirmation, a direct + * debit advice, a card payment receipt. + * + *

    Everything here is display text the issuing system already decided. + * Amounts arrive formatted with their currency, dates arrive in the locale + * the issuer prints them in, and account identifiers arrive masked or not as + * the issuer chose. A receipt is a restatement of a settled fact, so the + * template's job is to lay that fact out, never to compute or reformat + * it.

    + * + *

    The optional blocks — timeline, notes, support lines, verification URL + * — are what separates a receipt somebody keeps from one that only proves a + * number. Leave any of them empty and the preset drops the block rather than + * rendering an empty heading.

    + * + * @param documentTitle heading of the document, for example + * {@code Transfer confirmation} + * @param issuerName the institution that issued the receipt + * @param generatedOn when this copy was produced, already formatted + * @param reference the issuer's own reference for the payment, shown + * beside the title + * @param amountLabel caption above the amount, for example + * {@code Amount sent} + * @param amount the amount, formatted with its currency + * @param amountCaption one line under the amount naming the instrument or + * the counterparty + * @param status the payment's status and tone + * @param summaryFields the two-to-four facts shown beside the amount + * (value date, operation date, scheme) + * @param payerLabel caption over the paying side, for example + * {@code From} + * @param payer the paying side + * @param beneficiaryLabel caption over the receiving side, for example + * {@code To} + * @param beneficiary the receiving side + * @param detailGroups titled blocks of label/value rows + * @param timeline the payment's steps, oldest first; empty to omit + * @param notes free-text notes shown under the details + * @param verificationUrl URL encoded into the footer QR code; empty to omit + * @param verificationText one line explaining what the QR code leads to + * @param supportLines how to reach the issuer, one entry per line + * @param legalNote the small print block at the foot of the page + */ +public record ReceiptData( + String documentTitle, + String issuerName, + String generatedOn, + String reference, + String amountLabel, + String amount, + String amountCaption, + ReceiptStatus status, + List summaryFields, + String payerLabel, + ReceiptParty payer, + String beneficiaryLabel, + ReceiptParty beneficiary, + List detailGroups, + List timeline, + List notes, + String verificationUrl, + String verificationText, + List supportLines, + String legalNote) { + + /** + * Normalizes optional text, defaults the two party captions, and freezes + * every collection input. + */ + public ReceiptData { + documentTitle = Objects.requireNonNullElse(documentTitle, "Payment receipt"); + issuerName = Objects.requireNonNullElse(issuerName, ""); + generatedOn = Objects.requireNonNullElse(generatedOn, ""); + reference = Objects.requireNonNullElse(reference, ""); + amountLabel = Objects.requireNonNullElse(amountLabel, "Amount"); + amount = Objects.requireNonNullElse(amount, ""); + amountCaption = Objects.requireNonNullElse(amountCaption, ""); + status = Objects.requireNonNullElse(status, new ReceiptStatus("", null)); + summaryFields = List.copyOf(Objects.requireNonNullElse(summaryFields, List.of())); + payerLabel = blankTo(payerLabel, "From"); + beneficiaryLabel = blankTo(beneficiaryLabel, "To"); + detailGroups = List.copyOf(Objects.requireNonNullElse(detailGroups, List.of())); + timeline = List.copyOf(Objects.requireNonNullElse(timeline, List.of())); + notes = List.copyOf(Objects.requireNonNullElse(notes, List.of())); + verificationUrl = Objects.requireNonNullElse(verificationUrl, ""); + verificationText = Objects.requireNonNullElse(verificationText, ""); + supportLines = List.copyOf(Objects.requireNonNullElse(supportLines, List.of())); + legalNote = Objects.requireNonNullElse(legalNote, ""); + } + + private static String blankTo(String value, String fallback) { + return value == null || value.isBlank() ? fallback : value; + } + + /** + * Reports whether a paying side was supplied. + * + * @return {@code true} when the payer carries a name or any row + */ + public boolean hasPayer() { + return isPresent(payer); + } + + /** + * Reports whether a receiving side was supplied. + * + * @return {@code true} when the beneficiary carries a name or any row + */ + public boolean hasBeneficiary() { + return isPresent(beneficiary); + } + + private static boolean isPresent(ReceiptParty party) { + return party != null + && (!party.name().isBlank() || !party.fields().isEmpty() || !party.addressLines().isEmpty()); + } + + /** + * Starts a fluent receipt data builder. + * + * @return receipt data builder + */ + public static Builder builder() { + return new Builder(); + } + + /** + * Fluent builder for complete receipt content. + */ + public static final class Builder { + private String documentTitle; + private String issuerName; + private String generatedOn; + private String reference; + private String amountLabel; + private String amount; + private String amountCaption; + private ReceiptStatus status; + private final List summaryFields = new ArrayList<>(); + private String payerLabel; + private ReceiptParty payer; + private String beneficiaryLabel; + private ReceiptParty beneficiary; + private final List detailGroups = new ArrayList<>(); + private final List timeline = new ArrayList<>(); + private final List notes = new ArrayList<>(); + private String verificationUrl; + private String verificationText; + private final List supportLines = new ArrayList<>(); + private String legalNote; + + private Builder() { + } + + /** + * Sets the document heading. + * + * @param documentTitle heading text + * @return this builder + */ + public Builder documentTitle(String documentTitle) { + this.documentTitle = documentTitle; + return this; + } + + /** + * Sets the issuing institution's name. + * + * @param issuerName issuer name + * @return this builder + */ + public Builder issuerName(String issuerName) { + this.issuerName = issuerName; + return this; + } + + /** + * Sets when this copy was produced. + * + * @param generatedOn formatted generation date + * @return this builder + */ + public Builder generatedOn(String generatedOn) { + this.generatedOn = generatedOn; + return this; + } + + /** + * Sets the issuer's reference for the payment. + * + * @param reference reference text + * @return this builder + */ + public Builder reference(String reference) { + this.reference = reference; + return this; + } + + /** + * Sets the hero amount and the caption above it. + * + * @param label caption above the amount + * @param amount amount formatted with its currency + * @return this builder + */ + public Builder amount(String label, String amount) { + this.amountLabel = label; + this.amount = amount; + return this; + } + + /** + * Sets the line under the amount. + * + * @param amountCaption instrument or counterparty line + * @return this builder + */ + public Builder amountCaption(String amountCaption) { + this.amountCaption = amountCaption; + return this; + } + + /** + * Sets the payment status. + * + * @param status status label and tone + * @return this builder + */ + public Builder status(ReceiptStatus status) { + this.status = status; + return this; + } + + /** + * Sets the payment status from a label and a tone. + * + * @param label the issuer's status word + * @param tone how that word reads + * @return this builder + */ + public Builder status(String label, ReceiptStatusTone tone) { + return status(new ReceiptStatus(label, tone)); + } + + /** + * Appends a fact shown beside the amount. + * + * @param label field caption + * @param value field value + * @return this builder + */ + public Builder summaryField(String label, String value) { + summaryFields.add(ReceiptField.of(label, value)); + return this; + } + + /** + * Sets the paying side. + * + * @param label caption over the block + * @param spec party builder callback + * @return this builder + */ + public Builder payer(String label, Consumer spec) { + this.payerLabel = label; + this.payer = build(spec); + return this; + } + + /** + * Sets the receiving side. + * + * @param label caption over the block + * @param spec party builder callback + * @return this builder + */ + public Builder beneficiary(String label, Consumer spec) { + this.beneficiaryLabel = label; + this.beneficiary = build(spec); + return this; + } + + private static ReceiptParty build(Consumer spec) { + ReceiptParty.Builder builder = ReceiptParty.builder(); + if (spec != null) { + spec.accept(builder); + } + return builder.build(); + } + + /** + * Appends a titled block of label/value rows. + * + * @param title group heading + * @param spec group builder callback + * @return this builder + */ + public Builder detailGroup(String title, Consumer spec) { + ReceiptFieldGroup.Builder builder = ReceiptFieldGroup.builder(title); + if (spec != null) { + spec.accept(builder); + } + detailGroups.add(builder.build()); + return this; + } + + /** + * Appends a prepared field group. + * + * @param group field group + * @return this builder + */ + public Builder addDetailGroup(ReceiptFieldGroup group) { + detailGroups.add(Objects.requireNonNull(group, "group")); + return this; + } + + /** + * Appends a step to the payment timeline. + * + * @param label step name + * @param timestamp formatted timestamp + * @param detail optional explanation + * @return this builder + */ + public Builder event(String label, String timestamp, String detail) { + timeline.add(new ReceiptEvent(label, timestamp, detail)); + return this; + } + + /** + * Appends a note shown under the detail groups. + * + * @param note note text + * @return this builder + */ + public Builder note(String note) { + if (note != null && !note.isBlank()) { + notes.add(note); + } + return this; + } + + /** + * Sets the footer QR code target and the line that explains it. + * + * @param url URL encoded into the QR code + * @param text one-line explanation printed beside it + * @return this builder + */ + public Builder verification(String url, String text) { + this.verificationUrl = url; + this.verificationText = text; + return this; + } + + /** + * Appends a support contact line. + * + * @param line contact line + * @return this builder + */ + public Builder supportLine(String line) { + if (line != null && !line.isBlank()) { + supportLines.add(line); + } + return this; + } + + /** + * Sets the small-print block at the foot of the page. + * + * @param legalNote legal text + * @return this builder + */ + public Builder legalNote(String legalNote) { + this.legalNote = legalNote; + return this; + } + + /** + * Builds immutable receipt data. + * + * @return receipt data + */ + public ReceiptData build() { + return new ReceiptData( + documentTitle, + issuerName, + generatedOn, + reference, + amountLabel, + amount, + amountCaption, + status, + summaryFields, + payerLabel, + payer, + beneficiaryLabel, + beneficiary, + detailGroups, + timeline, + notes, + verificationUrl, + verificationText, + supportLines, + legalNote); + } + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/data/receipt/ReceiptDocumentSpec.java b/templates/src/main/java/com/demcha/compose/document/templates/data/receipt/ReceiptDocumentSpec.java new file mode 100644 index 000000000..a68fb3ce1 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/data/receipt/ReceiptDocumentSpec.java @@ -0,0 +1,54 @@ +package com.demcha.compose.document.templates.data.receipt; + +import java.util.function.Consumer; + +/** + * Public compose-first payment receipt input. + * + *

    Authoring role: gives callers one document-level object to pass + * to receipt templates, the same way {@code InvoiceDocumentSpec} does for + * invoices.

    + * + *

    Unlike the invoice spec this one does not re-declare the payload's + * builder methods. A receipt spec carries exactly one payload and + * {@link ReceiptData#builder()} is already its fluent surface, so mirroring + * twenty setters here would add a second place to keep in step for no + * additional reach — {@link #of(Consumer)} gives the same one-expression + * authoring.

    + * + * @param receipt normalized receipt content rendered by receipt templates + */ +public record ReceiptDocumentSpec(ReceiptData receipt) { + + /** + * Creates a normalized receipt document spec. + */ + public ReceiptDocumentSpec { + receipt = receipt == null ? ReceiptData.builder().build() : receipt; + } + + /** + * Wraps existing receipt data in the document-level spec expected by + * receipt templates. + * + * @param receipt receipt data + * @return document spec + */ + public static ReceiptDocumentSpec from(ReceiptData receipt) { + return new ReceiptDocumentSpec(receipt); + } + + /** + * Builds receipt data inline and wraps it. + * + * @param spec receipt data builder callback + * @return document spec + */ + public static ReceiptDocumentSpec of(Consumer spec) { + ReceiptData.Builder builder = ReceiptData.builder(); + if (spec != null) { + spec.accept(builder); + } + return new ReceiptDocumentSpec(builder.build()); + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/data/receipt/ReceiptEvent.java b/templates/src/main/java/com/demcha/compose/document/templates/data/receipt/ReceiptEvent.java new file mode 100644 index 000000000..4db422016 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/data/receipt/ReceiptEvent.java @@ -0,0 +1,38 @@ +package com.demcha.compose.document.templates.data.receipt; + +import java.util.Objects; + +/** + * One step in a payment's life — {@code Instructed}, {@code Sent to the + * clearing scheme}, {@code Settled}. + * + *

    The step a payment reached is the question a support agent is asked + * about a receipt, and the source document usually answers it with a single + * status word. Carrying the steps lets a template show the whole run.

    + * + * @param label step name + * @param timestamp when the step happened, already formatted for display + * @param detail optional one-line explanation shown under the step + */ +public record ReceiptEvent(String label, String timestamp, String detail) { + + /** + * Normalizes every field to non-null display text. + */ + public ReceiptEvent { + label = Objects.requireNonNullElse(label, ""); + timestamp = Objects.requireNonNullElse(timestamp, ""); + detail = Objects.requireNonNullElse(detail, ""); + } + + /** + * Creates a step with no explanatory line. + * + * @param label step name + * @param timestamp formatted timestamp + * @return receipt event + */ + public static ReceiptEvent of(String label, String timestamp) { + return new ReceiptEvent(label, timestamp, ""); + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/data/receipt/ReceiptField.java b/templates/src/main/java/com/demcha/compose/document/templates/data/receipt/ReceiptField.java new file mode 100644 index 000000000..cd3c585e3 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/data/receipt/ReceiptField.java @@ -0,0 +1,49 @@ +package com.demcha.compose.document.templates.data.receipt; + +import java.util.Objects; + +/** + * One label/value pair on a payment receipt — {@code Value date → 07 Jul 2026}, + * {@code Sort code → 04-00-75}. + * + *

    The value is display text, already formatted by the caller: a receipt + * restates what a payment system decided, so the template never parses an + * amount, a date, or an account number, and never reformats one.

    + * + * @param label field caption shown in the left column + * @param value field value shown in the right column + * @param emphasized whether the value renders in the emphasised weight — + * used for the one field per group a reader looks for first + */ +public record ReceiptField(String label, String value, boolean emphasized) { + + /** + * Normalizes both sides to non-null display text. + */ + public ReceiptField { + label = Objects.requireNonNullElse(label, ""); + value = Objects.requireNonNullElse(value, ""); + } + + /** + * Creates a plain field. + * + * @param label field caption + * @param value field value + * @return an unemphasised field + */ + public static ReceiptField of(String label, String value) { + return new ReceiptField(label, value, false); + } + + /** + * Creates a field whose value renders in the emphasised weight. + * + * @param label field caption + * @param value field value + * @return an emphasised field + */ + public static ReceiptField emphasized(String label, String value) { + return new ReceiptField(label, value, true); + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/data/receipt/ReceiptFieldGroup.java b/templates/src/main/java/com/demcha/compose/document/templates/data/receipt/ReceiptFieldGroup.java new file mode 100644 index 000000000..4bd5da2d5 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/data/receipt/ReceiptFieldGroup.java @@ -0,0 +1,93 @@ +package com.demcha.compose.document.templates.data.receipt; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +/** + * A titled block of {@link ReceiptField} rows — {@code Transfer details}, + * {@code Beneficiary details}, {@code Fees and charges}. + * + *

    A receipt is a sequence of these plus one amount; giving the group its + * own record is what lets a caller add a block the template has never heard + * of without touching the preset.

    + * + * @param title group heading + * @param fields rows inside the group + */ +public record ReceiptFieldGroup(String title, List fields) { + + /** + * Normalizes the title and freezes the row list. + */ + public ReceiptFieldGroup { + title = Objects.requireNonNullElse(title, ""); + fields = List.copyOf(Objects.requireNonNullElse(fields, List.of())); + } + + /** + * Starts a fluent group builder. + * + * @param title group heading + * @return group builder + */ + public static Builder builder(String title) { + return new Builder(title); + } + + /** + * Fluent builder for a titled field group. + */ + public static final class Builder { + private final String title; + private final List fields = new ArrayList<>(); + + private Builder(String title) { + this.title = title; + } + + /** + * Appends a plain row. + * + * @param label field caption + * @param value field value + * @return this builder + */ + public Builder field(String label, String value) { + fields.add(ReceiptField.of(label, value)); + return this; + } + + /** + * Appends a row whose value renders in the emphasised weight. + * + * @param label field caption + * @param value field value + * @return this builder + */ + public Builder emphasized(String label, String value) { + fields.add(ReceiptField.emphasized(label, value)); + return this; + } + + /** + * Appends a prepared row. + * + * @param field field row + * @return this builder + */ + public Builder add(ReceiptField field) { + fields.add(Objects.requireNonNull(field, "field")); + return this; + } + + /** + * Builds the immutable group. + * + * @return field group + */ + public ReceiptFieldGroup build() { + return new ReceiptFieldGroup(title, fields); + } + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/data/receipt/ReceiptParty.java b/templates/src/main/java/com/demcha/compose/document/templates/data/receipt/ReceiptParty.java new file mode 100644 index 000000000..1ea74097a --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/data/receipt/ReceiptParty.java @@ -0,0 +1,100 @@ +package com.demcha.compose.document.templates.data.receipt; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +/** + * One side of a transfer — who sent the money, or who received it. + * + *

    The account rows are {@link ReceiptField}s rather than named accessors + * because what identifies an account is regional: a UK payment shows a sort + * code and an account number, a SEPA payment an IBAN and a BIC, a domestic US + * one a routing number. A named field per scheme would leave every template + * rendering blanks for the schemes it was not written for.

    + * + * @param name account holder or beneficiary name + * @param addressLines optional postal address, one entry per line + * @param fields account identification rows (sort code, IBAN, …) + */ +public record ReceiptParty(String name, List addressLines, List fields) { + + /** + * Normalizes the name and freezes both lists. + */ + public ReceiptParty { + name = Objects.requireNonNullElse(name, ""); + addressLines = List.copyOf(Objects.requireNonNullElse(addressLines, List.of())); + fields = List.copyOf(Objects.requireNonNullElse(fields, List.of())); + } + + /** + * Starts a fluent party builder. + * + * @return party builder + */ + public static Builder builder() { + return new Builder(); + } + + /** + * Fluent builder for one side of a transfer. + */ + public static final class Builder { + private String name; + private final List addressLines = new ArrayList<>(); + private final List fields = new ArrayList<>(); + + private Builder() { + } + + /** + * Sets the account holder or beneficiary name. + * + * @param name display name + * @return this builder + */ + public Builder name(String name) { + this.name = name; + return this; + } + + /** + * Appends address lines. + * + * @param lines address lines, one per rendered line + * @return this builder + */ + public Builder addressLines(String... lines) { + if (lines != null) { + for (String line : lines) { + if (line != null && !line.isBlank()) { + addressLines.add(line); + } + } + } + return this; + } + + /** + * Appends an account identification row. + * + * @param label field caption (for example {@code Sort code}) + * @param value field value + * @return this builder + */ + public Builder field(String label, String value) { + fields.add(ReceiptField.of(label, value)); + return this; + } + + /** + * Builds the immutable party. + * + * @return receipt party + */ + public ReceiptParty build() { + return new ReceiptParty(name, addressLines, fields); + } + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/data/receipt/ReceiptStatus.java b/templates/src/main/java/com/demcha/compose/document/templates/data/receipt/ReceiptStatus.java new file mode 100644 index 000000000..6e9a6d918 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/data/receipt/ReceiptStatus.java @@ -0,0 +1,52 @@ +package com.demcha.compose.document.templates.data.receipt; + +import java.util.Objects; + +/** + * The status shown on a receipt: the issuer's own word for it, plus the tone + * a template is allowed to colour on. + * + * @param label the issuer's status word, rendered verbatim + * @param tone how that word reads — see {@link ReceiptStatusTone} + */ +public record ReceiptStatus(String label, ReceiptStatusTone tone) { + + /** + * Normalizes the label and defaults a missing tone to + * {@link ReceiptStatusTone#IN_PROGRESS} — the reading that claims the + * least about a payment nobody described. + */ + public ReceiptStatus { + label = Objects.requireNonNullElse(label, ""); + tone = Objects.requireNonNullElse(tone, ReceiptStatusTone.IN_PROGRESS); + } + + /** + * Creates a settled status. + * + * @param label the issuer's word for it, for example {@code Completed} + * @return settled status + */ + public static ReceiptStatus settled(String label) { + return new ReceiptStatus(label, ReceiptStatusTone.SETTLED); + } + + /** + * Creates an in-progress status. + * + * @param label the issuer's word for it, for example {@code Processing} + * @return in-progress status + */ + public static ReceiptStatus inProgress(String label) { + return new ReceiptStatus(label, ReceiptStatusTone.IN_PROGRESS); + } + + /** + * Reports whether this status carries a label worth rendering. + * + * @return {@code true} when the label is non-blank + */ + public boolean hasLabel() { + return !label.isBlank(); + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/data/receipt/ReceiptStatusTone.java b/templates/src/main/java/com/demcha/compose/document/templates/data/receipt/ReceiptStatusTone.java new file mode 100644 index 000000000..d52a38474 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/data/receipt/ReceiptStatusTone.java @@ -0,0 +1,25 @@ +package com.demcha.compose.document.templates.data.receipt; + +/** + * How a receipt's status reads, independent of the word used for it. + * + *

    Payment systems each have their own vocabulary — {@code Completed}, + * {@code Settled}, {@code Executed} all mean the money arrived. The tone is + * what a template can colour on; the label stays whatever the issuer calls + * it, so a template never has to recognise a status word to render it + * correctly.

    + */ +public enum ReceiptStatusTone { + + /** The payment reached its destination. */ + SETTLED, + + /** The payment is on its way and nothing is wrong. */ + IN_PROGRESS, + + /** The payment needs someone to look at it — held, returned, disputed. */ + ATTENTION, + + /** The payment did not happen — rejected, cancelled, failed. */ + FAILED +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/data/receipt/package-info.java b/templates/src/main/java/com/demcha/compose/document/templates/data/receipt/package-info.java new file mode 100644 index 000000000..2e1b0852a --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/data/receipt/package-info.java @@ -0,0 +1,15 @@ +/** + * Records describing a payment receipt — a transfer confirmation, a direct + * debit advice, a card payment receipt. + * + *

    {@link com.demcha.compose.document.templates.data.receipt.ReceiptDocumentSpec} + * is the document-level input; it wraps a + * {@link com.demcha.compose.document.templates.data.receipt.ReceiptData} + * carrying the hero amount, the two parties, the titled field groups, the + * status timeline, and the footer small print.

    + * + *

    Every value in here is display text the issuing system already decided. + * These records hold no rendering, theming, or DSL dependency, which is what + * lets the same receipt render through any preset.

    + */ +package com.demcha.compose.document.templates.data.receipt; diff --git a/templates/src/main/java/com/demcha/compose/document/templates/data/rota/RotaCovers.java b/templates/src/main/java/com/demcha/compose/document/templates/data/rota/RotaCovers.java new file mode 100644 index 000000000..116f815cd --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/data/rota/RotaCovers.java @@ -0,0 +1,34 @@ +package com.demcha.compose.document.templates.data.rota; + +import java.util.Objects; + +/** + * How many people a day is expecting, split across the two services. + * + *

    Two fields and not one string. The counts arrive from the booking system + * as a pair, and a rota that prints {@code "104 / 50"} leaves the reader to know + * from somewhere else which half is which. Split, the preset can label them.

    + * + * @param lunch the earlier service's count, as printed + * @param dinner the later service's count, as printed + * @since 2.4.0 + */ +public record RotaCovers(String lunch, String dinner) { + + /** + * Normalizes optional fields. + */ + public RotaCovers { + lunch = Objects.requireNonNullElse(lunch, ""); + dinner = Objects.requireNonNullElse(dinner, ""); + } + + /** + * Whether the day states either count. + * + * @return {@code true} when there is anything to print + */ + public boolean isPresent() { + return !lunch.isBlank() || !dinner.isBlank(); + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/data/rota/RotaDay.java b/templates/src/main/java/com/demcha/compose/document/templates/data/rota/RotaDay.java new file mode 100644 index 000000000..c7961df41 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/data/rota/RotaDay.java @@ -0,0 +1,48 @@ +package com.demcha.compose.document.templates.data.rota; + +import java.util.Objects; + +/** + * One day of the rota: its heading, whatever is happening on it, and how busy + * it is expected to be. + * + *

    The date is three fields rather than one because a heading sets them + * differently: {@code MONDAY} large, {@code 31} beside it, and {@code ST} + * smaller and raised. A preset that wants them joined can join them; one that + * wants the suffix raised cannot split a string that arrived joined.

    + * + * @param name the weekday, as printed + * @param ordinal the day of the month, as printed + * @param ordinalSuffix the ordinal's tail, cased as the sheet prints it — + * {@code ST}, {@code nd} — which a design may set apart; + * blank when it does not + * @param note what else is happening that day; blank when nothing is + * @param covers the day's expected covers; never {@code null} + * @since 2.4.0 + */ +public record RotaDay(String name, String ordinal, String ordinalSuffix, String note, + RotaCovers covers) { + + /** + * Normalizes optional fields. + */ + public RotaDay { + name = Objects.requireNonNullElse(name, ""); + ordinal = Objects.requireNonNullElse(ordinal, ""); + ordinalSuffix = Objects.requireNonNullElse(ordinalSuffix, ""); + note = Objects.requireNonNullElse(note, ""); + covers = covers == null ? new RotaCovers("", "") : covers; + } + + /** + * A day with a heading and nothing else stated. + * + * @param name the weekday + * @param ordinal the day of the month + * @param ordinalSuffix the ordinal's tail + */ + public RotaDay(String name, String ordinal, String ordinalSuffix) { + this(name, ordinal, ordinalSuffix, "", null); + } + +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/data/rota/RotaFooter.java b/templates/src/main/java/com/demcha/compose/document/templates/data/rota/RotaFooter.java new file mode 100644 index 000000000..e9803a857 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/data/rota/RotaFooter.java @@ -0,0 +1,29 @@ +package com.demcha.compose.document.templates.data.rota; + +import java.util.Objects; + +/** + * The standing note the rota closes with — the line about swaps, or notice, or + * who to tell. + * + * @param note the note, as printed; blank when the rota closes with none + * @since 2.4.0 + */ +public record RotaFooter(String note) { + + /** + * Normalizes optional fields. + */ + public RotaFooter { + note = Objects.requireNonNullElse(note, ""); + } + + /** + * Whether there is anything to draw. + * + * @return {@code true} when the foot states a note + */ + public boolean isPresent() { + return !note.isBlank(); + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/data/rota/RotaGroup.java b/templates/src/main/java/com/demcha/compose/document/templates/data/rota/RotaGroup.java new file mode 100644 index 000000000..73b772dff --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/data/rota/RotaGroup.java @@ -0,0 +1,35 @@ +package com.demcha.compose.document.templates.data.rota; + +import java.util.List; +import java.util.Objects; + +/** + * A band of the rota: the people who do one kind of job, under the name of it. + * + * @param label the band's name, as the rota prints it + * @param icon the mark the band opens with, from the preset's own vocabulary; + * blank for a band that opens with none + * @param staff the people in it, in the order they are listed + * @since 2.4.0 + */ +public record RotaGroup(String label, String icon, List staff) { + + /** + * Normalizes optional fields and freezes the staff list. + */ + public RotaGroup { + label = Objects.requireNonNullElse(label, ""); + icon = Objects.requireNonNullElse(icon, ""); + staff = List.copyOf(Objects.requireNonNullElse(staff, List.of())); + } + + /** + * A band that opens with no mark. + * + * @param label the band's name + * @param staff the people in it + */ + public RotaGroup(String label, List staff) { + this(label, "", staff); + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/data/rota/RotaLegend.java b/templates/src/main/java/com/demcha/compose/document/templates/data/rota/RotaLegend.java new file mode 100644 index 000000000..0ae646c24 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/data/rota/RotaLegend.java @@ -0,0 +1,73 @@ +package com.demcha.compose.document.templates.data.rota; + +import java.util.List; +import java.util.Objects; + +/** + * The strip that tells a reader what the colours mean, and the labels on the + * covers row beneath it. + * + *

    The words are the document's and the colours are the preset's: an entry + * pairs the word a site uses with the {@link ShiftStatus} it stands for, so a + * rota that prints {@code A/L} and one that prints {@code HOL} both colour the + * same.

    + * + * @param label the strip's own label; blank for a strip that + * carries none + * @param coversLabel the label on the covers row + * @param coversLunchLabel the mark on the earlier service's count + * @param coversDinnerLabel the mark on the later service's count + * @param entries the statuses the rota documents, in order + * @since 2.4.0 + */ +public record RotaLegend(String label, String coversLabel, String coversLunchLabel, + String coversDinnerLabel, List entries) { + + /** + * Normalizes optional fields and freezes the entry list. + */ + public RotaLegend { + label = Objects.requireNonNullElse(label, ""); + coversLabel = Objects.requireNonNullElse(coversLabel, ""); + coversLunchLabel = Objects.requireNonNullElse(coversLunchLabel, ""); + coversDinnerLabel = Objects.requireNonNullElse(coversDinnerLabel, ""); + entries = List.copyOf(Objects.requireNonNullElse(entries, List.of())); + } + + /** + * A legend with entries and no covers row. + * + * @param label the strip's label + * @param entries the statuses it documents + */ + public RotaLegend(String label, List entries) { + this(label, "", "", "", entries); + } + + /** + * Whether there is anything to draw. + * + * @return {@code true} when the legend states a label or any entry + */ + public boolean isPresent() { + return !label.isBlank() || !entries.isEmpty(); + } + + /** + * One swatch: the word a site prints, and the status whose colour it shows. + * + * @param label the word, as printed + * @param status the status it stands for; {@code null} normalizes to + * {@link ShiftStatus#NONE} + */ + public record Entry(String label, ShiftStatus status) { + + /** + * Normalizes optional fields. + */ + public Entry { + label = Objects.requireNonNullElse(label, ""); + status = Objects.requireNonNullElse(status, ShiftStatus.NONE); + } + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/data/rota/RotaShift.java b/templates/src/main/java/com/demcha/compose/document/templates/data/rota/RotaShift.java new file mode 100644 index 000000000..9bdeabee7 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/data/rota/RotaShift.java @@ -0,0 +1,69 @@ +package com.demcha.compose.document.templates.data.rota; + +import java.util.Objects; + +/** + * One entry in one cell of a rota: what it says, what it means, and how loudly + * it says it. + * + *

    The text is what the reader sees — a span of hours where the day is + * worked, or the word for the status where it is not. Text and status are kept + * apart because they answer different questions: two sites may print + * {@code "A/L"} and {@code "HOL"} for the same {@link ShiftStatus#HOLIDAY}, and + * a preset that colours by status keeps working for both.

    + * + * @param text what the cell prints + * @param status what the entry means; {@code null} normalizes to + * {@link ShiftStatus#NONE} + * @param emphasis how much of a mark it makes; {@code null} normalizes to + * {@link ShiftEmphasis#PLAIN} + * @since 2.4.0 + */ +public record RotaShift(String text, ShiftStatus status, ShiftEmphasis emphasis) { + + /** + * Normalizes optional fields. + */ + public RotaShift { + text = Objects.requireNonNullElse(text, ""); + status = Objects.requireNonNullElse(status, ShiftStatus.NONE); + emphasis = Objects.requireNonNullElse(emphasis, ShiftEmphasis.PLAIN); + } + + /** + * A worked span of hours, printed as it stands and marked in no way. + * + * @param text the hours as the rota prints them + * @return the shift + */ + public static RotaShift hours(String text) { + return new RotaShift(text, ShiftStatus.NONE, ShiftEmphasis.PLAIN); + } + + /** + * An entry that carries a status, drawn as loudly as the preset draws one. + * + * @param text what the cell prints + * @param status what the entry means + * @return the shift + */ + public static RotaShift strong(String text, ShiftStatus status) { + return new RotaShift(text, status, ShiftEmphasis.STRONG); + } + + /** + * The same entry, drawn quietly. + * + *

    There is no single {@code marked(text, status)} factory that picks the + * emphasis for you: a design that halves a day draws the two halves + * differently, and a factory that chose {@link ShiftEmphasis#STRONG} for + * both would be wrong in a way only a pixel diff catches.

    + * + * @param text what the cell prints + * @param status what the entry means + * @return the shift + */ + public static RotaShift soft(String text, ShiftStatus status) { + return new RotaShift(text, status, ShiftEmphasis.SOFT); + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/data/rota/RotaStaff.java b/templates/src/main/java/com/demcha/compose/document/templates/data/rota/RotaStaff.java new file mode 100644 index 000000000..ea2480f04 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/data/rota/RotaStaff.java @@ -0,0 +1,45 @@ +package com.demcha.compose.document.templates.data.rota; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +/** + * One person, and what they are down for on each day of the rota. + * + *

    {@code days} is a list of days, and each day is a list of what that person + * does on it — which is not the same as one shift each. A day with nothing in it + * is a legal blank cell rather than a hole in the data, and a day with two + * entries is a split shift. Modelling a day as a single shift would make the + * split the exception a preset has to invent a representation for.

    + * + * @param name the person, as the rota prints them + * @param days one list of entries per day, in the order the rota's days run + * @since 2.4.0 + */ +public record RotaStaff(String name, List> days) { + + /** + * Normalizes optional fields and freezes the day lists, including the inner + * ones — a frozen outer list of mutable days is not a frozen rota. + */ + public RotaStaff { + name = Objects.requireNonNullElse(name, ""); + List> stated = Objects.requireNonNullElse(days, List.of()); + List> copied = new ArrayList<>(stated.size()); + for (List day : stated) { + copied.add(List.copyOf(Objects.requireNonNullElse(day, List.of()))); + } + days = List.copyOf(copied); + } + + /** + * What this person is down for on one day. + * + * @param index the day's position in the rota's own day list + * @return the entries, or nothing when the rota does not reach that day + */ + public List day(int index) { + return index >= 0 && index < days.size() ? days.get(index) : List.of(); + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/data/rota/RotaVenue.java b/templates/src/main/java/com/demcha/compose/document/templates/data/rota/RotaVenue.java new file mode 100644 index 000000000..efdbdf71b --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/data/rota/RotaVenue.java @@ -0,0 +1,33 @@ +package com.demcha.compose.document.templates.data.rota; + +import java.util.Objects; + +/** + * Who the rota is for: the mark it opens with and the name it signs off with. + * + * @param wordmark the name as the lockup sets it + * @param wordmarkSub the line under it — a site, a branch, a department; blank + * when the lockup is one line + * @param footerName the name the foot repeats; blank when the foot names none + * @since 2.4.0 + */ +public record RotaVenue(String wordmark, String wordmarkSub, String footerName) { + + /** + * Normalizes optional fields. + */ + public RotaVenue { + wordmark = Objects.requireNonNullElse(wordmark, ""); + wordmarkSub = Objects.requireNonNullElse(wordmarkSub, ""); + footerName = Objects.requireNonNullElse(footerName, ""); + } + + /** + * Whether there is anything to draw. + * + * @return {@code true} when the venue states any of its three parts + */ + public boolean isPresent() { + return !wordmark.isBlank() || !wordmarkSub.isBlank() || !footerName.isBlank(); + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/data/rota/RotaWeek.java b/templates/src/main/java/com/demcha/compose/document/templates/data/rota/RotaWeek.java new file mode 100644 index 000000000..3a82fb0b1 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/data/rota/RotaWeek.java @@ -0,0 +1,30 @@ +package com.demcha.compose.document.templates.data.rota; + +import java.util.Objects; + +/** + * Which week the rota covers. + * + * @param title the sheet's own title; blank when the design carries none + * @param rangeLabel the span the week runs, as printed + * @since 2.4.0 + */ +public record RotaWeek(String title, String rangeLabel) { + + /** + * Normalizes optional fields. + */ + public RotaWeek { + title = Objects.requireNonNullElse(title, ""); + rangeLabel = Objects.requireNonNullElse(rangeLabel, ""); + } + + /** + * Whether there is anything to draw. + * + * @return {@code true} when the week states a title or a range + */ + public boolean isPresent() { + return !title.isBlank() || !rangeLabel.isBlank(); + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/data/rota/ShiftEmphasis.java b/templates/src/main/java/com/demcha/compose/document/templates/data/rota/ShiftEmphasis.java new file mode 100644 index 000000000..19dd649b5 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/data/rota/ShiftEmphasis.java @@ -0,0 +1,27 @@ +package com.demcha.compose.document.templates.data.rota; + +/** + * How much of a mark a shift makes on the cell it sits in. + * + *

    A rota is read across a row at a glance, so not every entry can shout. The + * document says which ones should: the day someone is off is the thing a reader + * scans for, the hours they work are the thing they read once they have found + * the person.

    + * + * @since 2.4.0 + */ +public enum ShiftEmphasis { + + /** The loudest form the preset has — a solid mark in the status's colour. */ + STRONG, + + /** + * A quieter form of the same mark. Which entry gets it is the document's + * decision and not its position: a split day is often loud then quiet, but + * the reverse happens too, and so does loud twice. + */ + SOFT, + + /** No mark at all: the text alone, on the cell. */ + PLAIN +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/data/rota/ShiftStatus.java b/templates/src/main/java/com/demcha/compose/document/templates/data/rota/ShiftStatus.java new file mode 100644 index 000000000..8d74c1b1e --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/data/rota/ShiftStatus.java @@ -0,0 +1,39 @@ +package com.demcha.compose.document.templates.data.rota; + +/** + * What a cell on a rota says about the shift in it. + * + *

    A status is a meaning, not a colour: the document says a day is holiday + * and the preset decides what holiday looks like. That is why the statuses are + * an enumeration rather than the colour strings a spreadsheet would carry — two + * presets can render the same rota in two palettes, and a status a preset does + * not style still says what it means to a reader of the legend.

    + * + * @since 2.4.0 + */ +public enum ShiftStatus { + + /** A worked shift, printed as the hours themselves and carrying no status. */ + NONE, + + /** Time the person has asked for and which is not yet granted. */ + REQUEST, + + /** A day not worked. */ + OFF, + + /** Booked leave. */ + HOLIDAY, + + /** A shift spent on stock rather than on the floor. */ + STOCK, + + /** On call: not rostered, but reachable. */ + STANDBY, + + /** A shift spent training or being trained. */ + TRAINING, + + /** Covering another team or another site. */ + SUPPORT +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/data/rota/StructuredRotaData.java b/templates/src/main/java/com/demcha/compose/document/templates/data/rota/StructuredRotaData.java new file mode 100644 index 000000000..f4895a641 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/data/rota/StructuredRotaData.java @@ -0,0 +1,147 @@ +package com.demcha.compose.document.templates.data.rota; + +import java.util.List; + +/** + * Display-oriented input for a staff rota: who works when, over one span of + * days. + * + *

    The shape is a grid the document owns rather than one the preset infers. + * {@link #days()} is the columns — the sheet's days, in order — and every + * {@link RotaStaff#days()} runs in that same order, so a cell is found by + * position and a rota of five days is as ordinary as one of seven. Nothing here + * says a week.

    + * + *

    Every component normalizes {@code null} to its empty form and freezes its + * collections, so a partial rota composes without null checks in preset code. + * The section records construct positionally; this builder is where a document + * is assembled.

    + * + * @param venue who the rota is for + * @param week which span it covers + * @param days the day columns, in the order they are printed + * @param legend what the marks mean, and the labels on the covers row + * @param groups the bands of staff, in the order they are printed + * @param footer the standing note the sheet closes with + * @since 2.4.0 + */ +public record StructuredRotaData( + RotaVenue venue, + RotaWeek week, + List days, + RotaLegend legend, + List groups, + RotaFooter footer) { + + /** + * Normalizes absent components to their empty forms and freezes the lists. + */ + public StructuredRotaData { + venue = venue == null ? new RotaVenue(null, null, null) : venue; + week = week == null ? new RotaWeek(null, null) : week; + days = List.copyOf(days == null ? List.of() : days); + legend = legend == null ? new RotaLegend(null, null, null, null, null) : legend; + groups = List.copyOf(groups == null ? List.of() : groups); + footer = footer == null ? new RotaFooter(null) : footer; + } + + /** + * Starts a fluent rota data builder. + * + * @return rota data builder + */ + public static Builder builder() { + return new Builder(); + } + + /** + * Fluent builder for complete rota content. + */ + public static final class Builder { + + private RotaVenue venue; + private RotaWeek week; + private List days = List.of(); + private RotaLegend legend; + private List groups = List.of(); + private RotaFooter footer; + + private Builder() { + } + + /** + * Sets who the rota is for. + * + * @param venue the venue block + * @return this builder + */ + public Builder venue(RotaVenue venue) { + this.venue = venue; + return this; + } + + /** + * Sets which span the rota covers. + * + * @param week the week block + * @return this builder + */ + public Builder week(RotaWeek week) { + this.week = week; + return this; + } + + /** + * Sets the day columns. + * + * @param days the days, in the order they are printed + * @return this builder + */ + public Builder days(List days) { + this.days = days; + return this; + } + + /** + * Sets what the marks mean. + * + * @param legend the legend block + * @return this builder + */ + public Builder legend(RotaLegend legend) { + this.legend = legend; + return this; + } + + /** + * Sets the bands of staff. + * + * @param groups the bands, in the order they are printed + * @return this builder + */ + public Builder groups(List groups) { + this.groups = groups; + return this; + } + + /** + * Sets the standing note the sheet closes with. + * + * @param footer the footer block + * @return this builder + */ + public Builder footer(RotaFooter footer) { + this.footer = footer; + return this; + } + + /** + * Builds normalized rota data. + * + * @return rota data + */ + public StructuredRotaData build() { + return new StructuredRotaData(venue, week, days, legend, groups, footer); + } + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/data/rota/StructuredRotaDocumentSpec.java b/templates/src/main/java/com/demcha/compose/document/templates/data/rota/StructuredRotaDocumentSpec.java new file mode 100644 index 000000000..bda90d6e1 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/data/rota/StructuredRotaDocumentSpec.java @@ -0,0 +1,32 @@ +package com.demcha.compose.document.templates.data.rota; + +/** + * Public compose-first input for rota templates. + * + *

    Authoring role: the document-level object rota presets are + * parameterised on, the way structured invoice presets are parameterised on + * {@code StructuredInvoiceDocumentSpec}.

    + * + * @param rota normalized rota content + * @since 2.4.0 + */ +public record StructuredRotaDocumentSpec(StructuredRotaData rota) { + + /** + * Creates a normalized rota document spec. + */ + public StructuredRotaDocumentSpec { + rota = rota == null ? StructuredRotaData.builder().build() : rota; + } + + /** + * Wraps existing rota data in the document-level spec expected by rota + * templates. + * + * @param rota rota data + * @return document spec + */ + public static StructuredRotaDocumentSpec from(StructuredRotaData rota) { + return new StructuredRotaDocumentSpec(rota); + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/data/rota/package-info.java b/templates/src/main/java/com/demcha/compose/document/templates/data/rota/package-info.java new file mode 100644 index 000000000..a21b8912d --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/data/rota/package-info.java @@ -0,0 +1,27 @@ +/** + * Shared, render-neutral rota document specs and supporting data records, + * consumed by the layered {@code rota.presets} presets. + * + *

    A rota is a grid the document owns rather than one a preset infers: + * {@link com.demcha.compose.document.templates.data.rota.StructuredRotaData#days()} + * is the columns and every + * {@link com.demcha.compose.document.templates.data.rota.RotaStaff#days()} + * runs in that same order, so a cell is found by position. A day holds a list + * of entries rather than one, which makes an empty day and a split day ordinary + * rather than special cases.

    + * + *

    This package supersedes {@code templates.data.schedule}, which modelled + * the same document and which nothing ever rendered. The three differences are + * the reasons: staff belong to bands rather than being ordered by a token, + * an entry states how loudly it is drawn, and colour stays out of the document + * — a schedule category carried three {@link java.awt.Color}s, which stops two + * presets drawing the same rota in two palettes.

    + * + *

    What a cell means and what it prints are separate: + * {@link com.demcha.compose.document.templates.data.rota.ShiftStatus} is the + * meaning a preset colours by, and the text is the word a particular site uses + * for it. Two rotas that print {@code A/L} and {@code HOL} colour alike.

    + * + * @since 2.4.0 + */ +package com.demcha.compose.document.templates.data.rota; diff --git a/templates/src/main/java/com/demcha/compose/document/templates/data/schedule/ScheduleAssignment.java b/templates/src/main/java/com/demcha/compose/document/templates/data/schedule/ScheduleAssignment.java index 592e7ab28..f32294d64 100644 --- a/templates/src/main/java/com/demcha/compose/document/templates/data/schedule/ScheduleAssignment.java +++ b/templates/src/main/java/com/demcha/compose/document/templates/data/schedule/ScheduleAssignment.java @@ -13,7 +13,13 @@ * @param categoryId category identifier * @param slots shift/time slots * @param note optional assignment note + * + * @deprecated superseded by + * {@link com.demcha.compose.document.templates.data.rota.RotaShift}, + * which a person's own day list holds directly rather than an + * assignment referring to both by identifier. */ +@Deprecated(since = "2.4.0") public record ScheduleAssignment( String personId, String dayId, diff --git a/templates/src/main/java/com/demcha/compose/document/templates/data/schedule/ScheduleCategory.java b/templates/src/main/java/com/demcha/compose/document/templates/data/schedule/ScheduleCategory.java index 0dff636af..f36f2dc58 100644 --- a/templates/src/main/java/com/demcha/compose/document/templates/data/schedule/ScheduleCategory.java +++ b/templates/src/main/java/com/demcha/compose/document/templates/data/schedule/ScheduleCategory.java @@ -11,7 +11,14 @@ * @param fillColor category fill color * @param textColor category text color * @param borderColor category border color + * + * @deprecated superseded by + * {@link com.demcha.compose.document.templates.data.rota.ShiftStatus} + * and a legend entry that names it. A category here carries three + * colours, which is a rendering decision living in the data layer: + * two presets cannot draw the same rota in two palettes. */ +@Deprecated(since = "2.4.0") public record ScheduleCategory( String id, String label, diff --git a/templates/src/main/java/com/demcha/compose/document/templates/data/schedule/ScheduleDay.java b/templates/src/main/java/com/demcha/compose/document/templates/data/schedule/ScheduleDay.java index 05dab5492..b185fc22c 100644 --- a/templates/src/main/java/com/demcha/compose/document/templates/data/schedule/ScheduleDay.java +++ b/templates/src/main/java/com/demcha/compose/document/templates/data/schedule/ScheduleDay.java @@ -9,7 +9,14 @@ * @param label display day label * @param headerNote optional header note * @param headerCategoryId optional category shown in the header + * + * @deprecated superseded by + * {@link com.demcha.compose.document.templates.data.rota.RotaDay}, + * which keeps the weekday, the ordinal and its tail apart so a + * design can set the suffix apart, and carries the day's expected + * covers. */ +@Deprecated(since = "2.4.0") public record ScheduleDay( String id, String label, diff --git a/templates/src/main/java/com/demcha/compose/document/templates/data/schedule/ScheduleMetricRow.java b/templates/src/main/java/com/demcha/compose/document/templates/data/schedule/ScheduleMetricRow.java index 848e097c4..c869250b3 100644 --- a/templates/src/main/java/com/demcha/compose/document/templates/data/schedule/ScheduleMetricRow.java +++ b/templates/src/main/java/com/demcha/compose/document/templates/data/schedule/ScheduleMetricRow.java @@ -10,7 +10,13 @@ * * @param label metric label * @param dayValues values aligned to days in display order + * + * @deprecated superseded by + * {@link com.demcha.compose.document.templates.data.rota.RotaCovers}, + * which the day itself carries rather than a parallel row whose + * values are aligned to the days by position alone. */ +@Deprecated(since = "2.4.0") public record ScheduleMetricRow( String label, List dayValues diff --git a/templates/src/main/java/com/demcha/compose/document/templates/data/schedule/SchedulePerson.java b/templates/src/main/java/com/demcha/compose/document/templates/data/schedule/SchedulePerson.java index 75a566820..6b822c58b 100644 --- a/templates/src/main/java/com/demcha/compose/document/templates/data/schedule/SchedulePerson.java +++ b/templates/src/main/java/com/demcha/compose/document/templates/data/schedule/SchedulePerson.java @@ -8,7 +8,13 @@ * @param id stable person identifier * @param displayName display name * @param sortOrder ordering token for roster display + * + * @deprecated superseded by + * {@link com.demcha.compose.document.templates.data.rota.RotaStaff}, + * which holds its own days and sits inside the band it belongs to + * rather than being ordered by a token and grouped nowhere. */ +@Deprecated(since = "2.4.0") public record SchedulePerson( String id, String displayName, diff --git a/templates/src/main/java/com/demcha/compose/document/templates/data/schedule/ScheduleSlot.java b/templates/src/main/java/com/demcha/compose/document/templates/data/schedule/ScheduleSlot.java index 0cda177df..72038c66f 100644 --- a/templates/src/main/java/com/demcha/compose/document/templates/data/schedule/ScheduleSlot.java +++ b/templates/src/main/java/com/demcha/compose/document/templates/data/schedule/ScheduleSlot.java @@ -7,7 +7,13 @@ * * @param start start time/label * @param end end time/label + * + * @deprecated superseded by + * {@link com.demcha.compose.document.templates.data.rota.RotaShift}, + * whose text is whatever the cell prints — a span of hours, or the + * word for a day not worked, which a start/end pair cannot carry. */ +@Deprecated(since = "2.4.0") public record ScheduleSlot( String start, String end diff --git a/templates/src/main/java/com/demcha/compose/document/templates/data/schedule/WeeklyScheduleData.java b/templates/src/main/java/com/demcha/compose/document/templates/data/schedule/WeeklyScheduleData.java index 3666fc840..2e30187f9 100644 --- a/templates/src/main/java/com/demcha/compose/document/templates/data/schedule/WeeklyScheduleData.java +++ b/templates/src/main/java/com/demcha/compose/document/templates/data/schedule/WeeklyScheduleData.java @@ -18,7 +18,14 @@ * @param people people rows in the schedule matrix * @param assignments person/day assignments * @param footerNotes footer notes rendered after the matrix + * + * @deprecated superseded by + * {@link com.demcha.compose.document.templates.data.rota.StructuredRotaData}, + * which carries the staff bands and the per-entry emphasis a rota is + * actually drawn with, and leaves colour to the preset rather than + * holding it in the document. No preset ever rendered this model. */ +@Deprecated(since = "2.4.0") public record WeeklyScheduleData( String title, String weekLabel, diff --git a/templates/src/main/java/com/demcha/compose/document/templates/data/schedule/WeeklyScheduleDocumentSpec.java b/templates/src/main/java/com/demcha/compose/document/templates/data/schedule/WeeklyScheduleDocumentSpec.java index 8589d8797..70c4eaeb0 100644 --- a/templates/src/main/java/com/demcha/compose/document/templates/data/schedule/WeeklyScheduleDocumentSpec.java +++ b/templates/src/main/java/com/demcha/compose/document/templates/data/schedule/WeeklyScheduleDocumentSpec.java @@ -13,7 +13,13 @@ * * @param schedule normalized weekly schedule content rendered by templates * @author Artem Demchyshyn + * + * @deprecated superseded by + * {@link com.demcha.compose.document.templates.data.rota.StructuredRotaDocumentSpec}, + * which is the spec rota presets are parameterised on. No preset + * ever consumed this one. */ +@Deprecated(since = "2.4.0") public record WeeklyScheduleDocumentSpec(WeeklyScheduleData schedule) { /** diff --git a/templates/src/main/java/com/demcha/compose/document/templates/data/schedule/package-info.java b/templates/src/main/java/com/demcha/compose/document/templates/data/schedule/package-info.java index da9ec5959..7eda93070 100644 --- a/templates/src/main/java/com/demcha/compose/document/templates/data/schedule/package-info.java +++ b/templates/src/main/java/com/demcha/compose/document/templates/data/schedule/package-info.java @@ -1,4 +1,17 @@ /** - * Weekly schedule document specs and supporting data records for canonical templates. + * Weekly schedule document specs and supporting data records for canonical + * templates. + * + *

    Superseded. Every type here is deprecated in favour of + * {@link com.demcha.compose.document.templates.data.rota}, which models the + * same document — who works when, over a span of days — and models it as the + * sheet is actually drawn: staff in bands, an entry that states how loudly it + * is drawn, and a day heading whose suffix a design can set apart. It also + * keeps colour out of the document, which this package does not: a category + * here carries three {@link java.awt.Color}s, so two presets cannot draw the + * same schedule in two palettes. Nothing in the library ever rendered these + * records.

    + * + * @see com.demcha.compose.document.templates.data.rota */ package com.demcha.compose.document.templates.data.schedule; diff --git a/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/ClassicInvoice.java b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/ClassicInvoice.java new file mode 100644 index 000000000..dd7090afe --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/ClassicInvoice.java @@ -0,0 +1,461 @@ +package com.demcha.compose.document.templates.invoice.presets; + +import com.demcha.compose.document.api.DocumentSession; +import com.demcha.compose.document.dsl.PageFlowBuilder; +import com.demcha.compose.document.dsl.RowBuilder; +import com.demcha.compose.document.dsl.SectionBuilder; +import com.demcha.compose.document.dsl.TableBuilder; +import com.demcha.compose.document.style.DocumentColor; +import com.demcha.compose.document.style.DocumentInsets; +import com.demcha.compose.document.style.DocumentStroke; +import com.demcha.compose.document.style.DocumentTextDecoration; +import com.demcha.compose.document.style.DocumentTextStyle; +import com.demcha.compose.document.table.DocumentTableColumn; +import com.demcha.compose.document.table.DocumentTableStyle; +import com.demcha.compose.document.templates.api.DocumentTemplate; +import com.demcha.compose.document.templates.core.theme.BrandTheme; +import com.demcha.compose.document.templates.data.invoice.InvoiceData; +import com.demcha.compose.document.templates.data.invoice.InvoiceDocumentSpec; +import com.demcha.compose.document.templates.data.invoice.InvoiceLineItem; +import com.demcha.compose.document.templates.data.invoice.InvoiceParty; +import com.demcha.compose.document.templates.data.invoice.InvoiceSummaryRow; + +import java.util.List; +import java.util.Objects; + +/** + * Classic Invoice — the letterhead-style layered invoice preset. + * + *

    An invoice with a document header band (company name + first + * address line on the left, a 28pt "INVOICE" title with number and + * issue date on the right), a "TOTAL DUE" hero strip, a + * BILL TO / FROM two-column party row, the line-items table, a + * dedicated Summary table composed after the line items + * (subtotal / tax / TOTAL, the last row emphasized), and a notes / + * payment-terms footer. Long invoices paginate naturally: the + * line-items table flows onto the next page with its header repeated, + * and the summary + footer follow the table. The preset carries its + * spacing on the page flow itself (16pt flow spacing, 24pt padding on + * every edge) and expects a zero session margin — see + * {@link #RECOMMENDED_MARGIN}.

    + * + *

    Layout traits that distinguish it from {@link ModernInvoice} and are + * part of this preset's visual contract: the header band (Modern has + * none), the party order (BILL TO left, FROM right — Modern renders + * FROM first), the separate Summary table (Modern folds the summary rows + * into the line-items table; this one repeats the line-items column + * pattern and shrink-wraps to its own rows), the fully-rounded hero + * panel (Modern rounds the right edge only), and the dark table-header + * ink on the deep-teal fill (Modern uses the light surface colour). The + * party blocks join address lines, email, and phone only — no tax id + * line.

    + * + *

    Ported-render fidelity: the preset reproduces the rendered output + * of the published standalone {@code invoice-classic} template. Three + * traits follow from that and are deliberate: the header always reads + * "INVOICE" — {@link InvoiceData#title()} is not rendered; the + * "TOTAL DUE" strip carries the invoice metadata (number, dates, + * status), not an amount; and the table-header ink stays dark on the + * teal fill. Changing any of them is a redesign of the ported look, + * not a port fix.

    + * + *

    The preset reads every shared surface from the + * {@link BrandTheme#invoiceModern()} tokens (palette, Helvetica type + * scale, hero panel radius / padding / accent width). The two ink + * colours below and the geometry constants that have no theme slot + * stay preset-local.

    + */ +public final class ClassicInvoice { + + /** + * Stable template identifier. + */ + public static final String ID = "invoice-classic"; + + /** + * Human-readable display name. + */ + public static final String DISPLAY_NAME = "Classic Invoice"; + + /** + * Recommended session margin (in points). The preset pads the page + * flow itself (24pt on every edge), so the session margin stays zero; + * a non-zero margin would double the page frame. + */ + public static final double RECOMMENDED_MARGIN = 0.0; + + private static final double TABLE_PADDING = 7.0; + + /** + * Flow spacing between the header, hero, parties, table, summary, + * and footer blocks. Intentionally wider than the theme's + * {@code pageFlowSpacing} — the letterhead look breathes more. + */ + private static final double PAGE_SPACING = 16.0; + + /** + * Page-flow padding on every edge — the preset's page frame + * (replaces the session margin, see {@link #RECOMMENDED_MARGIN}). + */ + private static final double PAGE_PADDING = 24.0; + + /** + * Company-name size in the header band — the h2 step of the modern + * business type ladder (between the theme's 11pt banner and 28pt + * headline slots, which is why it has no {@code Typography} slot). + */ + private static final double COMPANY_NAME_SIZE = 17.0; + + /** + * Deep teal used for the INVOICE title, the company name, and the + * line-items table header fill (the modern business primary). + * Same value as {@link ModernInvoice}'s preset-local primary — kept + * preset-local because adding a {@code Palette} slot would change the + * record's public constructor; fold both into a shared slot when the + * palette can grow compatibly. + */ + private static final DocumentColor PRIMARY = DocumentColor.rgb(20, 60, 75); + + /** + * Gold accent for the hero strip, the status read-out, and the footer + * column rules (the modern business accent). Preset-local, same + * rationale as {@link #PRIMARY}. + */ + private static final DocumentColor ACCENT = DocumentColor.rgb(196, 153, 76); + + private ClassicInvoice() { + } + + /** + * Builds the preset with the modern business theme + * ({@link BrandTheme#invoiceModern()}). + * + * @return ready-to-use template + */ + public static DocumentTemplate create() { + return create(BrandTheme.invoiceModern()); + } + + /** + * Builds the preset with a caller-supplied theme, so callers can + * vary the invoice flavour (typography scale, palette) without + * forking this class. + * + * @param theme active theme + * @return ready-to-use template + */ + public static DocumentTemplate create(BrandTheme theme) { + Objects.requireNonNull(theme, "theme"); + return new Template(theme); + } + + private record Template(BrandTheme theme) implements DocumentTemplate { + + @Override + public String id() { + return ID; + } + + @Override + public String displayName() { + return DISPLAY_NAME; + } + + @Override + public void compose(DocumentSession document, InvoiceDocumentSpec spec) { + Objects.requireNonNull(document, "document"); + InvoiceData data = Objects.requireNonNull(spec, "spec").invoice(); + + DocumentColor panelFill = theme.palette().banner(); // soft tan + DocumentColor surface = theme.palette().mainFill(); // cream + DocumentColor rule = theme.palette().rule(); + + DocumentTextStyle titleStyle = DocumentTextStyle.builder() + .fontName(theme.typography().headlineFont()) + .size(theme.typography().sizeHeadline()) + .decoration(DocumentTextDecoration.BOLD) + .color(PRIMARY) + .build(); + DocumentTextStyle companyStyle = DocumentTextStyle.builder() + .fontName(theme.typography().headlineFont()) + .size(COMPANY_NAME_SIZE) + .decoration(DocumentTextDecoration.BOLD) + .color(PRIMARY) + .build(); + DocumentTextStyle labelStyle = theme.bodyBoldStyle(); + DocumentTextStyle bodyStyle = theme.bodyStyle(); + DocumentTextStyle captionStyle = DocumentTextStyle.builder() + .fontName(theme.typography().bodyFont()) + .size(theme.typography().sizeEntrySubtitle()) + .color(theme.palette().muted()) + .build(); + + // Line-item cells intentionally carry NO textStyle — they inherit + // the DSL default table-cell text (same guard as ModernInvoice). + DocumentTableStyle bordered = DocumentTableStyle.builder() + .stroke(DocumentStroke.of(rule, 0.6)) + .padding(DocumentInsets.of(TABLE_PADDING)) + .build(); + // The header keeps the dark body-bold ink on the deep-teal fill — + // the ported template's approved render (Modern uses the light + // surface ink instead). A higher-contrast header is a deliberate + // redesign of the ported look, not a port fix. + DocumentTableStyle headerStyle = DocumentTableStyle.builder() + .fillColor(PRIMARY) + .stroke(DocumentStroke.of(rule, 0.6)) + .padding(DocumentInsets.of(TABLE_PADDING + 1)) + .textStyle(labelStyle) + .build(); + DocumentTableStyle totalStyle = DocumentTableStyle.builder() + .fillColor(panelFill) + .stroke(DocumentStroke.of(rule, 0.6)) + .padding(DocumentInsets.of(TABLE_PADDING + 1)) + .textStyle(labelStyle) + .build(); + + PageFlowBuilder flow = document.dsl().pageFlow() + .name("Invoice") + .spacing(PAGE_SPACING) + .padding(DocumentInsets.of(PAGE_PADDING)) + .addRow("Header", row -> renderHeader(row, data, + companyStyle, titleStyle, captionStyle)) + .addSection("Hero", section -> renderHero(section, data, + panelFill, labelStyle)) + .addRow("Parties", row -> renderParties(row, data, + labelStyle, bodyStyle)) + .addTable(table -> renderLineItems(table, data, + bordered, headerStyle, panelFill, surface)); + // The engine rejects a zero-row table, so an invoice without + // summary rows renders without the Summary section. + if (!data.summaryRows().isEmpty()) { + flow.addSection("Summary", section -> renderSummary(section, data, + bordered, totalStyle)); + } + flow.addSection("Footer", section -> renderFooter(section, data, + labelStyle, captionStyle)) + .build(); + } + + private void renderHeader(RowBuilder row, + InvoiceData data, + DocumentTextStyle companyStyle, + DocumentTextStyle titleStyle, + DocumentTextStyle captionStyle) { + row.spacing(18); + row.weights(1, 1); + row.addSection("HeaderLeft", section -> section + .spacing(2) + .addParagraph(p -> p + .text(data.fromParty().name()) + .textStyle(companyStyle) + .margin(DocumentInsets.zero())) + .addParagraph(p -> p + .text(firstAddressLine(data.fromParty())) + .textStyle(captionStyle) + .margin(DocumentInsets.zero()))); + row.addSection("HeaderRight", section -> section + .spacing(2) + .addParagraph(p -> p + .text("INVOICE") + .textStyle(titleStyle) + .margin(DocumentInsets.zero())) + .addParagraph(p -> p + .text(safe(data.invoiceNumber())) + .textStyle(captionStyle) + .margin(DocumentInsets.zero())) + .addParagraph(p -> p + .text("Issued " + safe(data.issueDate())) + .textStyle(captionStyle) + .margin(DocumentInsets.zero()))); + } + + private void renderHero(SectionBuilder section, + InvoiceData data, + DocumentColor panelFill, + DocumentTextStyle labelStyle) { + // The strip is a metadata read-out — the ported template renders + // no amount after the TOTAL DUE label. + section.softPanel(panelFill, + theme.spacing().bannerCornerRadius(), + theme.spacing().bannerInnerPadding()) + .accentLeft(ACCENT, theme.spacing().accentRuleWidth()) + .spacing(6) + .addParagraph(p -> p + .text("TOTAL DUE") + .textStyle(labelStyle) + .margin(DocumentInsets.zero())) + .addRich(rich -> rich + .plain("Invoice ").bold(safe(data.invoiceNumber())) + .plain(" Issued ").bold(safe(data.issueDate())) + .plain(" Due ").bold(safe(data.dueDate())) + .plain(" Status ").accent(safeStatus(data.status()), ACCENT)); + } + + private void renderParties(RowBuilder row, + InvoiceData data, + DocumentTextStyle labelStyle, + DocumentTextStyle bodyStyle) { + row.spacing(18); + row.weights(1, 1); + row.addSection("BillTo", section -> renderContactBlock( + section, data.billToParty(), "BILL TO", labelStyle, bodyStyle)); + row.addSection("From", section -> renderContactBlock( + section, data.fromParty(), "FROM", labelStyle, bodyStyle)); + } + + private static void renderLineItems(TableBuilder table, + InvoiceData data, + DocumentTableStyle bordered, + DocumentTableStyle headerStyle, + DocumentColor zebraOdd, + DocumentColor zebraEven) { + table.name("LineItems") + .columns( + DocumentTableColumn.auto(), + DocumentTableColumn.fixed(54), + DocumentTableColumn.fixed(96), + DocumentTableColumn.fixed(96)) + .defaultCellStyle(bordered) + .headerRow("Description", "Qty", "Unit", "Amount") + .headerStyle(headerStyle) + .repeatHeader() + .zebra(zebraOdd, zebraEven); + // Only item.description() renders in the cell — the optional + // details string would drive the auto-sized description column + // past the inner page width (same guard as ModernInvoice). + for (InvoiceLineItem item : data.lineItems()) { + table.row(item.description(), item.quantity(), + item.unitPrice(), item.amount()); + } + } + + private static void renderSummary(SectionBuilder section, + InvoiceData data, + DocumentTableStyle bordered, + DocumentTableStyle totalStyle) { + // The summary lives in its own table that repeats the LineItems + // column pattern (auto / 54 / 96 / 96) — the ported template's + // shape. The table shrink-wraps to its own rows (the empty auto + // column collapses), so it renders narrower than the line items + // above. The LAST row is the grand total and renders via + // totalRow for the emphasized style. + section.addTable(summary -> { + summary.name("SummaryTable") + .columns( + DocumentTableColumn.auto(), + DocumentTableColumn.fixed(54), + DocumentTableColumn.fixed(96), + DocumentTableColumn.fixed(96)) + .defaultCellStyle(bordered); + List summaries = data.summaryRows(); + for (int i = 0; i < summaries.size(); i++) { + InvoiceSummaryRow row = summaries.get(i); + if (i == summaries.size() - 1) { + summary.totalRow(totalStyle, "", "", row.label(), row.value()); + } else { + summary.row("", "", row.label(), row.value()); + } + } + }); + } + + private void renderFooter(SectionBuilder section, + InvoiceData data, + DocumentTextStyle labelStyle, + DocumentTextStyle captionStyle) { + section.spacing(8) + .addRow("FooterRow", row -> row + .spacing(18) + .weights(1, 1) + .addSection("InvoiceNotes", col -> col + .accentLeft(ACCENT, 3) + .padding(0, 0, 0, 8) + .spacing(3) + .addParagraph(p -> p + .text("Notes") + .textStyle(labelStyle) + .margin(DocumentInsets.zero())) + .addList(list -> list.items(data.notes()))) + .addSection("InvoicePaymentTerms", col -> col + .accentLeft(ACCENT, 3) + .padding(0, 0, 0, 8) + .spacing(3) + .addParagraph(p -> p + .text("Payment terms") + .textStyle(labelStyle) + .margin(DocumentInsets.zero())) + .addList(list -> list.items(data.paymentTerms())))); + if (!data.footerNote().isBlank()) { + section.addParagraph(p -> p + .text(data.footerNote()) + .textStyle(captionStyle) + .margin(new DocumentInsets(14, 0, 0, 0))); + } + } + + private static void renderContactBlock(SectionBuilder section, + InvoiceParty party, + String label, + DocumentTextStyle labelStyle, + DocumentTextStyle bodyStyle) { + section.spacing(2) + .addParagraph(p -> p + .text(label) + .textStyle(labelStyle) + .margin(DocumentInsets.zero())) + .addParagraph(p -> p + .text(party.name()) + .textStyle(labelStyle) + .margin(DocumentInsets.zero())) + .addParagraph(p -> p + .text(joinAddress(party)) + .textStyle(bodyStyle) + .lineSpacing(1.3) + .margin(DocumentInsets.zero())); + } + } + + private static String firstAddressLine(InvoiceParty party) { + for (String line : party.addressLines()) { + if (line != null && !line.isBlank()) { + return line; + } + } + return ""; + } + + private static String joinAddress(InvoiceParty party) { + StringBuilder builder = new StringBuilder(); + for (String line : party.addressLines()) { + if (line == null || line.isBlank()) { + continue; + } + append(builder, line); + } + if (!party.email().isBlank()) { + append(builder, party.email()); + } + if (!party.phone().isBlank()) { + append(builder, party.phone()); + } + return builder.toString(); + } + + private static void append(StringBuilder builder, String line) { + if (builder.length() > 0) { + builder.append('\n'); + } + builder.append(line); + } + + private static String safeStatus(String status) { + if (status == null || status.isBlank()) { + return "—"; + } + return status; + } + + private static String safe(String value) { + return value == null ? "" : value; + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/ConsultingBody.java b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/ConsultingBody.java new file mode 100644 index 000000000..75c38b050 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/ConsultingBody.java @@ -0,0 +1,238 @@ +package com.demcha.compose.document.templates.invoice.presets; + +import com.demcha.compose.document.dsl.ParagraphBuilder; +import com.demcha.compose.document.dsl.SectionBuilder; +import com.demcha.compose.document.dsl.TableBuilder; +import com.demcha.compose.document.node.DocumentNode; +import com.demcha.compose.document.node.TextAlign; +import com.demcha.compose.document.style.ClipPolicy; +import com.demcha.compose.document.style.DocumentColor; +import com.demcha.compose.document.style.DocumentInsets; +import com.demcha.compose.document.style.DocumentStroke; +import com.demcha.compose.document.style.DocumentTextStyle; +import com.demcha.compose.document.style.ShapeOutline; +import com.demcha.compose.document.table.DocumentTableCell; +import com.demcha.compose.document.table.DocumentTableColumn; +import com.demcha.compose.document.table.DocumentTableStyle; +import com.demcha.compose.document.table.DocumentTableTextAnchor; +import com.demcha.compose.document.templates.data.invoice.InvoiceRecipient; +import com.demcha.compose.document.templates.data.invoice.InvoiceServiceLines; +import com.demcha.compose.document.templates.data.invoice.InvoiceSummaryBlock; +import com.demcha.compose.document.templates.data.invoice.InvoiceTotalsBlock; +import com.demcha.compose.document.templates.data.invoice.StructuredInvoiceData; + +import java.util.List; + +import static com.demcha.compose.document.templates.invoice.presets.ConsultingStyles.ACCENT_PRIMARY; +import static com.demcha.compose.document.templates.invoice.presets.ConsultingStyles.BODY; +import static com.demcha.compose.document.templates.invoice.presets.ConsultingStyles.BODY_ACCENT; +import static com.demcha.compose.document.templates.invoice.presets.ConsultingStyles.BODY_BOLD; +import static com.demcha.compose.document.templates.invoice.presets.ConsultingStyles.CONTENT_WIDTH; +import static com.demcha.compose.document.templates.invoice.presets.ConsultingStyles.EMPHASIS_FILL; +import static com.demcha.compose.document.templates.invoice.presets.ConsultingStyles.PAGE_BACKGROUND; +import static com.demcha.compose.document.templates.invoice.presets.ConsultingStyles.ROW_ALT; +import static com.demcha.compose.document.templates.invoice.presets.ConsultingStyles.SMALL; +import static com.demcha.compose.document.templates.invoice.presets.ConsultingStyles.SMALL_BOLD; +import static com.demcha.compose.document.templates.invoice.presets.ConsultingStyles.TABLE_HEADER; +import static com.demcha.compose.document.templates.invoice.presets.ConsultingStyles.TABLE_RATIOS; +import static com.demcha.compose.document.templates.invoice.presets.ConsultingStyles.TOTALS_WIDTH; +import static com.demcha.compose.document.templates.invoice.presets.ConsultingStyles.TOTAL_AMOUNT; +import static com.demcha.compose.document.templates.invoice.presets.ConsultingStyles.TOTAL_AMOUNT_RIGHT_INSET; +import static com.demcha.compose.document.templates.invoice.presets.ConsultingStyles.TOTAL_LABEL_LEFT_INSET; +import static com.demcha.compose.document.templates.invoice.presets.ConsultingStyles.tableStyle; +import static com.demcha.compose.document.templates.invoice.presets.ConsultingText.decimal; +import static com.demcha.compose.document.templates.invoice.presets.ConsultingText.link; +import static com.demcha.compose.document.templates.invoice.presets.ConsultingText.money; +import static com.demcha.compose.document.templates.invoice.presets.ConsultingText.titleBreakerWidth; + +/** + * The body of the Consulting Invoice: who is billed and for what, the + * priced service lines, and the totals stack. + */ +final class ConsultingBody { + + private ConsultingBody() { + } + + /** The billed-to block: name, attention line, address, then the email. */ + static void renderBilledTo(SectionBuilder section, StructuredInvoiceData data) { + InvoiceRecipient billTo = data.billTo(); + ConsultingMasthead.sectionHeading(section, billTo.heading()); + section.spacing(1.5).addParagraph(billTo.name(), BODY_BOLD); + // A recipient without an attention line gets no line box for it. + if (!billTo.subline().isBlank()) { + section.addParagraph(billTo.subline(), BODY); + } + section.addParagraph(String.join("\n", billTo.addressLines()), BODY); + if (!billTo.email().isBlank()) { + section.addParagraph(paragraph -> paragraph + .rich(rich -> rich + .style(billTo.emailLabel() + " ", BODY_BOLD) + .with(billTo.email(), BODY, link("mailto:" + billTo.email()))) + .margin(DocumentInsets.top(5))); + } + } + + /** What the invoice covers, and the period it covers. */ + static void renderSummary(SectionBuilder section, StructuredInvoiceData data) { + InvoiceSummaryBlock summary = data.summary(); + ConsultingMasthead.sectionHeading(section, summary.heading()); + section.spacing(1) + .addParagraph(paragraph -> paragraph + .text(summary.intro()) + .textStyle(BODY) + .lineSpacing(1.35) + .margin(DocumentInsets.zero())) + .addParagraph(paragraph -> paragraph + .text(summary.servicePeriod()) + .textStyle(BODY_BOLD) + .lineSpacing(1.15) + .margin(DocumentInsets.zero())); + } + + /** + * The line-items table: a repeating header on the accent fill, and one + * zebra-filled row per service line. The unit-price and amount headers + * carry the currency on their second line. + */ + static void renderServiceLines(TableBuilder table, StructuredInvoiceData data) { + InvoiceServiceLines serviceLines = data.serviceLines(); + InvoiceServiceLines.Columns columns = serviceLines.columns(); + // A document that states no currency prints no parenthetical. + String currency = data.currencyCode().isBlank() + ? "" : "(" + data.currencyCode() + ")"; + + DocumentTableStyle headerStyle = DocumentTableStyle.builder() + .padding(DocumentInsets.symmetric(4.5, 4.0)) + .fillColor(ACCENT_PRIMARY) + .stroke(DocumentStroke.of(ACCENT_PRIMARY, 0.6)) + .textStyle(TABLE_HEADER) + .textAnchor(DocumentTableTextAnchor.CENTER) + .lineSpacing(1.0) + .build(); + + table.name("LineItems") + .columns(column(0), column(1), column(2), column(3), column(4), column(5)) + .headerCells( + DocumentTableCell.text(columns.index()), + DocumentTableCell.text(columns.description()), + DocumentTableCell.text(columns.servicePeriod()), + DocumentTableCell.text(columns.quantity()), + DocumentTableCell.lines(columns.unitPrice(), currency), + DocumentTableCell.lines(columns.amount(), currency)) + .headerStyle(headerStyle) + .repeatHeader() + .padding(DocumentInsets.zero()) + .margin(DocumentInsets.zero()); + + List lines = serviceLines.lines(); + for (int index = 0; index < lines.size(); index++) { + InvoiceServiceLines.Line line = lines.get(index); + DocumentColor fill = index % 2 == 0 ? PAGE_BACKGROUND : ROW_ALT; + table.rowCells( + DocumentTableCell.text(Integer.toString(line.lineNumber())) + .withStyle(tableStyle(fill, DocumentTableTextAnchor.CENTER)), + DocumentTableCell.node(descriptionNode(line, fill)) + .withStyle(tableStyle(fill, DocumentTableTextAnchor.CENTER_LEFT)), + DocumentTableCell.text(line.servicePeriod()) + .withStyle(tableStyle(fill, DocumentTableTextAnchor.CENTER)), + DocumentTableCell.lines(decimal(line.quantity()), line.unit()) + .withStyle(tableStyle(fill, DocumentTableTextAnchor.CENTER)), + DocumentTableCell.text(money(line.unitPrice())) + .withStyle(tableStyle(fill, DocumentTableTextAnchor.CENTER_RIGHT)), + DocumentTableCell.text(money(line.amount())) + .withStyle(tableStyle(fill, DocumentTableTextAnchor.CENTER_RIGHT))); + } + } + + private static DocumentTableColumn column(int index) { + return DocumentTableColumn.fixed(CONTENT_WIDTH * TABLE_RATIOS[index]); + } + + /** + * The description cell: the title, an invisible breaker that fills the + * rest of the title line, then the description on its own line. The + * breaker is a zero-height rectangle in the row's own fill — the engine + * has no rich-text line break (see + * {@link ConsultingText#titleBreakerWidth(String)}). + */ + private static DocumentNode descriptionNode(InvoiceServiceLines.Line line, + DocumentColor fill) { + return new ParagraphBuilder() + .name("Description") + .align(TextAlign.LEFT) + .textStyle(SMALL) + .lineSpacing(1.15) + .margin(DocumentInsets.zero()) + .rich(rich -> rich + .style(line.title(), SMALL_BOLD) + .shape(new ShapeOutline.Rectangle(titleBreakerWidth(line.title()), 0.1), fill) + .style(line.description(), SMALL)) + .build(); + } + + /** + * The totals stack, pinned to the right of the content box and kept on + * one page: the rows above the rule, then the emphasized total band. + */ + static void renderTotals(SectionBuilder section, StructuredInvoiceData data) { + InvoiceTotalsBlock totals = data.totals(); + section.keepTogether() + .margin(6, 0, 0, (float) (CONTENT_WIDTH - TOTALS_WIDTH)) + .spacing(1); + for (InvoiceTotalsBlock.Row row : totals.rows()) { + totalsBand(section, row.label(), money(row.amount()), false); + } + section.addLine(line -> line + .horizontal(TOTALS_WIDTH) + .thickness(1.0) + .color(ACCENT_PRIMARY) + .margin(DocumentInsets.top(2))); + String totalLabel = data.currencyCode().isBlank() + ? totals.totalLabel() + : totals.totalLabel() + " (" + data.currencyCode() + ")"; + totalsBand(section, totalLabel, money(totals.totalAmount()), true); + } + + private static void totalsBand(SectionBuilder section, + String label, + String amount, + boolean emphasized) { + double height = emphasized ? 27 : 15; + section.addContainer(container -> { + container.name(emphasized ? "TotalDue" : "TotalRow") + .rectangle(TOTALS_WIDTH, height) + .clipPolicy(ClipPolicy.OVERFLOW_VISIBLE) + .centerLeft(insetParagraph( + label, + emphasized ? BODY_ACCENT : SMALL_BOLD, + TextAlign.LEFT, + 0, + TOTAL_LABEL_LEFT_INSET)) + .centerRight(insetParagraph( + amount, + emphasized ? TOTAL_AMOUNT : BODY_BOLD, + TextAlign.RIGHT, + TOTAL_AMOUNT_RIGHT_INSET, + 0)); + if (emphasized) { + container.fillColor(EMPHASIS_FILL); + } + }); + } + + private static DocumentNode insetParagraph(String text, + DocumentTextStyle style, + TextAlign align, + double right, + double left) { + return new ParagraphBuilder() + .text(text) + .textStyle(style) + .align(align) + .lineSpacing(1.15) + .padding(new DocumentInsets(0, (float) right, 0, (float) left)) + .margin(DocumentInsets.zero()) + .build(); + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/ConsultingClosing.java b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/ConsultingClosing.java new file mode 100644 index 000000000..a92bc52d4 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/ConsultingClosing.java @@ -0,0 +1,179 @@ +package com.demcha.compose.document.templates.invoice.presets; + +import com.demcha.compose.document.dsl.ParagraphBuilder; +import com.demcha.compose.document.dsl.SectionBuilder; +import com.demcha.compose.document.dsl.ShapeContainerBuilder; +import com.demcha.compose.document.node.DocumentNode; +import com.demcha.compose.document.node.LayerAlign; +import com.demcha.compose.document.style.ClipPolicy; +import com.demcha.compose.document.style.DocumentInsets; +import com.demcha.compose.document.style.DocumentStroke; +import com.demcha.compose.document.templates.core.identity.ContactUri; +import com.demcha.compose.document.templates.data.invoice.InvoiceNotesBlock; +import com.demcha.compose.document.templates.data.invoice.InvoicePaymentBlock; +import com.demcha.compose.document.templates.data.invoice.StructuredInvoiceData; + +import static com.demcha.compose.document.templates.invoice.presets.ConsultingStyles.ACCENT_PRIMARY; +import static com.demcha.compose.document.templates.invoice.presets.ConsultingStyles.BODY; +import static com.demcha.compose.document.templates.invoice.presets.ConsultingStyles.BODY_LINK; +import static com.demcha.compose.document.templates.invoice.presets.ConsultingStyles.EMPHASIS_FILL; +import static com.demcha.compose.document.templates.invoice.presets.ConsultingStyles.HAIRLINE; +import static com.demcha.compose.document.templates.invoice.presets.ConsultingStyles.NOTES_WIDTH; +import static com.demcha.compose.document.templates.invoice.presets.ConsultingStyles.NOTICE_ACCENT; +import static com.demcha.compose.document.templates.invoice.presets.ConsultingStyles.NOTICE_BOLD; +import static com.demcha.compose.document.templates.invoice.presets.ConsultingStyles.PAYMENT_WIDTH; +import static com.demcha.compose.document.templates.invoice.presets.ConsultingStyles.SMALL; +import static com.demcha.compose.document.templates.invoice.presets.ConsultingStyles.SMALL_BOLD; +import static com.demcha.compose.document.templates.invoice.presets.ConsultingStyles.SMALL_ITALIC; +import static com.demcha.compose.document.templates.invoice.presets.ConsultingText.link; +import static com.demcha.compose.document.templates.invoice.presets.ConsultingText.nonBreakingPhone; + +/** + * The closing band of the Consulting Invoice: where the money goes on the + * left, and the notes with the due-by chip on the right. + */ +final class ConsultingClosing { + + private ConsultingClosing() { + } + + /** Payment information: the bank badge, the labelled fields, the instruction. */ + static void renderPayment(SectionBuilder section, StructuredInvoiceData data) { + InvoicePaymentBlock payment = data.payment(); + ConsultingMasthead.sectionHeading(section, payment.heading()); + // The band is as tall as the fields it holds (15pt each), never + // shorter than the badge standing beside them. + double bankBandHeight = Math.max(38.0, payment.fields().size() * 15.0); + section.spacing(3) + .addContainer(container -> container + .name("BankInformationBand") + .rectangle(PAYMENT_WIDTH, bankBandHeight) + .clipPolicy(ClipPolicy.OVERFLOW_VISIBLE) + .centerLeft(bankBadge()) + .position(bankDetails(payment), 58, 0, LayerAlign.CENTER_LEFT)) + .addParagraph(paragraph -> paragraph + .text(payment.instruction()) + .textStyle(SMALL_ITALIC) + .lineSpacing(1.30) + .margin(DocumentInsets.top(4))); + } + + private static DocumentNode bankBadge() { + return new ShapeContainerBuilder() + .name("BankBadge") + .circle(38) + .fillColor(EMPHASIS_FILL) + .center(ConsultingIcons.icon(ConsultingIcons.BANK, 19.5)) + .build(); + } + + private static DocumentNode bankDetails(InvoicePaymentBlock payment) { + SectionBuilder details = new SectionBuilder().name("BankDetails").spacing(0); + double width = PAYMENT_WIDTH - 61; + for (InvoicePaymentBlock.Field field : payment.fields()) { + details.addContainer(container -> container + .name("BankField") + .rectangle(width, 15) + .clipPolicy(ClipPolicy.OVERFLOW_VISIBLE) + .centerLeft(ConsultingMasthead.paragraph(field.label(), SMALL_BOLD)) + .position(ConsultingMasthead.paragraph(field.value(), SMALL), + 76, 0, LayerAlign.CENTER_LEFT)); + } + return details.build(); + } + + /** + * The notes column: the paragraphs, with the query channels linked + * where they appear in the prose, and the due-by chip beneath them. + */ + static void renderNotes(SectionBuilder section, StructuredInvoiceData data) { + InvoiceNotesBlock notes = data.notes(); + section.accentLeft(HAIRLINE, 1.0).padding(0, 0, 0, 24).spacing(3); + ConsultingMasthead.sectionHeading(section, notes.heading()); + for (String paragraphText : notes.paragraphs()) { + section.addParagraph(paragraph -> { + paragraph.lineSpacing(1.30).margin(DocumentInsets.zero()); + renderNoteParagraph(paragraph, paragraphText, notes); + }); + } + renderDueNotice(section, data.payment()); + } + + /** + * Links the contact channels inside a note paragraph. The channels are + * matched by value rather than by markup, so the prose stays plain + * content: a paragraph that mentions neither is drawn as plain text. + */ + private static void renderNoteParagraph(ParagraphBuilder paragraph, + String text, + InvoiceNotesBlock notes) { + int emailIndex = notes.contactEmail().isEmpty() ? -1 : text.indexOf(notes.contactEmail()); + if (emailIndex < 0) { + paragraph.text(text).textStyle(BODY); + return; + } + String before = text.substring(0, emailIndex); + int afterEmail = emailIndex + notes.contactEmail().length(); + int phoneIndex = notes.contactPhone().isEmpty() + ? -1 : text.indexOf(notes.contactPhone(), afterEmail); + if (phoneIndex < 0) { + String after = text.substring(afterEmail); + paragraph.rich(rich -> rich + .style(before, BODY) + .with(notes.contactEmail(), BODY_LINK, + link("mailto:" + notes.contactEmail())) + .style(after, BODY)); + return; + } + String between = text.substring(afterEmail, phoneIndex); + String after = text.substring(phoneIndex + notes.contactPhone().length()); + paragraph.rich(rich -> rich + .style(before, BODY) + .with(notes.contactEmail(), BODY_LINK, link("mailto:" + notes.contactEmail())) + .style(between, BODY) + .with(nonBreakingPhone(notes.contactPhone()), BODY, + ContactUri.telLink(notes.contactPhone())) + .style(after, BODY)); + } + + /** The due-by chip: a calendar badge and the notice, on the quiet fill. */ + private static void renderDueNotice(SectionBuilder section, InvoicePaymentBlock payment) { + double width = NOTES_WIDTH - 24; + section.addContainer(container -> container + .name("PaymentTerms") + .roundedRect(width, 34, 4) + .fillColor(EMPHASIS_FILL) + .clipPolicy(ClipPolicy.OVERFLOW_VISIBLE) + .position(calendarBadge(), 8, 0, LayerAlign.CENTER_LEFT) + .position(dueNotice(payment), 42, 0, LayerAlign.CENTER_LEFT)); + } + + private static DocumentNode calendarBadge() { + return new ShapeContainerBuilder() + .name("CalendarBadge") + .circle(23) + .stroke(DocumentStroke.of(ACCENT_PRIMARY, 0.9)) + .center(ConsultingIcons.icon(ConsultingIcons.CALENDAR, 12.5)) + .build(); + } + + private static DocumentNode dueNotice(InvoicePaymentBlock payment) { + String notice = payment.dueNotice(); + String emphasis = payment.dueNoticeEmphasis(); + ParagraphBuilder paragraph = new ParagraphBuilder() + .name("DueNotice") + .lineSpacing(1.1) + .margin(DocumentInsets.zero()); + int index = emphasis.isEmpty() ? -1 : notice.indexOf(emphasis); + if (index < 0) { + return paragraph.text(notice).textStyle(NOTICE_BOLD).build(); + } + String before = notice.substring(0, index); + String after = notice.substring(index + emphasis.length()); + return paragraph.rich(rich -> rich + .style(before, NOTICE_BOLD) + .style(emphasis, NOTICE_ACCENT) + .style(after, NOTICE_BOLD)) + .build(); + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/ConsultingIcons.java b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/ConsultingIcons.java new file mode 100644 index 000000000..d8e4d09da --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/ConsultingIcons.java @@ -0,0 +1,70 @@ +package com.demcha.compose.document.templates.invoice.presets; + +import com.demcha.compose.document.dsl.ImageBuilder; +import com.demcha.compose.document.image.DocumentImageData; +import com.demcha.compose.document.node.DocumentNode; + +import java.io.IOException; +import java.io.InputStream; +import java.io.UncheckedIOException; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Classpath loader for the Consulting Invoice icon set — the channel marks + * beside the contact lines, the bank badge, and the calendar of the + * due-by notice. + * + *

    These are template chrome, not content: they ship inside the templates + * artifact under {@code templates/invoice/consulting/icons/} and are named + * by the preset, never by the data. Loaded bytes are cached per name, so a + * render reads each icon file once per JVM.

    + */ +final class ConsultingIcons { + + static final String LOCATION = "location"; + static final String PHONE = "phone"; + static final String EMAIL = "email"; + static final String WEBSITE = "website"; + static final String BANK = "bank"; + static final String CALENDAR = "payment-calendar"; + + private static final String ICON_ROOT = "/templates/invoice/consulting/icons/"; + private static final Map CACHE = new ConcurrentHashMap<>(); + + private ConsultingIcons() { + } + + /** + * Builds a sized icon node. + * + * @param name the icon name + * @param size the box the icon is fitted into + * @return the icon node + */ + static DocumentNode icon(String name, double size) { + return new ImageBuilder() + .name("Icon-" + name) + .source(image(name)) + .fitToBounds(size, size) + .build(); + } + + private static DocumentImageData image(String name) { + return CACHE.computeIfAbsent(name, ConsultingIcons::read); + } + + private static DocumentImageData read(String name) { + String resourcePath = ICON_ROOT + name + ".png"; + try (InputStream input = ConsultingIcons.class.getResourceAsStream(resourcePath)) { + if (input == null) { + throw new IllegalStateException( + "Missing consulting invoice icon: " + resourcePath); + } + return DocumentImageData.fromBytes(input.readAllBytes()); + } catch (IOException e) { + throw new UncheckedIOException( + "Failed to read consulting invoice icon: " + resourcePath, e); + } + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/ConsultingInvoice.java b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/ConsultingInvoice.java new file mode 100644 index 000000000..b183950d2 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/ConsultingInvoice.java @@ -0,0 +1,193 @@ +package com.demcha.compose.document.templates.invoice.presets; + +import com.demcha.compose.document.api.DocumentSession; +import com.demcha.compose.document.api.PageBackgroundFill; +import com.demcha.compose.document.node.RowVerticalAlign; +import com.demcha.compose.document.output.DocumentHeaderFooter; +import com.demcha.compose.document.output.DocumentHeaderFooterZone; +import com.demcha.compose.document.style.DocumentInsets; +import com.demcha.compose.document.templates.api.DocumentTemplate; +import com.demcha.compose.document.templates.data.invoice.InvoiceContactBlock; +import com.demcha.compose.document.templates.data.invoice.StructuredInvoiceData; +import com.demcha.compose.document.templates.data.invoice.StructuredInvoiceDocumentSpec; + +import java.util.List; +import java.util.Objects; + +import static com.demcha.compose.document.templates.invoice.presets.ConsultingStyles.CONTENT_WIDTH; +import static com.demcha.compose.document.templates.invoice.presets.ConsultingStyles.FOOTER_BAND_RATIO; +import static com.demcha.compose.document.templates.invoice.presets.ConsultingStyles.FOOTER_FILL; +import static com.demcha.compose.document.templates.invoice.presets.ConsultingStyles.FOOTER_TEXT_SIZE; +import static com.demcha.compose.document.templates.invoice.presets.ConsultingStyles.FOOTER_ZONE_HEIGHT; +import static com.demcha.compose.document.templates.invoice.presets.ConsultingStyles.HAIRLINE; +import static com.demcha.compose.document.templates.invoice.presets.ConsultingStyles.HEADER_GAP; +import static com.demcha.compose.document.templates.invoice.presets.ConsultingStyles.HEADER_LEFT_WEIGHT; +import static com.demcha.compose.document.templates.invoice.presets.ConsultingStyles.HEADER_RIGHT_WEIGHT; +import static com.demcha.compose.document.templates.invoice.presets.ConsultingStyles.LOWER_GAP; +import static com.demcha.compose.document.templates.invoice.presets.ConsultingStyles.LOWER_LEFT_WEIGHT; +import static com.demcha.compose.document.templates.invoice.presets.ConsultingStyles.MUTED; +import static com.demcha.compose.document.templates.invoice.presets.ConsultingStyles.PAGE; +import static com.demcha.compose.document.templates.invoice.presets.ConsultingStyles.PAGE_BACKGROUND; +import static com.demcha.compose.document.templates.invoice.presets.ConsultingStyles.PAGE_MARGIN; +import static com.demcha.compose.document.templates.invoice.presets.ConsultingStyles.PARTIES_LEFT_WEIGHT; + +/** + * Consulting Invoice — the professional-services invoice preset: a + * corporate masthead over priced service lines, with the bank details and + * the closing notes side by side beneath them. + * + *

    The masthead pairs the sender's brand lockup and contact channels with + * the document title and its metadata; the body bills a recipient, states + * what the invoice covers, and prices the work line by line with a service + * period and a unit against each line; the totals stack closes with an + * emphasized total band. Long invoices flow: the line-items table repeats + * its header on the next page, and the totals stack stays whole.

    + * + *

    The preset owns its session geometry — A4, the margin published as + * {@link #RECOMMENDED_MARGIN}, the near-white page fill and the quiet + * footer band — and draws the legal line and page numbers as footer chrome + * rather than flow content, so they repeat on every page. The legal line is + * composed from the supplier's registered name and registration, so those + * are stated once. A session margin set by the caller is overwritten.

    + * + *

    The preset draws in {@code FontName.POPPINS} and consumes the + * structured invoice model ({@link StructuredInvoiceDocumentSpec}): its + * headings, labels and column titles are content, so the same composition + * prints an invoice in another business's wording without a fork. The + * brand logo arrives through the data as an image the caller owns; a + * document without one falls back to the wordmark lockup. The contact + * marks, the bank badge and the calendar are template chrome and ship + * inside the artifact.

    + */ +public final class ConsultingInvoice { + + /** + * Stable template identifier. + */ + public static final String ID = "invoice-consulting"; + + /** + * Human-readable display name. + */ + public static final String DISPLAY_NAME = "Consulting Invoice"; + + /** + * The session margin (in points) the preset sets. It is published so a + * caller can measure against the same frame; the preset applies it + * itself inside {@code compose}, together with the page size and the + * page fills, so callers leave the session unconfigured. + */ + public static final double RECOMMENDED_MARGIN = ConsultingStyles.PAGE_MARGIN; + + private ConsultingInvoice() { + } + + /** + * Builds the preset. + * + * @return ready-to-use template + */ + public static DocumentTemplate create() { + return new Template(); + } + + private record Template() implements DocumentTemplate { + + @Override + public String id() { + return ID; + } + + @Override + public String displayName() { + return DISPLAY_NAME; + } + + @Override + public void compose(DocumentSession document, StructuredInvoiceDocumentSpec spec) { + Objects.requireNonNull(document, "document"); + StructuredInvoiceData data = Objects.requireNonNull(spec, "spec").invoice(); + + renderChrome(document, data.supplier()); + + document.pageFlow(page -> page + .name("ConsultingInvoice") + .spacing(0) + .addRow("Masthead", row -> row + .gap(HEADER_GAP) + .weights(HEADER_LEFT_WEIGHT, HEADER_RIGHT_WEIGHT) + .verticalAlign(RowVerticalAlign.TOP) + .addSection("SupplierHeader", + section -> ConsultingMasthead.renderSupplier(section, data)) + .addSection("InvoiceHeader", + section -> ConsultingMasthead.renderInvoiceHeader(section, data))) + .addSpacer(spacer -> spacer.height(16)) + .addRow("PartiesAndSummary", row -> row + .gap(35) + .weights(PARTIES_LEFT_WEIGHT, 1.0 - PARTIES_LEFT_WEIGHT) + .verticalAlign(RowVerticalAlign.TOP) + .addSection("BilledTo", + section -> ConsultingBody.renderBilledTo(section, data)) + .addSection("InvoiceSummary", + section -> ConsultingBody.renderSummary(section, data))) + .addSpacer(spacer -> spacer.height(10)) + .addTable(table -> ConsultingBody.renderServiceLines(table, data)) + .addSection("Totals", section -> ConsultingBody.renderTotals(section, data)) + .addLine(line -> line + .horizontal(CONTENT_WIDTH) + .thickness(0.8) + .color(HAIRLINE) + .margin(DocumentInsets.symmetric(5, 0))) + .addRow("PaymentAndNotes", row -> row + .gap(LOWER_GAP) + .weights(LOWER_LEFT_WEIGHT, 1.0 - LOWER_LEFT_WEIGHT) + .verticalAlign(RowVerticalAlign.TOP) + .addSection("PaymentInformation", + section -> ConsultingClosing.renderPayment(section, data)) + .addSection("Notes", + section -> ConsultingClosing.renderNotes(section, data)))); + } + + /** + * The page frame and the band that closes every page: the near-white + * page fill and the quiet footer band are page-ratio background + * fills, and the legal line and page numbers are FOOTER-zone chrome + * drawn after layout, so both repeat on every page of a long + * invoice. + */ + private static void renderChrome(DocumentSession document, InvoiceContactBlock supplier) { + document.pageSize(PAGE) + .margin(DocumentInsets.of(PAGE_MARGIN)) + .pageBackgrounds(List.of( + PageBackgroundFill.fullPage(PAGE_BACKGROUND), + PageBackgroundFill.bottomBand(FOOTER_BAND_RATIO, FOOTER_FILL))) + .footer(DocumentHeaderFooter.builder() + .zone(DocumentHeaderFooterZone.FOOTER) + .height((float) FOOTER_ZONE_HEIGHT) + .centerText(footerLine(supplier)) + .rightText("Page {page} / {pages}") + .fontSize((float) FOOTER_TEXT_SIZE) + .textColor(MUTED) + .showSeparator(false) + .build()); + } + + /** + * The legal line: the supplier's registered name and, when it is + * registered, its labelled registration number. A supplier without + * a registration prints its name alone rather than a name with a + * dangling separator. + */ + private static String footerLine(InvoiceContactBlock supplier) { + String registration = (supplier.registrationLabel() + " " + + supplier.registrationNumber()).trim(); + if (registration.isEmpty()) { + return supplier.legalName(); + } + if (supplier.legalName().isEmpty()) { + return registration; + } + return supplier.legalName() + " | " + registration; + } + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/ConsultingMasthead.java b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/ConsultingMasthead.java new file mode 100644 index 000000000..96ef659ae --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/ConsultingMasthead.java @@ -0,0 +1,220 @@ +package com.demcha.compose.document.templates.invoice.presets; + +import com.demcha.compose.document.dsl.ImageBuilder; +import com.demcha.compose.document.dsl.ParagraphBuilder; +import com.demcha.compose.document.dsl.SectionBuilder; +import com.demcha.compose.document.dsl.ShapeContainerBuilder; +import com.demcha.compose.document.node.DocumentNode; +import com.demcha.compose.document.node.LayerAlign; +import com.demcha.compose.document.node.TextAlign; +import com.demcha.compose.document.style.ClipPolicy; +import com.demcha.compose.document.style.DocumentInsets; +import com.demcha.compose.document.style.DocumentTextStyle; +import com.demcha.compose.document.templates.core.identity.ContactUri; +import com.demcha.compose.document.templates.data.invoice.InvoiceBrand; +import com.demcha.compose.document.templates.data.invoice.InvoiceContactBlock; +import com.demcha.compose.document.templates.data.invoice.InvoiceMasthead; +import com.demcha.compose.document.templates.data.invoice.StructuredInvoiceData; + +import static com.demcha.compose.document.templates.invoice.presets.ConsultingStyles.ACCENT_PRIMARY; +import static com.demcha.compose.document.templates.invoice.presets.ConsultingStyles.BODY; +import static com.demcha.compose.document.templates.invoice.presets.ConsultingStyles.BODY_ACCENT; +import static com.demcha.compose.document.templates.invoice.presets.ConsultingStyles.BRAND_LOGO_BOX_HEIGHT; +import static com.demcha.compose.document.templates.invoice.presets.ConsultingStyles.BRAND_MARK; +import static com.demcha.compose.document.templates.invoice.presets.ConsultingStyles.BRAND_NAME; +import static com.demcha.compose.document.templates.invoice.presets.ConsultingStyles.BRAND_QUALIFIER; +import static com.demcha.compose.document.templates.invoice.presets.ConsultingStyles.CONTACT_ROW_HEIGHT; +import static com.demcha.compose.document.templates.invoice.presets.ConsultingStyles.HAIRLINE; +import static com.demcha.compose.document.templates.invoice.presets.ConsultingStyles.INVOICE_TITLE; +import static com.demcha.compose.document.templates.invoice.presets.ConsultingStyles.INVOICE_WIDTH; +import static com.demcha.compose.document.templates.invoice.presets.ConsultingStyles.METADATA_ROW_HEIGHT; +import static com.demcha.compose.document.templates.invoice.presets.ConsultingStyles.SMALL; +import static com.demcha.compose.document.templates.invoice.presets.ConsultingStyles.SECTION_HEADING; +import static com.demcha.compose.document.templates.invoice.presets.ConsultingStyles.SMALL_BOLD; +import static com.demcha.compose.document.templates.invoice.presets.ConsultingStyles.SUPPLIER_WIDTH; +import static com.demcha.compose.document.templates.invoice.presets.ConsultingText.link; +import static com.demcha.compose.document.templates.invoice.presets.ConsultingText.tracked; + +/** + * The masthead of the Consulting Invoice: the sender's lockup and contact + * channels on the left, the document title and its metadata on the right. + * + *

    Both halves are shape containers with anchored children rather than + * rows, because a row is refused inside a row cell and the masthead itself + * is one row of the page flow.

    + */ +final class ConsultingMasthead { + + private ConsultingMasthead() { + } + + /** The sender's half: brand lockup, then one band per contact channel. */ + static void renderSupplier(SectionBuilder section, StructuredInvoiceData data) { + InvoiceBrand brand = data.brand(); + InvoiceContactBlock supplier = data.supplier(); + + section.spacing(2); + if (brand.hasLogo()) { + section.add(logoLockup(brand)); + } else { + renderTextLockup(section, brand); + } + // A channel the sender did not supply gets no band: an empty band + // would print a mark with nothing beside it, and an empty value has + // no address to link to. + String address = String.join("\n", supplier.addressLines()); + if (!address.isBlank()) { + contactBand(section, ConsultingIcons.LOCATION, 10.0, address, + CONTACT_ROW_HEIGHT + 5.0, null); + } + if (!supplier.phone().isBlank()) { + contactBand(section, ConsultingIcons.PHONE, 9.2, supplier.phone(), + CONTACT_ROW_HEIGHT, ContactUri.tel(supplier.phone())); + } + if (!supplier.email().isBlank()) { + contactBand(section, ConsultingIcons.EMAIL, 9.2, supplier.email(), + CONTACT_ROW_HEIGHT, "mailto:" + supplier.email()); + } + if (!supplier.website().isBlank()) { + contactBand(section, ConsultingIcons.WEBSITE, 9.4, supplier.website(), + CONTACT_ROW_HEIGHT, ContactUri.web(supplier.website())); + } + } + + /** + * The logo in its box. Height-only sizing lets the engine derive the + * width from the image's own metadata, so a wider or narrower logo + * stays on the same baseline as the contact bands below it. + */ + private static DocumentNode logoLockup(InvoiceBrand brand) { + DocumentNode logo = new ImageBuilder() + .name("BrandLogo") + .source(brand.logo()) + .height(BRAND_LOGO_BOX_HEIGHT) + .build(); + return new ShapeContainerBuilder() + .name("BrandLogoBox") + .rectangle(SUPPLIER_WIDTH, BRAND_LOGO_BOX_HEIGHT) + .clipPolicy(ClipPolicy.OVERFLOW_VISIBLE) + .centerLeft(logo) + .build(); + } + + /** + * The wordmark-only lockup a document without a logo falls back to: the + * brand's initial as a mark, the name beside it, the qualifier tracked + * between rules, and the tagline. + */ + private static void renderTextLockup(SectionBuilder section, InvoiceBrand brand) { + String mark = brand.name().isBlank() ? "" : brand.name().substring(0, 1); + section.addParagraph(paragraph -> paragraph + .rich(rich -> rich + .style(mark, BRAND_MARK) + .space() + .style(brand.name(), BRAND_NAME)) + .lineSpacing(1.0) + .margin(DocumentInsets.zero())) + .addParagraph(paragraph -> paragraph + .text(brand.qualifier().isBlank() + ? "" : "— " + tracked(brand.qualifier()) + " —") + .textStyle(BRAND_QUALIFIER) + .align(TextAlign.CENTER) + .margin(DocumentInsets.zero())) + .addParagraph(paragraph -> paragraph + .text(brand.tagline()) + .textStyle(BODY_ACCENT) + .align(TextAlign.CENTER) + .margin(DocumentInsets.symmetric(5, 0))); + } + + /** One contact line: the channel mark, and the value anchored beside it. */ + private static void contactBand(SectionBuilder section, + String iconName, + double iconSize, + String text, + double height, + String linkUri) { + DocumentNode contactText = linkUri == null + ? paragraph(text, SMALL) + : linkedParagraph(text, SMALL, linkUri); + section.addContainer(container -> container + .name("ContactBand") + .rectangle(SUPPLIER_WIDTH, height) + .clipPolicy(ClipPolicy.OVERFLOW_VISIBLE) + .centerLeft(ConsultingIcons.icon(iconName, iconSize)) + .position(contactText, 23, 0, LayerAlign.CENTER_LEFT)); + } + + /** The document half: the tracked title, then one band per metadata row. */ + static void renderInvoiceHeader(SectionBuilder section, StructuredInvoiceData data) { + InvoiceMasthead masthead = data.masthead(); + section.accentLeft(HAIRLINE, 1.1) + .padding(0, 0, 0, 24) + .spacing(1) + .addParagraph(paragraph -> paragraph + .text(tracked(masthead.title())) + .textStyle(INVOICE_TITLE) + .margin(DocumentInsets.bottom(8))); + for (InvoiceMasthead.Entry entry : masthead.entries()) { + metadataBand(section, entry); + } + } + + /** One metadata row: the label at the left, the value at the 48% mark. */ + private static void metadataBand(SectionBuilder section, InvoiceMasthead.Entry entry) { + double width = INVOICE_WIDTH - 24; + section.addContainer(container -> container + .name("MetadataBand") + .rectangle(width, METADATA_ROW_HEIGHT) + .clipPolicy(ClipPolicy.OVERFLOW_VISIBLE) + .centerLeft(paragraph(entry.label(), SMALL_BOLD)) + .position(paragraph(entry.value(), entry.emphasized() ? BODY_ACCENT : BODY), + width * 0.48, + 0, + LayerAlign.CENTER_LEFT)); + } + + /** A plain left-aligned paragraph node on the shared line spacing. */ + static DocumentNode paragraph(String text, DocumentTextStyle style) { + return new ParagraphBuilder() + .text(text) + .textStyle(style) + .align(TextAlign.LEFT) + .lineSpacing(1.15) + .margin(DocumentInsets.zero()) + .build(); + } + + private static DocumentNode linkedParagraph(String text, DocumentTextStyle style, String uri) { + return new ParagraphBuilder() + .text(text) + .textStyle(style) + .align(TextAlign.LEFT) + .link(link(uri)) + .lineSpacing(1.15) + .margin(DocumentInsets.zero()) + .build(); + } + + /** + * The heading every lower region opens with: tracked capitals over a + * short accent rule. + */ + static void sectionHeading(SectionBuilder section, String heading) { + if (heading.isBlank()) { + // No heading, no rule: an orphan rule reads as a divider. + return; + } + section.addParagraph(paragraph -> paragraph + .text(tracked(heading)) + .textStyle(SECTION_HEADING) + .lineSpacing(1.0) + .margin(DocumentInsets.zero())) + .addLine(line -> line + .horizontal(30) + .thickness(1.2) + .color(ACCENT_PRIMARY) + .margin(DocumentInsets.symmetric(4, 0))); + } + +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/ConsultingStyles.java b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/ConsultingStyles.java new file mode 100644 index 000000000..722332b7d --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/ConsultingStyles.java @@ -0,0 +1,149 @@ +package com.demcha.compose.document.templates.invoice.presets; + +import com.demcha.compose.document.api.DocumentPageSize; +import com.demcha.compose.document.style.DocumentColor; +import com.demcha.compose.document.style.DocumentInsets; +import com.demcha.compose.document.style.DocumentStroke; +import com.demcha.compose.document.style.DocumentTextDecoration; +import com.demcha.compose.document.style.DocumentTextStyle; +import com.demcha.compose.document.table.DocumentTableStyle; +import com.demcha.compose.document.table.DocumentTableTextAnchor; +import com.demcha.compose.font.FontName; + +/** + * The Consulting Invoice look: page geometry, the colour roles, the Poppins + * type scale, and the derived widths every region measures against. + * + *

    Preset-local rather than a {@code BrandTheme}: every value below was + * measured against the ported template's reference render, and the type + * scale derives from a single {@link #BODY_SIZE} step that no other family + * shares. A theme slot would add indirection without a second consumer.

    + */ +final class ConsultingStyles { + + private ConsultingStyles() { + } + + // --- page --------------------------------------------------------------- + static final DocumentPageSize PAGE = DocumentPageSize.A4; + static final double PAGE_MARGIN = 30.5; + /** The denominator of every derived width below. */ + static final double CONTENT_WIDTH = PAGE.width() - 2.0 * PAGE_MARGIN; + + // --- band splits, measured from the reference --------------------------- + static final double HEADER_LEFT_WEIGHT = 0.51; + static final double HEADER_RIGHT_WEIGHT = 1.0 - HEADER_LEFT_WEIGHT; + static final double HEADER_GAP = 25.0; + static final double SUPPLIER_WIDTH = (CONTENT_WIDTH - HEADER_GAP) * HEADER_LEFT_WEIGHT; + static final double INVOICE_WIDTH = (CONTENT_WIDTH - HEADER_GAP) * HEADER_RIGHT_WEIGHT; + static final double PARTIES_LEFT_WEIGHT = 0.55; + static final double LOWER_LEFT_WEIGHT = 0.54; + static final double LOWER_GAP = 22.0; + static final double PAYMENT_WIDTH = (CONTENT_WIDTH - LOWER_GAP) * LOWER_LEFT_WEIGHT; + static final double NOTES_WIDTH = (CONTENT_WIDTH - LOWER_GAP) * (1.0 - LOWER_LEFT_WEIGHT); + static final double TOTALS_WIDTH = CONTENT_WIDTH * 0.42; + + /** #, description, service period, qty, unit price, amount. */ + static final double[] TABLE_RATIOS = {0.050, 0.341, 0.218, 0.110, 0.154, 0.127}; + + static final double TABLE_CELL_HORIZONTAL_PADDING = 5.0; + static final double DESCRIPTION_CONTENT_WIDTH = + CONTENT_WIDTH * TABLE_RATIOS[1] - 2.0 * TABLE_CELL_HORIZONTAL_PADDING; + + // --- independent dimensions --------------------------------------------- + static final double CONTACT_ROW_HEIGHT = 13.0; + static final double METADATA_ROW_HEIGHT = 18.0; + static final double FOOTER_BAND_RATIO = 0.039; + static final double FOOTER_ZONE_HEIGHT = 22.0; + static final double FOOTER_TEXT_SIZE = 7.0; + static final double BRAND_LOGO_BOX_HEIGHT = 64.0; + static final double TOTAL_LABEL_LEFT_INSET = 13.0; + static final double TOTAL_AMOUNT_RIGHT_INSET = 10.0; + + // --- type scale, all derived from one step ------------------------------ + static final double BODY_SIZE = 7.6; + static final double SMALL_SIZE = BODY_SIZE * 0.90; + static final double SECTION_SIZE = BODY_SIZE * 1.15; + static final double TITLE_SIZE = BODY_SIZE * 3.10; + static final double TOTAL_SIZE = BODY_SIZE * 1.80; + + // --- colour roles: named for the job, not the hue ----------------------- + static final DocumentColor PAGE_BACKGROUND = DocumentColor.rgb(254, 254, 254); + static final DocumentColor ACCENT_PRIMARY = DocumentColor.rgb(7, 86, 86); + static final DocumentColor INK = DocumentColor.rgb(35, 43, 54); + static final DocumentColor MUTED = DocumentColor.rgb(83, 91, 100); + static final DocumentColor ROW_ALT = DocumentColor.rgb(248, 248, 248); + static final DocumentColor EMPHASIS_FILL = DocumentColor.rgb(237, 246, 245); + static final DocumentColor FOOTER_FILL = DocumentColor.rgb(238, 246, 246); + static final DocumentColor HAIRLINE = DocumentColor.rgb(225, 229, 229); + + static final FontName TEXT_FONT = FontName.POPPINS; + + // --- text roles ---------------------------------------------------------- + static final DocumentTextStyle BODY = style(BODY_SIZE, DocumentTextDecoration.DEFAULT, INK); + static final DocumentTextStyle BODY_BOLD = style(BODY_SIZE, DocumentTextDecoration.BOLD, INK); + static final DocumentTextStyle BODY_ACCENT = + style(BODY_SIZE, DocumentTextDecoration.BOLD, ACCENT_PRIMARY); + static final DocumentTextStyle BODY_LINK = + style(BODY_SIZE, DocumentTextDecoration.DEFAULT, ACCENT_PRIMARY); + static final DocumentTextStyle SMALL = style(SMALL_SIZE, DocumentTextDecoration.DEFAULT, INK); + static final DocumentTextStyle SMALL_BOLD = style(SMALL_SIZE, DocumentTextDecoration.BOLD, INK); + static final DocumentTextStyle SMALL_ITALIC = + style(SMALL_SIZE, DocumentTextDecoration.ITALIC, INK); + static final DocumentTextStyle NOTICE_BOLD = + style(BODY_SIZE * 0.76, DocumentTextDecoration.BOLD, INK); + static final DocumentTextStyle NOTICE_ACCENT = + style(BODY_SIZE * 0.76, DocumentTextDecoration.BOLD, ACCENT_PRIMARY); + static final DocumentTextStyle SECTION_HEADING = + style(SECTION_SIZE, DocumentTextDecoration.BOLD, ACCENT_PRIMARY); + static final DocumentTextStyle INVOICE_TITLE = + style(TITLE_SIZE, DocumentTextDecoration.BOLD, ACCENT_PRIMARY); + static final DocumentTextStyle BRAND_NAME = + style(BODY_SIZE * 2.45, DocumentTextDecoration.BOLD, INK); + static final DocumentTextStyle BRAND_MARK = + style(BODY_SIZE * 3.25, DocumentTextDecoration.BOLD, ACCENT_PRIMARY); + static final DocumentTextStyle BRAND_QUALIFIER = + style(BODY_SIZE * 1.20, DocumentTextDecoration.DEFAULT, INK); + static final DocumentTextStyle TABLE_HEADER = + style(SMALL_SIZE, DocumentTextDecoration.BOLD, DocumentColor.WHITE); + static final DocumentTextStyle TOTAL_AMOUNT = + style(TOTAL_SIZE, DocumentTextDecoration.BOLD, ACCENT_PRIMARY); + + /** + * One line-item cell style: the zebra fill it sits on and where its value + * is anchored. + * + * @param fill the row fill + * @param anchor where the value sits in the cell + * @return the cell style + */ + static DocumentTableStyle tableStyle(DocumentColor fill, DocumentTableTextAnchor anchor) { + return DocumentTableStyle.builder() + .padding(DocumentInsets.symmetric(4.2, TABLE_CELL_HORIZONTAL_PADDING)) + .fillColor(fill) + .stroke(DocumentStroke.of(HAIRLINE, 0.55)) + .textStyle(BODY) + .textAnchor(anchor) + .lineSpacing(1.15) + .build(); + } + + /** + * Composes a text style in the preset's face. + * + * @param size type size in points + * @param decoration weight or slant + * @param color ink colour + * @return the composed style + */ + static DocumentTextStyle style(double size, + DocumentTextDecoration decoration, + DocumentColor color) { + return DocumentTextStyle.builder() + .fontName(TEXT_FONT) + .size(size) + .decoration(decoration) + .color(color) + .build(); + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/ConsultingText.java b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/ConsultingText.java new file mode 100644 index 000000000..be22cabac --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/ConsultingText.java @@ -0,0 +1,146 @@ +package com.demcha.compose.document.templates.invoice.presets; + +import com.demcha.compose.document.node.DocumentLinkOptions; + +import java.awt.Font; +import java.awt.font.FontRenderContext; +import java.io.InputStream; +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.text.NumberFormat; +import java.util.Locale; +import java.util.Objects; + +import static com.demcha.compose.document.templates.invoice.presets.ConsultingStyles.DESCRIPTION_CONTENT_WIDTH; +import static com.demcha.compose.document.templates.invoice.presets.ConsultingStyles.SMALL_SIZE; + +/** + * The text and number utilities the Consulting Invoice regions share: + * money and quantity formatting, link URIs, and the measurement behind the + * line-item title breaker. + */ +final class ConsultingText { + + /** + * The face the line-item title is measured in. + * + *

    The templates artifact does not carry it. The + * face ships in {@code graph-compose-fonts}, which this module does not + * depend on, so a caller that adds only the templates artifact measures + * in the platform's {@code SansSerif} instead — a different width, and + * one that differs between operating systems. That caller also renders + * the document in a substituted face, so the page is already not the + * designed one; what the fallback adds is that the description column's + * break lands differently per platform. Put the font artifact on the + * classpath to get the designed render.

    + */ + private static final Font DESCRIPTION_BOLD_FONT = loadDescriptionBoldFont(); + + private static final FontRenderContext FONT_RENDER_CONTEXT = + new FontRenderContext(null, true, true); + + private ConsultingText() { + } + + /** + * Width of the invisible breaker that pushes a line item's description + * onto its own line: what is left of the description column once the + * title has been set. + * + *

    A rich run cannot carry a line break — the engine strips a newline + * inside one — so the description column ends its title run with a + * zero-height rectangle wide enough to fill the line instead. Sizing + * that rectangle needs the rendered title width, which is measured here + * rather than laid out.

    + * + * @param title the line-item title + * @return the breaker width, never below one point + */ + static double titleBreakerWidth(String title) { + double titleWidth = DESCRIPTION_BOLD_FONT + .deriveFont((float) SMALL_SIZE) + .getStringBounds(Objects.requireNonNullElse(title, ""), FONT_RENDER_CONTEXT) + .getWidth(); + return Math.max(1.0, DESCRIPTION_CONTENT_WIDTH - titleWidth - 4.0); + } + + private static Font loadDescriptionBoldFont() { + try (InputStream stream = ConsultingText.class.getClassLoader() + .getResourceAsStream("fonts/google/poppins/Poppins-Bold.ttf")) { + if (stream != null) { + return Font.createFont(Font.TRUETYPE_FONT, stream); + } + } catch (Exception cause) { + // A present-but-unreadable font is worth saying out loud; a + // missing one is the documented fallback below. + System.getLogger(ConsultingText.class.getName()) + .log(System.Logger.Level.WARNING, + "Falling back to the platform face: the packaged Poppins Bold " + + "could not be read", + cause); + } + return new Font("SansSerif", Font.BOLD, 1); + } + + /** + * Spaces a value out into tracked capitals (the heading treatment). + * + * @param value the text, already in the case it is set in + * @return the tracked text + */ + static String tracked(String value) { + StringBuilder tracked = new StringBuilder(value.length() * 2); + for (int index = 0; index < value.length(); index++) { + if (index > 0) { + tracked.append(' '); + } + tracked.append(value.charAt(index)); + } + return tracked.toString(); + } + + /** + * Formats a quantity at two decimal places, without grouping. + * + * @param value the quantity + * @return the formatted quantity + */ + static String decimal(BigDecimal value) { + return value.setScale(2, RoundingMode.HALF_UP).toPlainString(); + } + + /** + * Formats an amount with thousands grouping and two decimal places. The + * currency itself is printed in the column header, not per figure. + * + * @param value the amount + * @return the formatted amount + */ + static String money(BigDecimal value) { + NumberFormat format = NumberFormat.getNumberInstance(Locale.ENGLISH); + format.setGroupingUsed(true); + format.setMinimumFractionDigits(2); + format.setMaximumFractionDigits(2); + return format.format(value); + } + + /** + * Wraps a URI as link options. + * + * @param uri the target + * @return the link options + */ + static DocumentLinkOptions link(String uri) { + return new DocumentLinkOptions(uri); + } + + /** + * Keeps a printed phone number on one line. + * + * @param phone the printed number + * @return the number with non-breaking spaces + */ + static String nonBreakingPhone(String phone) { + return phone.replace(' ', '\u00A0'); + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/LumaStudioClosing.java b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/LumaStudioClosing.java new file mode 100644 index 000000000..aed7054c3 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/LumaStudioClosing.java @@ -0,0 +1,269 @@ +package com.demcha.compose.document.templates.invoice.presets; + +import com.demcha.compose.document.dsl.LineBuilder; +import com.demcha.compose.document.dsl.SectionBuilder; +import com.demcha.compose.document.dsl.ShapeBuilder; +import com.demcha.compose.document.node.DocumentNode; +import com.demcha.compose.document.node.LayerAlign; +import com.demcha.compose.document.node.TextAlign; +import com.demcha.compose.document.style.ClipPolicy; +import com.demcha.compose.document.style.DocumentInsets; +import com.demcha.compose.document.templates.data.invoice.InvoiceNotesBlock; +import com.demcha.compose.document.templates.data.invoice.InvoicePaymentBlock; +import com.demcha.compose.document.templates.data.invoice.InvoiceTotalsBlock; + +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioStyles.ACCENT; +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioStyles.BANK_LABEL; +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioStyles.BANK_PIPE_OFFSET; +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioStyles.BANK_ROW_HEIGHT; +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioStyles.BANK_VALUE; +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioStyles.BANK_VALUE_OFFSET; +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioStyles.BANNER_CONTENT_INSET; +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioStyles.BANNER_DISC_ICON_SIZE; +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioStyles.BANNER_DISC_SIZE; +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioStyles.BANNER_DUE; +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioStyles.BANNER_FLOW_HEIGHT; +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioStyles.BANNER_HEIGHT; +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioStyles.BANNER_SIGN_OFF; +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioStyles.BANNER_TEXT_OFFSET; +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioStyles.CLOSING_DIVIDER_INSET; +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioStyles.CLOSING_LEFT_WEIGHT; +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioStyles.CONTENT_WIDTH; +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioStyles.HAIRLINE; +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioStyles.HAIRLINE_QUIET; +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioStyles.INK_SURFACE; +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioStyles.LINE_PITCH; +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioStyles.NOTE_TEXT; +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioStyles.PAGE; +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioStyles.PAGE_MARGIN_LEFT; +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioStyles.PAGE_MARGIN_RIGHT; +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioStyles.PANEL_HIGHLIGHT; +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioStyles.PAPER; +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioStyles.RULE_THICKNESS; +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioStyles.SECTION_HEADING_OFFSET; +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioStyles.SMALL_BOLD; +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioStyles.TABLE_TO_TOTALS; +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioStyles.TOTALS_AMOUNT_INSET; +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioStyles.TOTALS_LABEL_INSET; +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioStyles.TOTALS_ROW_HEIGHT; +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioStyles.TOTALS_TO_RULE; +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioStyles.TOTALS_WIDTH; +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioStyles.TOTAL_AMOUNT; +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioStyles.TOTAL_DUE_AMOUNT; +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioStyles.TOTAL_DUE_HEIGHT; +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioStyles.TOTAL_DUE_LABEL; +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioStyles.TOTAL_LABEL; +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioStyles.TRACK_TOTAL_LABEL; +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioStyles.leading; +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioStyles.money; +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioWidgets.disc; +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioWidgets.paragraph; +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioWidgets.sectionHead; +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioWidgets.tracked; + +/** + * What closes the sheet: the totals stack, the notes and payment pair, and + * the sign-off band. + */ +final class LumaStudioClosing { + + private LumaStudioClosing() { + } + + // -- totals ---------------------------------------------------------- + + /** + * The totals stack, right-aligned by its own left margin, closing on the + * filled total-due band. + */ + static void renderTotals(SectionBuilder section, InvoiceTotalsBlock totals, + String currencySymbol) { + section.name("Totals") + .keepTogether() + .spacing(0) + .margin(new DocumentInsets( + TABLE_TO_TOTALS, 0, 0, CONTENT_WIDTH - TOTALS_WIDTH)); + for (InvoiceTotalsBlock.Row row : totals.rows()) { + totalsRow(section, row.label(), money(currencySymbol, row.amount()), false); + } + totalsRow(section, totals.totalLabel(), + money(currencySymbol, totals.totalAmount()), true); + } + + private static void totalsRow(SectionBuilder section, String label, String amount, + boolean emphasised) { + section.addContainer(container -> { + container.name(emphasised ? "TotalDueBand" : "TotalsRow") + .rectangle(TOTALS_WIDTH, emphasised ? TOTAL_DUE_HEIGHT : TOTALS_ROW_HEIGHT) + .clipPolicy(ClipPolicy.OVERFLOW_VISIBLE) + .position(tracked("TotalsLabel", label, + emphasised ? TOTAL_DUE_LABEL : TOTAL_LABEL, + TRACK_TOTAL_LABEL, + emphasised ? PANEL_HIGHLIGHT : PAPER), + TOTALS_LABEL_INSET, 0, LayerAlign.CENTER_LEFT) + .position(paragraph(amount, + emphasised ? TOTAL_DUE_AMOUNT : TOTAL_AMOUNT, + TextAlign.RIGHT), + -TOTALS_AMOUNT_INSET, 0, LayerAlign.CENTER_RIGHT); + if (emphasised) { + container.fillColor(PANEL_HIGHLIGHT) + .margin(new DocumentInsets(LINE_PITCH * 0.23, 0, 0, 0)); + } + }); + } + + /** The hairline that closes the totals and opens the pair below. */ + static void renderFooterRule(LineBuilder line) { + line.name("FooterRule") + .horizontal(CONTENT_WIDTH) + .thickness(RULE_THICKNESS) + .color(HAIRLINE) + .margin(new DocumentInsets(TOTALS_TO_RULE, 0, 0, 0)); + } + + // -- notes and payment ----------------------------------------------- + + /** The notes column: the disc heading, then the paragraphs under it. */ + static void renderNotes(SectionBuilder section, InvoiceNotesBlock notes) { + section.name("Notes").keepTogether().spacing(0); + sectionHead(section, LumaStudioIcons.NOTES, notes.heading(), + CONTENT_WIDTH * CLOSING_LEFT_WEIGHT); + SectionBuilder body = new SectionBuilder() + .name("NotesBody") + .spacing(LINE_PITCH * 0.75) + .padding(new DocumentInsets(0, 0, 0, SECTION_HEADING_OFFSET)) + .margin(new DocumentInsets(LINE_PITCH * 0.18, 0, 0, 0)); + for (String note : notes.paragraphs()) { + body.addParagraph(p -> p + .text(note) + .textStyle(NOTE_TEXT) + .lineSpacing(leading(LINE_PITCH * 0.91, NOTE_TEXT)) + .margin(DocumentInsets.zero())); + } + section.add(body.build()); + } + + /** + * The payment column: the disc heading, the instruction, who is paid, and + * the bank fields as label / pipe / value on one axis. + * + *

    The divider between the two columns is this column's own left + * border, so it takes its height from what this column holds.

    + */ + static void renderPayment(SectionBuilder section, InvoicePaymentBlock payment) { + section.name("PaymentDetails") + .keepTogether() + .spacing(0) + .accentLeft(HAIRLINE_QUIET, RULE_THICKNESS) + .padding(new DocumentInsets(0, 0, 0, CLOSING_DIVIDER_INSET)); + double blockWidth = CONTENT_WIDTH * (1.0 - CLOSING_LEFT_WEIGHT) - CLOSING_DIVIDER_INSET; + sectionHead(section, LumaStudioIcons.BANK, payment.heading(), blockWidth); + + SectionBuilder body = new SectionBuilder() + .name("PaymentBody") + .spacing(0) + .padding(new DocumentInsets(0, 0, 0, SECTION_HEADING_OFFSET)) + .margin(new DocumentInsets(LINE_PITCH * 0.18, 0, 0, 0)); + body.addParagraph(p -> p + .name("PaymentInstruction") + .text(payment.instruction()) + .textStyle(NOTE_TEXT) + .lineSpacing(0) + .margin(DocumentInsets.zero())) + .addParagraph(p -> p + .name("AccountHolder") + .text(payment.accountHolder()) + .textStyle(SMALL_BOLD) + .lineSpacing(0) + .margin(new DocumentInsets( + LINE_PITCH * 0.45, 0, LINE_PITCH * 0.55, 0))); + + double fieldWidth = blockWidth - SECTION_HEADING_OFFSET; + for (InvoicePaymentBlock.Field field : payment.fields()) { + body.addContainer(container -> container + .name("PaymentField") + .rectangle(fieldWidth, BANK_ROW_HEIGHT) + .clipPolicy(ClipPolicy.OVERFLOW_VISIBLE) + .centerLeft(paragraph(field.label(), BANK_LABEL, TextAlign.LEFT)) + .position(pipe(), BANK_PIPE_OFFSET, 0, LayerAlign.CENTER_LEFT) + .position(paragraph(field.value(), BANK_VALUE, TextAlign.LEFT), + BANK_VALUE_OFFSET, 0, LayerAlign.CENTER_LEFT)); + } + section.add(body.build()); + } + + private static DocumentNode pipe() { + return new LineBuilder() + .name("BankPipe") + .vertical(BANK_ROW_HEIGHT * 0.66) + .thickness(0.8) + .color(ACCENT) + .margin(DocumentInsets.zero()) + .build(); + } + + // -- the closing band ------------------------------------------------ + + /** + * The sign-off band: the dark strip, the heart on its disc, and the two + * closing lines. + * + *

    The strip is drawn by this block rather than left to the page + * background that closes every page. On a one-page invoice the two + * coincide exactly — the flow ends at the foot, so the block lands on the + * background band and nothing about the sheet changes. On a longer one the + * flow ends wherever the closing blocks end, and a block that relied on the + * background would set white words on pale paper. Carrying its own ground + * is what makes the sign-off legible on whatever page it lands.

    + * + *

    The strip bleeds to both paper edges through negative side margins, + * and the ground is a layer rather than the container's fill: a container's + * own fill is dropped under a large negative margin. The block's height in + * the flow is almost nothing — it reaches down into the margin the page + * reserves for the band instead of pushing the flow.

    + * + *

    It is flow content only because footer chrome carries text today: one + * size and one colour across three slots, and no glyph, where this band + * needs two faces and a disc. Once a footer zone can hold a node, the + * sign-off belongs in one — pinned to the foot of the last page, where the + * design draws it — and this block and its strip go away.

    + */ + static void renderBanner(SectionBuilder section, InvoicePaymentBlock payment) { + section.name("ClosingBanner").keepTogether().spacing(0); + SectionBuilder lines = new SectionBuilder().name("BannerLines").spacing(0); + lines.addParagraph(p -> p + .name("SignOff") + .text(payment.signOff()) + .textStyle(BANNER_SIGN_OFF) + .lineSpacing(0) + .margin(DocumentInsets.zero())) + .addParagraph(p -> p + .name("DueNotice") + .text(payment.dueNotice()) + .textStyle(BANNER_DUE) + .lineSpacing(0) + .margin(new DocumentInsets(LINE_PITCH * 0.25, 0, 0, 0))); + section.addContainer(container -> container + .name("BannerContent") + .rectangle(PAGE.width(), BANNER_HEIGHT) + .clipPolicy(ClipPolicy.OVERFLOW_VISIBLE) + .margin(new DocumentInsets( + 0, -PAGE_MARGIN_RIGHT, BANNER_FLOW_HEIGHT - BANNER_HEIGHT, + -PAGE_MARGIN_LEFT)) + .position(band(), 0, 0, LayerAlign.TOP_LEFT, 0) + .position(disc(BANNER_DISC_SIZE, LumaStudioIcons.HEART, BANNER_DISC_ICON_SIZE), + BANNER_CONTENT_INSET, 0, LayerAlign.CENTER_LEFT, 1) + .position(lines.build(), BANNER_CONTENT_INSET + BANNER_TEXT_OFFSET, 0, + LayerAlign.CENTER_LEFT, 1)); + } + + /** The strip the sign-off is set on, paper edge to paper edge. */ + private static DocumentNode band() { + return new ShapeBuilder() + .name("BannerBand") + .size(PAGE.width(), BANNER_HEIGHT) + .fillColor(INK_SURFACE) + .margin(DocumentInsets.zero()) + .build(); + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/LumaStudioIcons.java b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/LumaStudioIcons.java new file mode 100644 index 000000000..671fbdfc2 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/LumaStudioIcons.java @@ -0,0 +1,65 @@ +package com.demcha.compose.document.templates.invoice.presets; + +import com.demcha.compose.document.node.DocumentNode; +import com.demcha.compose.document.svg.SvgIcon; + +import java.io.IOException; +import java.io.InputStream; +import java.io.UncheckedIOException; +import java.nio.charset.StandardCharsets; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Classpath loader for the Luma Studio invoice marks: the three contact + * glyphs, the two section discs, the closing heart and the sidebar sprig. + * + *

    They ship inside the templates artifact under + * {@code templates/invoice/luma-studio/icons/} as SVG, so they scale with the + * box they are drawn into rather than being resampled. Parsed icons are + * cached per token, so a repeated mark reads its file once per JVM.

    + * + *

    Every mark here is chrome rather than data — this sheet draws the same + * ones whatever the invoice says — so the tokens are constants and no + * document names one.

    + */ +final class LumaStudioIcons { + + static final String PHONE = "phone"; + static final String EMAIL = "email"; + static final String WEBSITE = "website"; + static final String NOTES = "notes"; + static final String BANK = "bank"; + static final String HEART = "heart"; + static final String SPRIG = "sprig"; + + private static final String ICON_ROOT = "/templates/invoice/luma-studio/icons/"; + private static final Map CACHE = new ConcurrentHashMap<>(); + + private LumaStudioIcons() { + } + + /** + * Builds one mark at a given width; the height follows its ratio. + * + * @param token one of the constants on this class + * @param width the drawn width + * @return the icon node + */ + static DocumentNode icon(String token, double width) { + return CACHE.computeIfAbsent(token, LumaStudioIcons::read).node(width); + } + + private static SvgIcon read(String token) { + String resourcePath = ICON_ROOT + token + ".svg"; + try (InputStream input = LumaStudioIcons.class.getResourceAsStream(resourcePath)) { + if (input == null) { + throw new IllegalStateException("Missing luma studio invoice icon: " + resourcePath); + } + return SvgIcon.parse(new String(input.readAllBytes(), StandardCharsets.UTF_8)); + } catch (IOException e) { + throw new UncheckedIOException( + "Failed to read luma studio invoice icon: " + resourcePath, e); + } + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/LumaStudioInvoice.java b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/LumaStudioInvoice.java new file mode 100644 index 000000000..6a3a16f9a --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/LumaStudioInvoice.java @@ -0,0 +1,223 @@ +package com.demcha.compose.document.templates.invoice.presets; + +import com.demcha.compose.document.api.DocumentSession; +import com.demcha.compose.document.api.PageBackgroundFill; +import com.demcha.compose.document.output.DocumentHeaderFooter; +import com.demcha.compose.document.output.DocumentHeaderFooterZone; +import com.demcha.compose.document.style.DocumentInsets; +import com.demcha.compose.document.templates.api.DocumentTemplate; +import com.demcha.compose.document.templates.data.invoice.StructuredInvoiceData; +import com.demcha.compose.document.templates.data.invoice.StructuredInvoiceDocumentSpec; + +import java.util.Currency; +import java.util.List; +import java.util.Locale; +import java.util.Objects; + +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioStyles.BANNER_HEIGHT; +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioStyles.BANNER_HEIGHT_RATIO; +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioStyles.CLOSING_LEFT_WEIGHT; +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioStyles.CLOSING_TO_BANNER; +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioStyles.FOLIO_ZONE_HEIGHT; +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioStyles.INK_SURFACE; +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioStyles.MASTHEAD_LEFT_WEIGHT; +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioStyles.MASTHEAD_RIGHT_WEIGHT; +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioStyles.MICRO_SIZE; +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioStyles.ON_DARK; +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioStyles.PAGE; +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioStyles.PAGE_MARGIN_LEFT; +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioStyles.PAGE_MARGIN_RIGHT; +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioStyles.PAGE_MARGIN_TOP; +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioStyles.PAPER; +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioStyles.PARTIES_LEFT_WEIGHT; +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioStyles.PARTIES_TO_TABLE; +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioStyles.RULE_TO_CLOSING; +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioStyles.RULE_TO_PARTIES; +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioStyles.SIDEBAR_SURFACE; +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioStyles.SIDEBAR_WIDTH_RATIO; + +/** + * Luma Studio Invoice — a studio invoice built around a cream sidebar: the + * brand lockup sits on a terracotta block at the top of that column, an + * ornament runs down the rest of it, and the billing sheet is set in the + * page beside them. + * + *

    The sheet reads top to bottom: the sender over the document title and + * its metadata, the two parties across a rule, the priced service lines, the + * totals stack closing on a filled total-due band, and the notes and payment + * details side by side above the sign-off band at the foot.

    + * + *

    Long invoices flow. The line-items table repeats its dark header on the + * next page, the totals stack and each closing block stay whole, and the paper + * tint, the cream sidebar column and the dark foot band are page backgrounds, + * so every page carries the same frame and the folio always has a dark ground. + * Every page reserves the band's height as bottom margin, so no row runs under + * it.

    + * + *

    The sign-off carries its own strip rather than relying on that foot band. + * On a one-page invoice the two land together; on a longer one the flow ends + * where the closing blocks end, and a sign-off that relied on the background + * would set white words on pale paper. It is flow content only because footer + * chrome carries text today and this band needs two faces and a disc — once a + * footer zone can hold a node, the sign-off belongs in one.

    + * + *

    The preset owns its session geometry — A4, the margins, the page fills + * and the folio zone — so callers leave the session unconfigured; a margin + * set by the caller is overwritten. It consumes the structured invoice model + * ({@link StructuredInvoiceDocumentSpec}): every heading, label and column + * title is content, so the same composition prints an invoice in another + * studio's wording without a fork. Amounts are prefixed with the mark of the + * data's own currency code. The preset draws in Carlito, Spectral and Tinos; + * its marks — the contact icons, the two closing-block badges, the heart on + * the band and the sidebar sprig — are template chrome and ship inside the + * artifact.

    + * + * @since 2.4.0 + */ +public final class LumaStudioInvoice { + + /** + * Stable template identifier. + */ + public static final String ID = "invoice-luma-studio"; + + /** + * Human-readable display name. + */ + public static final String DISPLAY_NAME = "Luma Studio Invoice"; + + private LumaStudioInvoice() { + } + + /** + * Builds the preset. + * + * @return ready-to-use template + */ + public static DocumentTemplate create() { + return new Template(); + } + + private record Template() implements DocumentTemplate { + + @Override + public String id() { + return ID; + } + + @Override + public String displayName() { + return DISPLAY_NAME; + } + + @Override + public void compose(DocumentSession document, StructuredInvoiceDocumentSpec spec) { + Objects.requireNonNull(document, "document"); + StructuredInvoiceData data = Objects.requireNonNull(spec, "spec").invoice(); + String currency = currencyMark(data.currencyCode()); + + renderChrome(document); + + document.pageFlow(page -> { + page.name("LumaStudioInvoice").spacing(0).padding(DocumentInsets.zero()); + + page.add(LumaStudioSidebar.render(data.brand())); + + page.addRow("Masthead", row -> row + .spacing(0) + .gap(0) + .weights(MASTHEAD_LEFT_WEIGHT, MASTHEAD_RIGHT_WEIGHT) + .addSection("SupplierHeader", + section -> LumaStudioMasthead.renderSupplier(section, data.supplier())) + .addSection("InvoiceHeader", + section -> LumaStudioMasthead.renderTitle(section, data.masthead()))); + + page.addLine(LumaStudioMasthead::renderRule); + + // The gap down to the table is this row's bottom margin rather + // than a spacer: a block of its own would take the parties' + // place at a page break and leave the gap stranded. + page.addRow("Parties", row -> row + .spacing(0) + .gap(0) + .weights(PARTIES_LEFT_WEIGHT, 1.0 - PARTIES_LEFT_WEIGHT) + .margin(new DocumentInsets(RULE_TO_PARTIES, 0, PARTIES_TO_TABLE, 0)) + .addSection("BillTo", + section -> LumaStudioMasthead.renderParty(section, data.billTo(), false)) + .addSection("ShipTo", + section -> LumaStudioMasthead.renderParty(section, data.shipTo(), true))); + + LumaStudioLines.render(page, data.serviceLines(), currency); + + page.addSection("Totals", + section -> LumaStudioClosing.renderTotals(section, data.totals(), currency)); + + page.addLine(LumaStudioClosing::renderFooterRule); + + page.addRow("Closing", row -> row + .spacing(0) + .gap(0) + .weights(CLOSING_LEFT_WEIGHT, 1.0 - CLOSING_LEFT_WEIGHT) + .margin(new DocumentInsets(RULE_TO_CLOSING, 0, 0, 0)) + .addSection("Notes", + section -> LumaStudioClosing.renderNotes(section, data.notes())) + .addSection("PaymentDetails", + section -> LumaStudioClosing.renderPayment(section, data.payment()))); + + page.addSection("ClosingBannerBlock", block -> { + block.spacing(0).margin(new DocumentInsets(CLOSING_TO_BANNER, 0, 0, 0)); + LumaStudioClosing.renderBanner(block, data.payment()); + }); + }); + } + + /** + * The page frame: the paper tint, the cream sidebar column and the + * dark foot band are page-ratio background fills, and the folio is + * FOOTER-zone chrome, so all four repeat on every page. + * + *

    The bottom margin is the band's height, so the last row on a page + * stops above it rather than running underneath, and the sign-off block + * reaches down into that reserved space by its own negative bottom + * margin rather than pushing the flow onto another page.

    + */ + private static void renderChrome(DocumentSession document) { + document.pageSize(PAGE) + .margin(new DocumentInsets( + PAGE_MARGIN_TOP, PAGE_MARGIN_RIGHT, BANNER_HEIGHT, PAGE_MARGIN_LEFT)) + .pageBackgrounds(List.of( + PageBackgroundFill.fullPage(PAPER), + PageBackgroundFill.leftColumn(SIDEBAR_WIDTH_RATIO, SIDEBAR_SURFACE), + PageBackgroundFill.bottomBand(BANNER_HEIGHT_RATIO, INK_SURFACE))) + .footer(DocumentHeaderFooter.builder() + .zone(DocumentHeaderFooterZone.FOOTER) + .height((float) FOLIO_ZONE_HEIGHT) + .rightText("Page {page} of {pages}") + .fontSize((float) MICRO_SIZE) + .textColor(ON_DARK) + .showSeparator(false) + .build()); + } + + /** + * The mark amounts are written with, for the currency the data states. + * + *

    The model carries the ISO code, and this sheet prints a symbol + * against every figure, so the code is the authority and the symbol is + * derived from it — two stated fields could disagree, and then the + * document would contradict itself. A code the platform does not know + * prints as itself rather than as nothing, so an amount is never left + * bare.

    + */ + private static String currencyMark(String currencyCode) { + if (currencyCode.isBlank()) { + return ""; + } + try { + return Currency.getInstance(currencyCode).getSymbol(Locale.ENGLISH); + } catch (IllegalArgumentException unknownCode) { + return currencyCode; + } + } + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/LumaStudioLines.java b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/LumaStudioLines.java new file mode 100644 index 000000000..d1a2fc6d3 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/LumaStudioLines.java @@ -0,0 +1,182 @@ +package com.demcha.compose.document.templates.invoice.presets; + +import com.demcha.compose.document.dsl.PageFlowBuilder; +import com.demcha.compose.document.dsl.SectionBuilder; +import com.demcha.compose.document.dsl.TableBuilder; +import com.demcha.compose.document.node.DocumentNode; +import com.demcha.compose.document.node.TextAlign; +import com.demcha.compose.document.node.TextVerticalAlign; +import com.demcha.compose.document.style.DocumentInsets; +import com.demcha.compose.document.style.DocumentStroke; +import com.demcha.compose.document.style.DocumentTextDecoration; +import com.demcha.compose.document.table.DocumentTableCell; +import com.demcha.compose.document.table.DocumentTableColumn; +import com.demcha.compose.document.table.DocumentTableStyle; +import com.demcha.compose.document.table.DocumentTableTextAnchor; +import com.demcha.compose.document.templates.data.invoice.InvoiceServiceLines; + +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioStyles.BODY; +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioStyles.BODY_BOLD; +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioStyles.CONTENT_WIDTH; +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioStyles.HAIRLINE; +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioStyles.INK_SURFACE; +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioStyles.ITEM_DESC; +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioStyles.ITEM_INDEX; +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioStyles.ITEM_TITLE; +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioStyles.LINE_PITCH; +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioStyles.PAD_AMOUNT; +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioStyles.PAD_DESCRIPTION; +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioStyles.PAD_HEADER_CENTRE; +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioStyles.PAD_HEADER_LEFT; +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioStyles.PAD_HEADER_RIGHT; +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioStyles.PAD_INDEX; +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioStyles.PAD_NUMERIC; +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioStyles.TABLE_HEADER; +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioStyles.TABLE_RATIOS; +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioStyles.TABLE_RULE_THICKNESS; +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioStyles.TRACK_TABLE_HEADER; +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioStyles.cellStyle; +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioStyles.headerStyle; +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioStyles.leading; +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioStyles.money; +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioStyles.sans; +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioWidgets.tracked; + +/** + * The line-items table: a dark header band, one row per service line, and a + * hairline row between neighbours. + */ +final class LumaStudioLines { + + private LumaStudioLines() { + } + + static void render(PageFlowBuilder page, InvoiceServiceLines serviceLines, + String currencySymbol) { + page.addTable(table -> { + compose(table, serviceLines, currencySymbol); + // The dark header band is the region a continuation page needs: + // without this a second page carries five unlabelled money columns. + table.repeatHeader(); + }); + } + + private static void compose(TableBuilder table, InvoiceServiceLines serviceLines, + String currencySymbol) { + InvoiceServiceLines.Columns columns = serviceLines.columns(); + DocumentTableStyle centred = headerStyle( + DocumentTableTextAnchor.CENTER, PAD_HEADER_CENTRE); + table.name("LineItems") + .columns(column(0), column(1), column(2), column(3), column(4), column(5)) + .headerCells( + headerCell(columns.description(), TextAlign.LEFT) + .withStyle(headerStyle( + DocumentTableTextAnchor.CENTER_LEFT, PAD_HEADER_LEFT)) + .colSpan(2), + headerCell(columns.quantity(), TextAlign.CENTER), + headerCell(columns.unitPrice(), TextAlign.CENTER), + headerCell(columns.vat(), TextAlign.CENTER), + headerCell(columns.amount(), TextAlign.RIGHT) + .withStyle(headerStyle( + DocumentTableTextAnchor.CENTER_RIGHT, PAD_HEADER_RIGHT))) + .headerStyle(centred) + .padding(DocumentInsets.zero()) + .margin(DocumentInsets.zero()); + + for (InvoiceServiceLines.Line line : serviceLines.lines()) { + table.rowCells( + DocumentTableCell.text(String.format("%02d", line.lineNumber())) + .withStyle(cellStyle(ITEM_INDEX, + DocumentTableTextAnchor.CENTER_LEFT, PAD_INDEX)), + DocumentTableCell.node(description(line)) + .withStyle(cellStyle(BODY, + DocumentTableTextAnchor.CENTER_LEFT, PAD_DESCRIPTION)), + DocumentTableCell.text(quantity(line)) + .withStyle(cellStyle(BODY, + DocumentTableTextAnchor.CENTER, PAD_NUMERIC)), + DocumentTableCell.text(money(currencySymbol, line.unitPrice())) + .withStyle(cellStyle(BODY, + DocumentTableTextAnchor.CENTER, PAD_NUMERIC)), + DocumentTableCell.text(line.vatRate()) + .withStyle(cellStyle(BODY, + DocumentTableTextAnchor.CENTER, PAD_NUMERIC)), + DocumentTableCell.text(money(currencySymbol, line.amount())) + .withStyle(cellStyle(BODY_BOLD, + DocumentTableTextAnchor.CENTER_RIGHT, PAD_AMOUNT))); + itemRule(table); + } + } + + private static DocumentTableColumn column(int index) { + return DocumentTableColumn.fixed(CONTENT_WIDTH * TABLE_RATIOS[index]); + } + + /** + * A tracked header cell, written run by run like every other tracked line + * on the sheet — its gaps painted in the band's own dark, not the paper's. + */ + private static DocumentTableCell headerCell(String text, TextAlign align) { + return DocumentTableCell.node(tracked("HeaderCell", text, TABLE_HEADER, + TRACK_TABLE_HEADER, INK_SURFACE, align, TextVerticalAlign.DEFAULT)); + } + + /** + * A line's title over its description, as one node so the index cell has + * a single thing to centre against. + * + *

    They are two paragraphs in a block rather than two runs of one + * paragraph: a single paragraph would need an invisible spacer sized to + * the remaining width to break after the title, and there is no + * compose-time text measurement to size one with. The block puts the + * same leading between them and lands in the same place.

    + */ + private static DocumentNode description(InvoiceServiceLines.Line line) { + double gap = leading(LINE_PITCH * 0.83, ITEM_DESC); + SectionBuilder block = new SectionBuilder(); + block.name("ItemDescription").spacing(0) + .padding(DocumentInsets.zero()) + .margin(DocumentInsets.zero()); + block.addParagraph(p -> p + .name("ItemTitle") + .text(line.title()) + .textStyle(ITEM_TITLE) + .align(TextAlign.LEFT) + .lineSpacing(gap) + .margin(DocumentInsets.zero())); + block.addParagraph(p -> p + .name("ItemBody") + .text(line.description()) + .textStyle(ITEM_DESC) + .align(TextAlign.LEFT) + .lineSpacing(gap) + .margin(new DocumentInsets(gap, 0, 0, 0))); + return block.build(); + } + + /** + * How much was delivered, written plainly: a whole number loses its + * decimals, because a sheet that says "1" should not say "1.00". + */ + private static String quantity(InvoiceServiceLines.Line line) { + return line.quantity().stripTrailingZeros().toPlainString(); + } + + /** The hairline under a row, drawn as a row of its own. */ + private static void itemRule(TableBuilder table) { + DocumentTableStyle rule = DocumentTableStyle.builder() + .padding(DocumentInsets.zero()) + .fillColor(HAIRLINE) + .stroke(DocumentStroke.of(HAIRLINE, 0)) + .textStyle(sans(TABLE_RULE_THICKNESS, DocumentTextDecoration.DEFAULT, HAIRLINE)) + .textAnchor(DocumentTableTextAnchor.CENTER) + .lineSpacing(0) + .build(); + table.rowCells( + DocumentTableCell.text("").withStyle(rule), + DocumentTableCell.text("").withStyle(rule), + DocumentTableCell.text("").withStyle(rule), + DocumentTableCell.text("").withStyle(rule), + DocumentTableCell.text("").withStyle(rule), + DocumentTableCell.text("").withStyle(rule)); + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/LumaStudioMasthead.java b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/LumaStudioMasthead.java new file mode 100644 index 000000000..ab75a03b0 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/LumaStudioMasthead.java @@ -0,0 +1,248 @@ +package com.demcha.compose.document.templates.invoice.presets; + +import com.demcha.compose.document.dsl.LineBuilder; +import com.demcha.compose.document.dsl.SectionBuilder; +import com.demcha.compose.document.node.LayerAlign; +import com.demcha.compose.document.node.TextAlign; +import com.demcha.compose.document.node.TextVerticalAlign; +import com.demcha.compose.document.style.ClipPolicy; +import com.demcha.compose.document.style.DocumentInsets; +import com.demcha.compose.document.templates.core.identity.ContactUri; +import com.demcha.compose.document.templates.data.invoice.InvoiceContactBlock; +import com.demcha.compose.document.templates.data.invoice.InvoiceMasthead; +import com.demcha.compose.document.templates.data.invoice.InvoiceRecipient; + +import java.util.ArrayList; +import java.util.List; + +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioStyles.ACCENT; +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioStyles.BODY; +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioStyles.CONTACT_ICON_SIZE; +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioStyles.CONTACT_ROW_HEIGHT; +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioStyles.CONTACT_TEXT_OFFSET; +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioStyles.HAIRLINE_QUIET; +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioStyles.INVOICE_TITLE; +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioStyles.LINE_PITCH; +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioStyles.META_LABEL; +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioStyles.META_ROW_HEIGHT; +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioStyles.META_VALUE_SPLIT; +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioStyles.META_WIDTH; +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioStyles.MASTHEAD_TO_RULE; +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioStyles.MICRO_MUTED; +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioStyles.PAPER; +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioStyles.PARTIES_DIVIDER_INSET; +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioStyles.RULE_THICKNESS; +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioStyles.SECTION_HEADING; +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioStyles.SUPPLIER_NAME; +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioStyles.SUPPLIER_WIDTH; +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioStyles.TEXT_INSET; +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioStyles.TEXT_WIDTH; +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioStyles.TITLE_BLOCK_HEIGHT; +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioStyles.TITLE_CAP_INSET; +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioStyles.TITLE_RULE_THICKNESS; +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioStyles.TITLE_RULE_TO_METADATA; +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioStyles.TITLE_RULE_WIDTH; +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioStyles.TRACK_META_LABEL; +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioStyles.TRACK_SECTION_HEADING; +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioStyles.TRACK_TITLE; +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioStyles.leading; +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioWidgets.linkedParagraph; +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioWidgets.paragraph; +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioWidgets.tracked; + +/** + * The top of the sheet: the sender's block on the left, the title and its + * metadata on the right, and the two party blocks below the rule. + */ +final class LumaStudioMasthead { + + /** The break this sheet stacks address lines on. */ + private static final String NEWLINE = String.valueOf((char) 10); + + private LumaStudioMasthead() { + } + + // -- supplier -------------------------------------------------------- + + /** + * The sender: the legal name, the address, the contact rows behind their + * marks, and the registration line. + * + *

    The channels carry {@code tel:} and {@code mailto:} targets built + * from the values, and the website its own {@code https:} — so a reader + * can act on them and the document does not carry each address twice.

    + */ + static void renderSupplier(SectionBuilder section, InvoiceContactBlock supplier) { + section.name("SupplierHeader") + .spacing(0) + .padding(new DocumentInsets(0, 0, 0, TEXT_INSET)); + section.addParagraph(p -> p + .name("SupplierName") + .text(supplier.legalName()) + .textStyle(SUPPLIER_NAME) + .lineSpacing(0) + .margin(DocumentInsets.zero())); + section.addParagraph(p -> p + .name("SupplierAddress") + .text(String.join(NEWLINE, supplier.addressLines())) + .textStyle(BODY) + .lineSpacing(leading(LINE_PITCH, BODY)) + .margin(new DocumentInsets(LINE_PITCH * 0.50, 0, 0, 0))); + + SectionBuilder contacts = new SectionBuilder() + .name("ContactRows") + .spacing(0) + .margin(new DocumentInsets(LINE_PITCH * 0.79, 0, 0, 0)); + contactRow(contacts, LumaStudioIcons.PHONE, supplier.phone(), + ContactUri.tel(supplier.phone())); + contactRow(contacts, LumaStudioIcons.EMAIL, supplier.email(), + supplier.email().isBlank() ? null : "mailto:" + supplier.email()); + contactRow(contacts, LumaStudioIcons.WEBSITE, supplier.website(), + ContactUri.web(supplier.website())); + section.add(contacts.build()); + + section.addParagraph(p -> p + .name("Registration") + .text(registrationLine(supplier)) + .textStyle(MICRO_MUTED) + .lineSpacing(0) + .margin(new DocumentInsets(LINE_PITCH * 0.84, 0, 0, 0))); + } + + private static void contactRow(SectionBuilder section, String iconToken, + String value, String href) { + section.addContainer(container -> container + .name("ContactRow") + .rectangle(SUPPLIER_WIDTH, CONTACT_ROW_HEIGHT) + .clipPolicy(ClipPolicy.OVERFLOW_VISIBLE) + .centerLeft(LumaStudioIcons.icon(iconToken, CONTACT_ICON_SIZE)) + .position(linkedParagraph(value, href, BODY), CONTACT_TEXT_OFFSET, 0, + LayerAlign.CENTER_LEFT)); + } + + /** + * The registration line: one labelled number, or both behind a pale pipe + * when the sender carries a tax registration as well. + */ + private static String registrationLine(InvoiceContactBlock supplier) { + String first = pair(supplier.registrationLabel(), supplier.registrationNumber()); + String second = pair(supplier.taxRegistrationLabel(), supplier.taxRegistrationNumber()); + if (first.isBlank()) { + return second; + } + return second.isBlank() ? first : first + " | " + second; + } + + private static String pair(String label, String value) { + if (label.isBlank() && value.isBlank()) { + return ""; + } + return label.isBlank() ? value : label + " " + value; + } + + // -- title and metadata ---------------------------------------------- + + /** + * The document title over its accent rule, and the metadata rows beneath. + * + *

    The title and the rule share a band so the rule sits at the block's + * foot whatever the title measures: the title is seated to the top of the + * band and the rule anchored to its bottom.

    + */ + static void renderTitle(SectionBuilder section, InvoiceMasthead masthead) { + section.name("InvoiceHeader").spacing(0); + section.addContainer(container -> container + .name("InvoiceTitleBlock") + .rectangle(META_WIDTH, TITLE_BLOCK_HEIGHT) + .clipPolicy(ClipPolicy.OVERFLOW_VISIBLE) + .position(tracked("InvoiceTitle", masthead.title(), INVOICE_TITLE, + TRACK_TITLE, PAPER, TextVerticalAlign.TOP), + 0, TITLE_CAP_INSET, LayerAlign.TOP_LEFT) + .position(new LineBuilder() + .name("TitleRule") + .horizontal(TITLE_RULE_WIDTH) + .thickness(TITLE_RULE_THICKNESS) + .color(ACCENT) + .margin(DocumentInsets.zero()) + .build(), + 0, 0, LayerAlign.BOTTOM_LEFT) + .margin(new DocumentInsets(0, 0, TITLE_RULE_TO_METADATA, 0))); + for (InvoiceMasthead.Entry entry : masthead.entries()) { + section.addContainer(container -> container + .name("MetadataRow") + .rectangle(META_WIDTH, META_ROW_HEIGHT) + .clipPolicy(ClipPolicy.OVERFLOW_VISIBLE) + .centerLeft(tracked("MetaLabel", entry.label(), META_LABEL, + TRACK_META_LABEL, PAPER)) + .position(paragraph(entry.value(), BODY, TextAlign.LEFT), + META_WIDTH * META_VALUE_SPLIT, 0, LayerAlign.CENTER_LEFT)); + } + } + + // -- the rule between the masthead and the parties --------------------- + + /** + * The masthead rule, which stops at the text column rather than running + * to the table's edge — the only rule on the sheet that does. + * + * @param line the rule to draw + */ + static void renderRule(LineBuilder line) { + line.name("HeaderRule") + .horizontal(TEXT_WIDTH) + .thickness(RULE_THICKNESS) + .color(HAIRLINE_QUIET) + .margin(new DocumentInsets(MASTHEAD_TO_RULE, 0, 0, TEXT_INSET)); + } + + // -- parties --------------------------------------------------------- + + /** + * One party block: the tracked heading over its address. + * + * @param section the host column + * @param party the recipient to draw + * @param divided true for the right-hand block, which carries the + * hairline between the two as its own left border + */ + static void renderParty(SectionBuilder section, InvoiceRecipient party, boolean divided) { + // Named after the heading it draws, so the two columns are told apart + // in a layout snapshot; a party that states no heading still needs a + // name, so it falls back to its role. + String heading = party.heading(); + section.name(heading.isBlank() + ? (divided ? "ShipTo" : "BillTo") + : heading.replace(" ", "")).spacing(0); + if (divided) { + section.accentLeft(HAIRLINE_QUIET, RULE_THICKNESS) + .padding(new DocumentInsets(0, 0, 0, PARTIES_DIVIDER_INSET)); + } else { + section.padding(new DocumentInsets(0, 0, 0, TEXT_INSET)); + } + section.add(tracked("PartyHeading", heading, SECTION_HEADING, + TRACK_SECTION_HEADING, PAPER)) + .addParagraph(p -> p + .name("PartyAddress") + .text(String.join(NEWLINE, addressOf(party))) + .textStyle(BODY) + .lineSpacing(leading(LINE_PITCH * 1.183, BODY)) + .margin(new DocumentInsets(LINE_PITCH * 0.72, 0, 0, 0))); + } + + /** + * The lines a party is drawn as: the name, the subline when it carries + * one, then the address. This design sets them as one block, so a + * recipient that names an attention line gets it on the second line. + */ + private static List addressOf(InvoiceRecipient party) { + List lines = new ArrayList<>(); + if (!party.name().isBlank()) { + lines.add(party.name()); + } + if (!party.subline().isBlank()) { + lines.add(party.subline()); + } + lines.addAll(party.addressLines()); + return lines; + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/LumaStudioSidebar.java b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/LumaStudioSidebar.java new file mode 100644 index 000000000..4d3d39b2b --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/LumaStudioSidebar.java @@ -0,0 +1,170 @@ +package com.demcha.compose.document.templates.invoice.presets; + +import com.demcha.compose.document.dsl.EllipseBuilder; +import com.demcha.compose.document.dsl.LineBuilder; +import com.demcha.compose.document.dsl.ParagraphBuilder; +import com.demcha.compose.document.dsl.ShapeBuilder; +import com.demcha.compose.document.dsl.ShapeContainerBuilder; +import com.demcha.compose.document.node.DocumentNode; +import com.demcha.compose.document.node.LayerAlign; +import com.demcha.compose.document.node.TextAlign; +import com.demcha.compose.document.node.TextVerticalAlign; +import com.demcha.compose.document.style.ClipPolicy; +import com.demcha.compose.document.style.DocumentCornerRadius; +import com.demcha.compose.document.style.DocumentInsets; +import com.demcha.compose.document.style.DocumentTextStyle; +import com.demcha.compose.document.templates.data.invoice.InvoiceBrand; + +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioStyles.ACCENT; +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioStyles.LOCKUP_LEFT; +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioStyles.LOCKUP_RULE_THICKNESS; +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioStyles.LOCKUP_RULE_WIDTH; +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioStyles.LOCKUP_RULE_Y; +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioStyles.MONOGRAM_BOTTOM; +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioStyles.MONOGRAM_BOTTOM_Y; +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioStyles.MONOGRAM_TOP; +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioStyles.MONOGRAM_TOP_Y; +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioStyles.ON_ACCENT; +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioStyles.ORNAMENT_ARCH_HEIGHT_RATIO; +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioStyles.ORNAMENT_ARCH_LEFT_RATIO; +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioStyles.ORNAMENT_ARCH_WIDTH_RATIO; +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioStyles.ORNAMENT_DISC_LEFT_RATIO; +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioStyles.ORNAMENT_DISC_RATIO; +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioStyles.ORNAMENT_DISC_TOP_FACTOR; +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioStyles.ORNAMENT_SPRIG_LEFT_RATIO; +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioStyles.ORNAMENT_SPRIG_TOP_RATIO; +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioStyles.ORNAMENT_SPRIG_WIDTH_RATIO; +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioStyles.ORNAMENT_TINT; +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioStyles.PAGE; +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioStyles.PAGE_MARGIN_LEFT; +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioStyles.PAGE_MARGIN_TOP; +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioStyles.SIDEBAR_BRAND_HEIGHT; +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioStyles.SIDEBAR_STUB_HEIGHT; +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioStyles.SIDEBAR_WIDTH; +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioStyles.TAGLINE; +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioStyles.TAGLINE_Y; +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioStyles.TRACK_WORDMARK; +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioStyles.WORDMARK; +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioStyles.WORDMARK_Y; +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioWidgets.tracked; + +/** + * The sidebar: one node with one negative margin. + * + *

    The body flow's left margin excludes the column, so the lockup and the + * ornament are composed into a single overflowing stub pulled back over the + * margin — both of its offsets are the page margins themselves. The + * terracotta brand block is content rather than a page background, painted + * above the ornament in the stub's z-order, because the ornament's disc is a + * circle far larger than the column and the block has to cover its top.

    + */ +final class LumaStudioSidebar { + + private LumaStudioSidebar() { + } + + static DocumentNode render(InvoiceBrand brand) { + return new ShapeContainerBuilder() + .name("Sidebar") + .rectangle(SIDEBAR_WIDTH, SIDEBAR_STUB_HEIGHT) + .clipPolicy(ClipPolicy.OVERFLOW_VISIBLE) + .margin(new DocumentInsets( + -PAGE_MARGIN_TOP, 0, PAGE_MARGIN_TOP - SIDEBAR_STUB_HEIGHT, + -PAGE_MARGIN_LEFT)) + .position(ornament(), 0, SIDEBAR_BRAND_HEIGHT, LayerAlign.TOP_LEFT, 0) + .position(brandBlock(), 0, 0, LayerAlign.TOP_LEFT, 1) + .position(lockup(brand), 0, 0, LayerAlign.TOP_LEFT, 2) + .build(); + } + + private static DocumentNode brandBlock() { + return new ShapeBuilder() + .name("BrandBlock") + .size(SIDEBAR_WIDTH, SIDEBAR_BRAND_HEIGHT) + .fillColor(ACCENT) + .margin(DocumentInsets.zero()) + .build(); + } + + /** + * The lockup: the two monogram lines, a short rule, the tracked wordmark + * and the tagline, each seated at its own measured height. + */ + private static DocumentNode lockup(InvoiceBrand brand) { + return new ShapeContainerBuilder() + .name("BrandLockup") + .rectangle(SIDEBAR_WIDTH, SIDEBAR_BRAND_HEIGHT) + .clipPolicy(ClipPolicy.CLIP_PATH) + .position(seated(brand.monogramTop(), MONOGRAM_TOP), + LOCKUP_LEFT, MONOGRAM_TOP_Y, LayerAlign.TOP_LEFT) + .position(seated(brand.monogramBottom(), MONOGRAM_BOTTOM), + LOCKUP_LEFT, MONOGRAM_BOTTOM_Y, LayerAlign.TOP_LEFT) + .position(new LineBuilder() + .name("LockupRule") + .horizontal(LOCKUP_RULE_WIDTH) + .thickness(LOCKUP_RULE_THICKNESS) + .color(ON_ACCENT) + .margin(DocumentInsets.zero()) + .build(), + LOCKUP_LEFT, LOCKUP_RULE_Y, LayerAlign.TOP_LEFT) + .position(tracked("Wordmark", brand.name(), WORDMARK, + TRACK_WORDMARK, ACCENT, TextVerticalAlign.TOP), + LOCKUP_LEFT, WORDMARK_Y, LayerAlign.TOP_LEFT) + .position(seated(brand.tagline(), TAGLINE), + LOCKUP_LEFT, TAGLINE_Y, LayerAlign.TOP_LEFT) + .build(); + } + + private static DocumentNode seated(String text, DocumentTextStyle style) { + return new ParagraphBuilder() + .text(text) + .textStyle(style) + .align(TextAlign.LEFT) + .verticalAlign(TextVerticalAlign.TOP) + .lineSpacing(0) + .margin(DocumentInsets.zero()) + .build(); + } + + /** + * The art below the brand block: a tinted quarter-disc, an arch at the + * foot, and the sprig between them. + * + *

    The container clips to its bounds rather than to its path: the disc + * is a circle far wider than the column, and a rectangular container's + * path clip does not cut its positioned layers.

    + */ + private static DocumentNode ornament() { + double columnHeight = PAGE.height() - SIDEBAR_BRAND_HEIGHT; + double discDiameter = PAGE.width() * ORNAMENT_DISC_RATIO; + double archWidth = PAGE.width() * ORNAMENT_ARCH_WIDTH_RATIO; + + DocumentNode disc = new EllipseBuilder() + .name("OrnamentDisc") + .circle(discDiameter) + .fillColor(ORNAMENT_TINT) + .margin(DocumentInsets.zero()) + .build(); + DocumentNode arch = new ShapeBuilder() + .name("OrnamentArch") + .size(archWidth, PAGE.height() * ORNAMENT_ARCH_HEIGHT_RATIO) + .cornerRadius(DocumentCornerRadius.top(archWidth / 2.0)) + .fillColor(ACCENT) + .margin(DocumentInsets.zero()) + .build(); + + return new ShapeContainerBuilder() + .name("SidebarOrnament") + .rectangle(SIDEBAR_WIDTH, columnHeight) + .clipPolicy(ClipPolicy.CLIP_BOUNDS) + .position(disc, PAGE.width() * ORNAMENT_DISC_LEFT_RATIO, + discDiameter * ORNAMENT_DISC_TOP_FACTOR, LayerAlign.TOP_LEFT) + .position(arch, PAGE.width() * ORNAMENT_ARCH_LEFT_RATIO, 0, + LayerAlign.BOTTOM_LEFT) + .position(LumaStudioIcons.icon(LumaStudioIcons.SPRIG, + PAGE.width() * ORNAMENT_SPRIG_WIDTH_RATIO), + PAGE.width() * ORNAMENT_SPRIG_LEFT_RATIO, + PAGE.height() * ORNAMENT_SPRIG_TOP_RATIO, LayerAlign.TOP_LEFT) + .build(); + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/LumaStudioStyles.java b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/LumaStudioStyles.java new file mode 100644 index 000000000..bbc203b99 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/LumaStudioStyles.java @@ -0,0 +1,356 @@ +package com.demcha.compose.document.templates.invoice.presets; + +import com.demcha.compose.document.api.DocumentPageSize; +import com.demcha.compose.document.style.DocumentColor; +import com.demcha.compose.document.style.DocumentInsets; +import com.demcha.compose.document.style.DocumentStroke; +import com.demcha.compose.document.style.DocumentTextDecoration; +import com.demcha.compose.document.style.DocumentTextStyle; +import com.demcha.compose.document.table.DocumentTableStyle; +import com.demcha.compose.document.table.DocumentTableTextAnchor; +import com.demcha.compose.document.templates.core.text.TextStyles; +import com.demcha.compose.font.FontName; + +import java.math.BigDecimal; +import java.text.NumberFormat; +import java.util.Locale; + +/** + * The measured geometry, palette and type scale of the Luma Studio invoice. + * + *

    Two left edges, and that is deliberate

    + * + *

    The body text column starts further in than the line-item table, the two + * rules that bracket the closing block and the notes disc. The table's own + * first-cell padding brings its text back onto the text edge, so the text runs + * straight down the page while the table and its rules sit one cell padding + * wider. {@link #PAGE_MARGIN_LEFT} is therefore the table's edge, and + * every text block carries {@link #TEXT_INSET} as its left padding — one + * property per block rather than an inset restated on every paragraph.

    + * + *

    Leading is authored as a pitch

    + * + *

    {@link #leading} takes the distance the design draws between line tops + * and subtracts the line box the type already fills. The ratio is per family, + * because the three faces here set different line boxes at the same size — + * one shared constant would be wrong by a line per block on two of them.

    + */ +final class LumaStudioStyles { + + private LumaStudioStyles() { + } + + // -- page ------------------------------------------------------------ + + static final DocumentPageSize PAGE = DocumentPageSize.A4; + + static final double SIDEBAR_WIDTH_RATIO = 0.2218; + static final double SIDEBAR_WIDTH = PAGE.width() * SIDEBAR_WIDTH_RATIO; + static final double SIDEBAR_BRAND_HEIGHT_RATIO = 0.24614; + static final double SIDEBAR_BRAND_HEIGHT = PAGE.height() * SIDEBAR_BRAND_HEIGHT_RATIO; + + static final double PAGE_MARGIN_LEFT = PAGE.width() * 0.25782; + static final double PAGE_MARGIN_RIGHT = PAGE.width() * 0.0654; + static final double PAGE_MARGIN_TOP = 46.3; + static final double CONTENT_WIDTH = PAGE.width() - PAGE_MARGIN_LEFT - PAGE_MARGIN_RIGHT; + static final double TEXT_INSET = PAGE.width() * 0.01232; + static final double TEXT_WIDTH = CONTENT_WIDTH - TEXT_INSET; + + /** The stub the sidebar is drawn from: it carries art, not height. */ + static final double SIDEBAR_STUB_HEIGHT = 0.4; + static final double BANNER_FLOW_HEIGHT = 0.3; + static final double BANNER_HEIGHT_RATIO = 0.07445; + static final double BANNER_HEIGHT = PAGE.height() * BANNER_HEIGHT_RATIO; + static final double FOLIO_ZONE_HEIGHT = 30.5; + + // -- block rhythm ---------------------------------------------------- + + static final double MASTHEAD_TO_RULE = 13.0; + static final double RULE_TO_PARTIES = 12.0; + static final double PARTIES_TO_TABLE = 12.9; + static final double TABLE_TO_TOTALS = 4.4; + static final double TOTALS_TO_RULE = 2.0; + static final double RULE_TO_CLOSING = 12.0; + static final double CLOSING_TO_BANNER = 8.6; + + // -- the column grid ------------------------------------------------- + + static final double MASTHEAD_LEFT_WEIGHT = 0.5868; + static final double MASTHEAD_RIGHT_WEIGHT = 1.0 - MASTHEAD_LEFT_WEIGHT; + static final double SUPPLIER_WIDTH = CONTENT_WIDTH * MASTHEAD_LEFT_WEIGHT - TEXT_INSET; + static final double META_WIDTH = CONTENT_WIDTH * MASTHEAD_RIGHT_WEIGHT; + static final double PARTIES_LEFT_WEIGHT = 0.4748; + static final double CLOSING_LEFT_WEIGHT = 0.4146; + static final double[] TABLE_RATIOS = {0.0630, 0.3124, 0.0980, 0.2395, 0.0742, 0.2129}; + static final double TOTALS_WIDTH = CONTENT_WIDTH * 0.6204; + + // -- type ------------------------------------------------------------ + + static final FontName SANS = FontName.CARLITO; + static final FontName DISPLAY_SERIF = FontName.SPECTRAL; + static final FontName TEXT_SERIF = FontName.TINOS; + + static final double BODY_SIZE = 9.4; + static final double LINE_PITCH = 12.42; + static final double SMALL_SIZE = BODY_SIZE * 0.851; + static final double MICRO_SIZE = BODY_SIZE * 0.766; + static final double SUPPLIER_NAME_SIZE = BODY_SIZE * 1.170; + static final double TITLE_SIZE = BODY_SIZE * 3.883; + static final double TOTAL_DUE_SIZE = BODY_SIZE * 1.968; + static final double MONOGRAM_TOP_SIZE = BODY_SIZE * 6.170; + static final double MONOGRAM_BOTTOM_SIZE = BODY_SIZE * 3.300; + static final double WORDMARK_SIZE = BODY_SIZE * 0.830; + static final double TAGLINE_SIZE = BODY_SIZE * 0.773; + + // -- band heights ---------------------------------------------------- + + static final double CONTACT_ROW_HEIGHT = LINE_PITCH * 1.159; + static final double META_ROW_HEIGHT = LINE_PITCH * 1.409; + static final double TOTALS_ROW_HEIGHT = LINE_PITCH * 1.458; + static final double TOTAL_DUE_HEIGHT = LINE_PITCH * 2.182; + static final double BANK_ROW_HEIGHT = LINE_PITCH * 0.821; + + // -- ornaments and offsets ------------------------------------------- + + static final double CONTACT_ICON_SIZE = 8.6; + static final double CONTACT_TEXT_OFFSET = 21.0; + static final double META_VALUE_SPLIT = 0.6134; + static final double SECTION_DISC_SIZE = 24.8; + static final double SECTION_DISC_ICON_SIZE = 12.0; + static final double SECTION_HEADING_OFFSET = 35.5; + static final double BANK_PIPE_OFFSET = 53.6; + static final double BANK_VALUE_OFFSET = 63.8; + static final double PARTIES_DIVIDER_INSET = 34.4; + static final double CLOSING_DIVIDER_INSET = 24.3; + static final double TITLE_RULE_WIDTH = 58.7; + static final double TITLE_RULE_THICKNESS = 1.7; + static final double TITLE_BLOCK_HEIGHT = 45.7; + static final double TITLE_CAP_INSET = 3.4; + static final double TITLE_RULE_TO_METADATA = 15.3; + static final double TOTALS_LABEL_INSET = 41.8; + static final double TOTALS_AMOUNT_INSET = 13.0; + static final double RULE_THICKNESS = 0.56; + static final double TABLE_RULE_THICKNESS = 0.62; + + static final double LOCKUP_LEFT = PAGE.width() * 0.05592; + static final double LOCKUP_RULE_WIDTH = PAGE.width() * 0.03412; + static final double LOCKUP_RULE_THICKNESS = 1.7; + static final double MONOGRAM_TOP_Y = PAGE.height() * 0.04561; + static final double MONOGRAM_BOTTOM_Y = PAGE.height() * 0.10597; + static final double LOCKUP_RULE_Y = PAGE.height() * 0.16097; + static final double WORDMARK_Y = PAGE.height() * 0.18645; + static final double TAGLINE_Y = PAGE.height() * 0.20456; + + static final double ORNAMENT_DISC_RATIO = 0.34502; + static final double ORNAMENT_DISC_LEFT_RATIO = 0.02938; + static final double ORNAMENT_DISC_TOP_FACTOR = -0.48; + static final double ORNAMENT_ARCH_WIDTH_RATIO = 0.19526; + static final double ORNAMENT_ARCH_HEIGHT_RATIO = 0.28102; + static final double ORNAMENT_ARCH_LEFT_RATIO = 0.02654; + static final double ORNAMENT_SPRIG_WIDTH_RATIO = 0.16000; + static final double ORNAMENT_SPRIG_LEFT_RATIO = 0.02180; + static final double ORNAMENT_SPRIG_TOP_RATIO = 0.30500; + + static final double BANNER_CONTENT_INSET = PAGE.width() * 0.05498; + static final double BANNER_DISC_SIZE = 33.9; + static final double BANNER_DISC_ICON_SIZE = 16.0; + static final double BANNER_TEXT_OFFSET = 47.9; + + // -- table padding --------------------------------------------------- + + static final double TABLE_CELL_PAD_X = 7.33; + static final double TABLE_CELL_PAD_Y = 9.3; + static final double TABLE_HEADER_PAD_Y = 7.2; + static final double TABLE_TIGHT_PAD_X = 2.0; + static final double TABLE_AMOUNT_PAD_RIGHT = 15.2; + + static final DocumentInsets PAD_INDEX = new DocumentInsets( + TABLE_CELL_PAD_Y, TABLE_TIGHT_PAD_X, TABLE_CELL_PAD_Y, TABLE_CELL_PAD_X); + static final DocumentInsets PAD_DESCRIPTION = new DocumentInsets( + TABLE_CELL_PAD_Y, TABLE_TIGHT_PAD_X, TABLE_CELL_PAD_Y, TABLE_CELL_PAD_X * 0.93); + static final DocumentInsets PAD_NUMERIC = DocumentInsets.symmetric( + TABLE_CELL_PAD_Y, TABLE_TIGHT_PAD_X); + static final DocumentInsets PAD_AMOUNT = new DocumentInsets( + TABLE_CELL_PAD_Y, TABLE_AMOUNT_PAD_RIGHT, TABLE_CELL_PAD_Y, TABLE_TIGHT_PAD_X); + static final DocumentInsets PAD_HEADER_LEFT = new DocumentInsets( + TABLE_HEADER_PAD_Y, TABLE_TIGHT_PAD_X, TABLE_HEADER_PAD_Y, TABLE_CELL_PAD_X); + static final DocumentInsets PAD_HEADER_CENTRE = DocumentInsets.symmetric( + TABLE_HEADER_PAD_Y, TABLE_TIGHT_PAD_X); + static final DocumentInsets PAD_HEADER_RIGHT = new DocumentInsets( + TABLE_HEADER_PAD_Y, TABLE_AMOUNT_PAD_RIGHT, TABLE_HEADER_PAD_Y, TABLE_TIGHT_PAD_X); + + // -- palette --------------------------------------------------------- + + static final DocumentColor ACCENT = DocumentColor.rgb(163, 78, 51); + static final DocumentColor INK_SURFACE = DocumentColor.rgb(46, 46, 46); + static final DocumentColor INK = DocumentColor.rgb(51, 51, 51); + static final DocumentColor MUTED = DocumentColor.rgb(90, 87, 80); + static final DocumentColor SIDEBAR_SURFACE = DocumentColor.rgb(243, 236, 224); + static final DocumentColor ORNAMENT_TINT = DocumentColor.rgb(219, 199, 182); + static final DocumentColor PAPER = DocumentColor.rgb(251, 250, 249); + static final DocumentColor PANEL_HIGHLIGHT = DocumentColor.rgb(232, 222, 213); + static final DocumentColor HAIRLINE = DocumentColor.rgb(221, 212, 202); + static final DocumentColor HAIRLINE_QUIET = DocumentColor.rgb(211, 202, 189); + static final DocumentColor ON_DARK = DocumentColor.WHITE; + static final DocumentColor ON_ACCENT = DocumentColor.rgb(247, 240, 231); + + // -- text styles ----------------------------------------------------- + + static final DocumentTextStyle BODY = sans(BODY_SIZE, DocumentTextDecoration.DEFAULT, INK); + static final DocumentTextStyle BODY_BOLD = sans(BODY_SIZE, DocumentTextDecoration.BOLD, INK); + static final DocumentTextStyle SMALL_BOLD = + sans(SMALL_SIZE, DocumentTextDecoration.BOLD, INK); + static final DocumentTextStyle MICRO_MUTED = + sans(MICRO_SIZE, DocumentTextDecoration.DEFAULT, MUTED); + static final DocumentTextStyle SUPPLIER_NAME = + sans(SUPPLIER_NAME_SIZE, DocumentTextDecoration.BOLD, INK); + static final DocumentTextStyle META_LABEL = + sans(SMALL_SIZE, DocumentTextDecoration.BOLD, INK); + static final DocumentTextStyle SECTION_HEADING = + sans(SMALL_SIZE, DocumentTextDecoration.BOLD, ACCENT); + static final DocumentTextStyle TABLE_HEADER = + sans(SMALL_SIZE, DocumentTextDecoration.BOLD, ON_DARK); + static final DocumentTextStyle ITEM_INDEX = + sans(BODY_SIZE * 1.15, DocumentTextDecoration.DEFAULT, ACCENT); + static final DocumentTextStyle ITEM_TITLE = + sans(BODY_SIZE * 0.875, DocumentTextDecoration.BOLD, INK); + static final DocumentTextStyle ITEM_DESC = + serif(BODY_SIZE * 0.780, DocumentTextDecoration.ITALIC, MUTED); + static final DocumentTextStyle TOTAL_LABEL = + sans(BODY_SIZE * 1.02, DocumentTextDecoration.BOLD, INK); + static final DocumentTextStyle TOTAL_AMOUNT = + sans(BODY_SIZE * 1.02, DocumentTextDecoration.DEFAULT, INK); + static final DocumentTextStyle TOTAL_DUE_LABEL = + sans(BODY_SIZE * 1.09, DocumentTextDecoration.BOLD, INK); + static final DocumentTextStyle TOTAL_DUE_AMOUNT = + sans(TOTAL_DUE_SIZE, DocumentTextDecoration.BOLD, ACCENT); + static final DocumentTextStyle NOTE_TEXT = + serif(BODY_SIZE * 0.766, DocumentTextDecoration.ITALIC, INK); + static final DocumentTextStyle BANK_LABEL = + sans(MICRO_SIZE, DocumentTextDecoration.BOLD, INK); + static final DocumentTextStyle BANK_VALUE = + sans(MICRO_SIZE, DocumentTextDecoration.DEFAULT, MUTED); + static final DocumentTextStyle INVOICE_TITLE = + display(TITLE_SIZE, DocumentTextDecoration.DEFAULT, INK_SURFACE); + static final DocumentTextStyle MONOGRAM_TOP = + display(MONOGRAM_TOP_SIZE, DocumentTextDecoration.DEFAULT, ON_ACCENT); + static final DocumentTextStyle MONOGRAM_BOTTOM = + display(MONOGRAM_BOTTOM_SIZE, DocumentTextDecoration.DEFAULT, ON_ACCENT); + static final DocumentTextStyle WORDMARK = + sans(WORDMARK_SIZE, DocumentTextDecoration.BOLD, ON_ACCENT); + static final DocumentTextStyle TAGLINE = + serif(TAGLINE_SIZE, DocumentTextDecoration.ITALIC, ON_ACCENT); + static final DocumentTextStyle BANNER_SIGN_OFF = + sans(BODY_SIZE * 1.02, DocumentTextDecoration.BOLD, ON_DARK); + static final DocumentTextStyle BANNER_DUE = + serif(BODY_SIZE * 1.09, DocumentTextDecoration.ITALIC, ON_DARK); + + // -- tracking -------------------------------------------------------- + + /** + * The letter-spacing each tracked run is set with, in points. + * + *

    A text style carries no tracking, so a tracked run is written letter + * by letter with an invisible inline rectangle between the pairs, and + * these are its widths. They are frozen constants rather than a + * measurement: the design derived each by measuring the face and dividing + * the difference from a target width across the gaps, which would tie the + * preset to the font artifact's own resource layout at compose time. The + * numbers are what that measurement produced, and the pixel-parity gate + * is what proves they are still the right ones.

    + */ + static final double TRACK_TITLE = 0.059021; + static final double TRACK_META_LABEL = 1.462532; + static final double TRACK_SECTION_HEADING = 1.462576; + static final double TRACK_TABLE_HEADER = 1.525822; + static final double TRACK_TOTAL_LABEL = 0.612126; + static final double TRACK_WORDMARK = 0.990497; + + // -- helpers --------------------------------------------------------- + + static DocumentTextStyle sans(double size, DocumentTextDecoration decoration, + DocumentColor color) { + return TextStyles.of(SANS, size, decoration, color); + } + + static DocumentTextStyle display(double size, DocumentTextDecoration decoration, + DocumentColor color) { + return TextStyles.of(DISPLAY_SERIF, size, decoration, color); + } + + static DocumentTextStyle serif(double size, DocumentTextDecoration decoration, + DocumentColor color) { + return TextStyles.of(TEXT_SERIF, size, decoration, color); + } + + /** + * The leading to author so lines land on a given pitch. + * + * @param targetPitch the distance the design draws between line tops + * @param style the style of the lines + * @return the extra leading, never negative + */ + static double leading(double targetPitch, DocumentTextStyle style) { + return Math.max(0, targetPitch - style.size() * lineBoxRatio(style.fontName())); + } + + /** + * How tall a line box is as a share of its type size, per family. The + * three faces differ enough that one shared ratio would be wrong by a + * line per block on two of them. + */ + private static double lineBoxRatio(FontName fontName) { + if (fontName.sameFamily(DISPLAY_SERIF)) { + return 1.522; + } + if (fontName.sameFamily(TEXT_SERIF)) { + return 1.150; + } + return 1.221; + } + + static DocumentTableStyle cellStyle(DocumentTextStyle textStyle, + DocumentTableTextAnchor anchor, + DocumentInsets padding) { + return DocumentTableStyle.builder() + .padding(padding) + .fillColor(PAPER) + .stroke(DocumentStroke.of(PAPER, 0)) + .textStyle(textStyle) + .textAnchor(anchor) + .lineSpacing(0) + .build(); + } + + static DocumentTableStyle headerStyle(DocumentTableTextAnchor anchor, + DocumentInsets padding) { + return DocumentTableStyle.builder() + .padding(padding) + .fillColor(INK_SURFACE) + .stroke(DocumentStroke.of(INK_SURFACE, 0)) + .textStyle(TABLE_HEADER) + .textAnchor(anchor) + .lineSpacing(0) + .build(); + } + + /** + * A figure written the way this sheet writes money: the currency mark, + * then grouped digits to two places. + * + *

    The formatter is built per call rather than shared: {@link NumberFormat} + * is not thread-safe, and a document is cheap to format beside a static + * monitor every render on every thread would queue behind.

    + * + * @param currencySymbol the mark to prefix + * @param value the figure + * @return the formatted amount + */ + static String money(String currencySymbol, BigDecimal value) { + NumberFormat format = NumberFormat.getNumberInstance(Locale.ENGLISH); + format.setGroupingUsed(true); + format.setMinimumFractionDigits(2); + format.setMaximumFractionDigits(2); + return currencySymbol + format.format(value); + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/LumaStudioWidgets.java b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/LumaStudioWidgets.java new file mode 100644 index 000000000..ce754986a --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/LumaStudioWidgets.java @@ -0,0 +1,172 @@ +package com.demcha.compose.document.templates.invoice.presets; + +import com.demcha.compose.document.dsl.EllipseBuilder; +import com.demcha.compose.document.dsl.LayerStackBuilder; +import com.demcha.compose.document.dsl.ParagraphBuilder; +import com.demcha.compose.document.dsl.SectionBuilder; +import com.demcha.compose.document.node.DocumentLinkOptions; +import com.demcha.compose.document.node.DocumentNode; +import com.demcha.compose.document.node.LayerAlign; +import com.demcha.compose.document.node.TextAlign; +import com.demcha.compose.document.node.TextVerticalAlign; +import com.demcha.compose.document.style.ClipPolicy; +import com.demcha.compose.document.style.DocumentColor; +import com.demcha.compose.document.style.DocumentInsets; +import com.demcha.compose.document.style.DocumentTextStyle; +import com.demcha.compose.document.style.ShapeOutline; + +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioStyles.ACCENT; +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioStyles.PAPER; +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioStyles.SECTION_DISC_ICON_SIZE; +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioStyles.SECTION_DISC_SIZE; +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioStyles.SECTION_HEADING; +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioStyles.SECTION_HEADING_OFFSET; +import static com.demcha.compose.document.templates.invoice.presets.LumaStudioStyles.TRACK_SECTION_HEADING; + +/** + * The idioms this sheet repeats: the tracked run, the mark on its accent + * disc, the plain node-shaped paragraph, and the heading that pairs the two. + */ +final class LumaStudioWidgets { + + private LumaStudioWidgets() { + } + + /** + * A run of letter-spaced text. + * + *

    A text style carries no tracking, so the gap between letters is an + * inline rectangle of the ground colour — invisible, and exactly as wide + * as the spacing asks for. The rectangle is 0.05pt tall so it takes no + * part in the line box.

    + * + * @param name the node name + * @param text the text to space out + * @param style the style of the letters + * @param tracking the gap in points; zero or less writes the text plainly + * @param ground the colour behind the run, which the gaps are painted in + * @return the paragraph node + */ + static DocumentNode tracked(String name, String text, DocumentTextStyle style, + double tracking, DocumentColor ground) { + return tracked(name, text, style, tracking, ground, TextVerticalAlign.DEFAULT); + } + + /** + * The same, seated to a given vertical alignment. + * + * @param name the node name + * @param text the text to space out + * @param style the style of the letters + * @param tracking the gap in points + * @param ground the colour the gaps are painted in + * @param verticalAlign where the text sits in its box + * @return the paragraph node + */ + static DocumentNode tracked(String name, String text, DocumentTextStyle style, + double tracking, DocumentColor ground, + TextVerticalAlign verticalAlign) { + return tracked(name, text, style, tracking, ground, TextAlign.LEFT, verticalAlign); + } + + /** + * The same, aligned across the box it is drawn in — which the table + * headers need and the rest of the sheet does not. + * + * @param name the node name + * @param text the text to space out + * @param style the style of the letters + * @param tracking the gap in points + * @param ground the colour the gaps are painted in + * @param align where the run sits across its box + * @param verticalAlign where the text sits in its box + * @return the paragraph node + */ + static DocumentNode tracked(String name, String text, DocumentTextStyle style, + double tracking, DocumentColor ground, TextAlign align, + TextVerticalAlign verticalAlign) { + ParagraphBuilder paragraph = new ParagraphBuilder() + .name(name) + .textStyle(style) + .align(align) + .verticalAlign(verticalAlign) + .lineSpacing(0) + .margin(DocumentInsets.zero()); + if (tracking <= 0) { + return paragraph.text(text).build(); + } + return paragraph.rich(rich -> { + for (int index = 0; index < text.length(); index++) { + if (index > 0) { + rich.shape(new ShapeOutline.Rectangle(tracking, 0.05), ground); + } + rich.style(String.valueOf(text.charAt(index)), style); + } + }).build(); + } + + /** A plain paragraph as a node, for anchoring inside a container. */ + static DocumentNode paragraph(String text, DocumentTextStyle style, TextAlign align) { + return new ParagraphBuilder() + .text(text) + .textStyle(style) + .align(align) + .lineSpacing(0) + .margin(DocumentInsets.zero()) + .build(); + } + + /** The same, carrying a link when one is given. */ + static DocumentNode linkedParagraph(String text, String href, DocumentTextStyle style) { + ParagraphBuilder paragraph = new ParagraphBuilder() + .text(text) + .textStyle(style) + .align(TextAlign.LEFT) + .lineSpacing(0) + .margin(DocumentInsets.zero()); + if (href != null && !href.isBlank()) { + paragraph.link(new DocumentLinkOptions(href)); + } + return paragraph.build(); + } + + /** + * A mark centred on an accent disc. + * + *

    A layer stack over an ellipse rather than a filled shape container: + * a container's own fill is dropped when it sits inside a parent carrying + * a large negative margin, which is where the closing banner puts it.

    + */ + static DocumentNode disc(double diameter, String iconToken, double iconSize) { + return new LayerStackBuilder() + .name("Disc-" + iconToken) + .back(new EllipseBuilder() + .name("DiscFill") + .circle(diameter) + .fillColor(ACCENT) + .margin(DocumentInsets.zero()) + .build()) + .layer(LumaStudioIcons.icon(iconToken, iconSize), LayerAlign.CENTER) + .build(); + } + + /** + * A closing-block heading: the disc, and the tracked words on its axis. + * + * @param section the host block + * @param iconToken the mark on the disc + * @param heading the heading text + * @param width the band's width + */ + static void sectionHead(SectionBuilder section, String iconToken, String heading, + double width) { + section.addContainer(container -> container + .name("SectionHead") + .rectangle(width, SECTION_DISC_SIZE) + .clipPolicy(ClipPolicy.OVERFLOW_VISIBLE) + .centerLeft(disc(SECTION_DISC_SIZE, iconToken, SECTION_DISC_ICON_SIZE)) + .position(tracked("SectionHeading", heading, SECTION_HEADING, + TRACK_SECTION_HEADING, PAPER), + SECTION_HEADING_OFFSET, 0, LayerAlign.CENTER_LEFT)); + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/MerchantClosing.java b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/MerchantClosing.java new file mode 100644 index 000000000..4ff97c111 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/MerchantClosing.java @@ -0,0 +1,258 @@ +package com.demcha.compose.document.templates.invoice.presets; + +import com.demcha.compose.document.dsl.ImageBuilder; +import com.demcha.compose.document.dsl.PageFlowBuilder; +import com.demcha.compose.document.dsl.ParagraphBuilder; +import com.demcha.compose.document.dsl.ShapeContainerBuilder; +import com.demcha.compose.document.node.DocumentLinkOptions; +import com.demcha.compose.document.node.DocumentNode; +import com.demcha.compose.document.node.TextAlign; +import com.demcha.compose.document.style.DocumentInsets; +import com.demcha.compose.document.style.DocumentRowColumn; +import com.demcha.compose.document.templates.core.identity.ContactUri; +import com.demcha.compose.document.templates.data.invoice.InvoiceBrand; +import com.demcha.compose.document.templates.data.invoice.InvoiceContactBlock; +import com.demcha.compose.document.templates.data.invoice.InvoiceNotesBlock; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; + +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.ACCENT; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.CLOSING_BOLD_SIZE; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.CLOSING_NOTE_H; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.CLOSING_REG_SIZE; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.CONTENT_W; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.FOOTER_ADDR_CAP_Y; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.FOOTER_ADDR_SIZE; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.FOOTER_MARK_W; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.FOOTER_NAME_CAP_Y; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.FOOTER_NAME_SIZE; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.FOOTER_ROW_TOP; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.FOOTER_RULE_Y; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.FOOTER_SITE_CAP_Y; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.FOOTER_SITE_SIZE; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.FOOTER_TEXT_PAD_L; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.FOOTER_TILE_H; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.FOOTER_TILE_W; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.INK; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.NOTE_GUTTER; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.NOTE_ICON; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.NOTE_LINE1_CAP_Y; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.NOTE_LINE2_CAP_Y; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.NOTE_TOP_Y; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.ON_FILL; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.PANEL_BOTTOM_Y; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.RULE_BOX; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.RULE_SOFT; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.RULE_THIN; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.TILE_RADIUS; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.bold; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.capGap; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.capTop; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.py; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.style; + +/** + * The foot of the sheet: a rule, the closing note beside its mark, a second + * rule, and the identity band pairing a marked tile against the issuer's own + * details. + * + *

    What this port leaves out

    + * + *

    The design closes with a row of three discs carrying social-platform marks. + * Those marks belong to their platforms, and the templates artifact does not + * redistribute other companies' trademarks, so neither they nor the rule that + * divided them off are drawn. The band keeps the tile and the issuer's identity, + * which is what identifies the sheet.

    + */ +final class MerchantClosing { + + private MerchantClosing() { + } + + /** The rule above the closing note. */ + static void renderNoteRule(PageFlowBuilder page) { + page.addLine(line -> line + .name("ClosingNoteRule") + .horizontal(CONTENT_W) + .thickness(RULE_THIN) + .color(RULE_SOFT) + .margin(new DocumentInsets(py(1331 - PANEL_BOTTOM_Y), 0, 0, 0))); + } + + /** + * The closing note: a lead line and the prose under it, with any address the + * prose names made reachable. + */ + static void renderNote(PageFlowBuilder page, InvoiceNotesBlock notes, + InvoiceContactBlock supplier) { + if (notes.heading().isBlank() && notes.paragraphs().isEmpty()) { + return; + } + page.addRow("ClosingNote", row -> { + row.spacing(0) + .columns(DocumentRowColumn.fixed(NOTE_GUTTER), DocumentRowColumn.weight(1)) + .margin(new DocumentInsets(py(NOTE_TOP_Y - 1331) - RULE_BOX, 0, 0, 0)); + row.addSection("ClosingNoteIcon", gutter -> { + gutter.spacing(0); + gutter.addSvgIcon(MerchantIcons.icon(MerchantIcons.INFO), NOTE_ICON); + }); + row.addSection("ClosingNoteText", text -> { + text.spacing(capGap(NOTE_LINE2_CAP_Y - NOTE_LINE1_CAP_Y, + CLOSING_BOLD_SIZE, true, CLOSING_REG_SIZE, false)) + .keepWithNext() + .margin(capTop(NOTE_LINE1_CAP_Y - NOTE_TOP_Y, CLOSING_BOLD_SIZE, true)); + if (!notes.heading().isBlank()) { + text.addParagraph(p -> p + .name("ClosingNoteLead") + .text(notes.heading()) + .textStyle(bold(CLOSING_BOLD_SIZE, INK))); + } + List paragraphs = notes.paragraphs(); + for (int i = 0; i < paragraphs.size(); i++) { + String prose = paragraphs.get(i); + int index = i; + text.addParagraph(p -> { + p.name("ClosingNoteProse_" + index); + writeReachable(p, prose, notes, supplier); + }); + } + }); + }); + } + + /** The rule above the identity band. */ + static void renderIdentityRule(PageFlowBuilder page) { + page.addLine(line -> line + .name("FooterRule") + .horizontal(CONTENT_W) + .thickness(RULE_THIN) + .color(RULE_SOFT) + .margin(new DocumentInsets( + Math.max(0, py(FOOTER_RULE_Y - NOTE_TOP_Y) - CLOSING_NOTE_H), 0, 0, 0))); + } + + /** + * The identity band: the issuer's mark on a filled tile, and its name, + * address and site beside it. + */ + static void renderIdentity(PageFlowBuilder page, InvoiceBrand brand, + InvoiceContactBlock supplier) { + page.addRow("FooterIdentity", row -> { + row.spacing(0) + .columns(DocumentRowColumn.fixed(FOOTER_TILE_W), + DocumentRowColumn.weight(1)); + row.add(tile(brand)); + row.addSection("FooterText", text -> { + text.spacing(0).padding(new DocumentInsets(0, 0, 0, FOOTER_TEXT_PAD_L)); + text.addParagraph(p -> p + .name("FooterName") + .text(supplier.legalName()) + .textStyle(bold(FOOTER_NAME_SIZE, INK)) + .margin(new DocumentInsets( + py(FOOTER_NAME_CAP_Y) - FOOTER_ROW_TOP + - MerchantStyles.topBearing(FOOTER_NAME_SIZE, true), + 0, 0, 0))); + text.addParagraph(p -> p + .name("FooterAddress") + .text(String.join(", ", supplier.addressLines())) + .textStyle(style(FOOTER_ADDR_SIZE, INK)) + .margin(new DocumentInsets( + capGap(FOOTER_ADDR_CAP_Y - FOOTER_NAME_CAP_Y, + FOOTER_NAME_SIZE, true, FOOTER_ADDR_SIZE, false), + 0, 0, 0))); + if (!supplier.website().isBlank()) { + text.addParagraph(p -> { + p.name("FooterSite"); + p.text(supplier.website()); + p.textStyle(style(FOOTER_SITE_SIZE, ACCENT)); + p.link(ContactUri.webLink(supplier.website())); + p.margin(new DocumentInsets( + capGap(FOOTER_SITE_CAP_Y - FOOTER_ADDR_CAP_Y, + FOOTER_ADDR_SIZE, false, FOOTER_SITE_SIZE, false), + 0, 0, 0)); + }); + } + }); + }); + } + + /** + * The filled tile the issuer's mark sits on. + * + *

    The mark is the caller's: the tile carries the brand's logo when there + * is one and the brand's monogram otherwise. The templates artifact carries + * no mark of its own, and the tile is the design's shape rather than a + * container for a particular drawing.

    + */ + private static DocumentNode tile(InvoiceBrand brand) { + DocumentNode content = brand.logo() != null + ? new ImageBuilder() + .name("FooterMark") + .source(brand.logo()) + .width(FOOTER_MARK_W) + .build() + : new ParagraphBuilder() + .name("FooterMonogram") + .text(monogram(brand)) + .textStyle(bold(FOOTER_NAME_SIZE, ON_FILL)) + .align(TextAlign.CENTER) + .build(); + return new ShapeContainerBuilder() + .name("FooterTile") + .roundedRect(FOOTER_TILE_W, FOOTER_TILE_H, TILE_RADIUS) + .fillColor(INK) + .center(content) + .build(); + } + + private static String monogram(InvoiceBrand brand) { + String stated = (brand.monogramTop() + brand.monogramBottom()).trim(); + if (!stated.isBlank()) { + return stated; + } + String name = brand.name().trim(); + return name.isEmpty() ? "" : name.substring(0, 1).toUpperCase(java.util.Locale.ENGLISH); + } + + private static void writeReachable(ParagraphBuilder paragraph, String prose, + InvoiceNotesBlock notes, InvoiceContactBlock supplier) { + List found = new ArrayList<>(); + addIfPresent(found, prose, notes.contactEmail(), ContactUri.mailLink(notes.contactEmail())); + addIfPresent(found, prose, supplier.email(), ContactUri.mailLink(supplier.email())); + addIfPresent(found, prose, supplier.website(), ContactUri.webLink(supplier.website())); + found.sort(Comparator.comparingInt(Reachable::at)); + + int cursor = 0; + for (Reachable reachable : found) { + if (reachable.at() < cursor) { + continue; + } + if (reachable.at() > cursor) { + paragraph.inlineText(prose.substring(cursor, reachable.at()), + style(CLOSING_REG_SIZE, INK)); + } + paragraph.inlineText(reachable.text(), style(CLOSING_REG_SIZE, ACCENT), + reachable.link()); + cursor = reachable.at() + reachable.text().length(); + } + if (cursor < prose.length()) { + paragraph.inlineText(prose.substring(cursor), style(CLOSING_REG_SIZE, INK)); + } + } + + private static void addIfPresent(List found, String prose, String text, + DocumentLinkOptions link) { + if (text == null || text.isBlank() || link == null) { + return; + } + int at = prose.indexOf(text); + if (at >= 0) { + found.add(new Reachable(at, text, link)); + } + } + + private record Reachable(int at, String text, DocumentLinkOptions link) { + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/MerchantIcons.java b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/MerchantIcons.java new file mode 100644 index 000000000..c91e2904c --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/MerchantIcons.java @@ -0,0 +1,77 @@ +package com.demcha.compose.document.templates.invoice.presets; + +import com.demcha.compose.document.svg.SvgIcon; + +import java.io.IOException; +import java.io.InputStream; +import java.io.UncheckedIOException; +import java.nio.charset.StandardCharsets; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +/** + * The marks the Merchant invoice packages. + * + *

    Seven the preset chooses itself — the two party discs, the bank panel, its + * note, the due-by card, the closing note, and the three contact channels. The + * other four are a vocabulary a document may name on a service line through + * {@code InvoiceServiceLines.Line.icon()}.

    + */ +final class MerchantIcons { + + static final String BILL_TO = "bill-to"; + static final String SHIP_TO = "ship-to"; + static final String BANK = "bank"; + static final String CALENDAR = "calendar"; + static final String INFO = "info"; + static final String WEBSITE = "website"; + static final String EMAIL = "email"; + static final String PHONE = "phone"; + + private static final String ICON_ROOT = "/templates/invoice/merchant/icons/"; + + /** The marks a document may name on a service line. */ + static final Set LINE_TOKENS = Set.of("bag", "tag", "globe", "support"); + + private static final Set TOKENS = Set.of( + BILL_TO, SHIP_TO, BANK, CALENDAR, INFO, WEBSITE, EMAIL, PHONE, + "bag", "tag", "globe", "support"); + + private static final Map CACHE = new ConcurrentHashMap<>(); + + private MerchantIcons() { + } + + /** + * The packaged mark a token names. + * + * @param token one of this preset's tokens + * @return the parsed icon + * @throws IllegalArgumentException when the token names no packaged mark, + * listing the ones a document may choose — an icon token is data, so + * a wrong one is a data error and not a missing resource + */ + static SvgIcon icon(String token) { + if (!TOKENS.contains(token)) { + throw new IllegalArgumentException( + "Unknown merchant invoice icon token '" + token + + "'. This preset packages " + LINE_TOKENS.stream().sorted().toList() + + " for a service line's mark."); + } + return CACHE.computeIfAbsent(token, MerchantIcons::read); + } + + private static SvgIcon read(String token) { + String resourcePath = ICON_ROOT + token + ".svg"; + try (InputStream input = MerchantIcons.class.getResourceAsStream(resourcePath)) { + if (input == null) { + throw new IllegalStateException("Missing merchant invoice icon: " + resourcePath); + } + return SvgIcon.parse(new String(input.readAllBytes(), StandardCharsets.UTF_8)); + } catch (IOException e) { + throw new UncheckedIOException( + "Failed to read merchant invoice icon: " + resourcePath, e); + } + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/MerchantInvoice.java b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/MerchantInvoice.java new file mode 100644 index 000000000..f1e0ac1a5 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/MerchantInvoice.java @@ -0,0 +1,177 @@ +package com.demcha.compose.document.templates.invoice.presets; + +import com.demcha.compose.document.api.DocumentSession; +import com.demcha.compose.document.api.PageMarginRule; +import com.demcha.compose.document.output.DocumentHeaderFooter; +import com.demcha.compose.document.output.DocumentHeaderFooterZone; +import com.demcha.compose.document.style.DocumentInsets; +import com.demcha.compose.document.templates.api.DocumentTemplate; +import com.demcha.compose.document.templates.data.invoice.StructuredInvoiceData; + +import java.util.List; +import java.util.Objects; + +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.FACE; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.IDENTITY_H; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.INK; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.MASTHEAD_H; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.PAGE; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.PAGE_MARGIN_FIRST; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.PAGE_MARGIN_LATER; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.PAGE_NUMBER_BAND; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.PAGE_NUMBER_SIZE; + +/** + * Merchant — a commerce invoice with a green accent: a lockup against the title + * over a short accent rule, the supplier's details beside the invoice's + * metadata, two addressed parties on filled discs, a line-item table whose rows + * are bordered boxes, a bank panel beside the totals and the due-by card, a + * closing note, and an identity band closing the sheet on a marked tile. + * + *

    Each table row is one cell

    + * + *

    A cell's stroke draws all four of its edges, which is exactly what this + * design's rows are: a soft box round each one and no interior verticals. So a + * row is a single cell spanning every column with the five columns composed + * inside it — stroking five real cells would draw four verticals the design does + * not have.

    + * + *

    An inline mark sets the line box

    + * + *

    The supplier's contact rows are the one block whose line box is not its + * type: a mark taller than the text sets the box, so a pitch measured from the + * text and solved against the text lands short by the difference. Those pitches + * are solved against the mark instead, which is measured rather than assumed — + * the first render asked for 28.5 design pixels and laid out 31.

    + * + *

    Two blocks disagree about where the page starts

    + * + *

    The lockup's ink and the title's box sit at different heights, so the flow + * opens at the earlier of the two and each carries the difference as its own + * margin. That is also why the rules under the masthead and the identity band + * are placed against those blocks' computed heights rather than against a + * guessed one.

    + * + *

    It flows

    + * + *

    The design shows four service lines and real billing brings dozens, so the + * table is the region that grows: its header repeats on every page it reaches, + * and a continuation page reserves the whole enumeration band where page one + * does not — page one ends in a footer band the design already leaves room + * under, while a continuation page ends in table rows, and a table row has no + * hole in its middle for a number to sit in.

    + * + *

    What this port leaves out

    + * + *

    The design closes with a row of three discs carrying social-platform marks. + * Those marks belong to their platforms, and the templates artifact does not + * redistribute other companies' trademarks, so neither they nor the rule that + * divided them off are drawn. The band keeps the tile and the issuer's identity, + * which is what identifies the sheet.

    + * + *

    Money and marks

    + * + *

    The currency is stated once in each money column's caption and the figures + * under it are written bare, with the code carried only on the grand total, + * which stands under no caption. A quantity of nothing — a fee charged as it + * falls rather than per unit — prints the design's own dash, because a zero in a + * quantity column reads as none delivered rather than as not counted. A service + * line names its own mark from this preset's vocabulary, and an unknown token is + * reported as a data error naming the set.

    + * + *

    Fonts

    + * + *

    The sheet is set in Gothic A1, which is the family every size was solved + * against. The templates artifact carries no fonts — register it on the session, + * or the engine substitutes and every size is solved for type that is not + * there.

    + * + *

    Usage:

    + *
    {@code
    + * DocumentTemplate template = MerchantInvoice.create();
    + * template.compose(session, invoice);
    + * }
    + * + * @since 2.4.0 + */ +public final class MerchantInvoice { + + /** Stable identifier of this preset. */ + public static final String ID = "merchant-invoice"; + + /** Human-readable name of this preset. */ + public static final String DISPLAY_NAME = "Merchant Invoice"; + + private MerchantInvoice() { + } + + /** + * Creates the template. + * + * @return a template composing a {@link StructuredInvoiceData} + */ + public static DocumentTemplate create() { + return new Template(); + } + + private record Template() implements DocumentTemplate { + + @Override + public String id() { + return ID; + } + + @Override + public String displayName() { + return DISPLAY_NAME; + } + + @Override + public void compose(DocumentSession document, StructuredInvoiceData data) { + Objects.requireNonNull(document, "document"); + Objects.requireNonNull(data, "data"); + + document.pageSize(PAGE); + document.margin(PAGE_MARGIN_FIRST); + document.pageMargins(List.of(PageMarginRule.from(2, PAGE_MARGIN_LATER))); + renderPageNumber(document); + + String currency = data.currencyCode(); + document.pageFlow(page -> { + page.name("MerchantInvoice").padding(DocumentInsets.zero()).spacing(0); + MerchantMasthead.renderBrandHeader(page, data.brand(), data.masthead()); + MerchantMasthead.renderTitleRule(page, MASTHEAD_H); + MerchantMasthead.renderIdentityRow(page, data.supplier(), data.masthead()); + MerchantMasthead.renderIdentityRule(page, IDENTITY_H); + MerchantParties.render(page, data.billTo(), data.shipTo()); + MerchantLines.render(page, data.serviceLines(), currency); + MerchantSettlement.render(page, data.payment(), data.totals(), currency); + MerchantClosing.renderNoteRule(page); + MerchantClosing.renderNote(page, data.notes(), data.supplier()); + MerchantClosing.renderIdentityRule(page); + MerchantClosing.renderIdentity(page, data.brand(), data.supplier()); + }); + } + + /** + * The enumeration. The design has none — it is one sheet — but the + * document flows, and a reader holding page 1 of 2 has to be able to tell + * that page 2 is missing. + * + *

    The face is stated because the zone's own default is the standard-14 + * one, which would leave the number the single line on the sheet in a + * family the design never uses.

    + */ + private static void renderPageNumber(DocumentSession document) { + document.footer(DocumentHeaderFooter.builder() + .zone(DocumentHeaderFooterZone.FOOTER) + .height((float) PAGE_NUMBER_BAND) + .fontName(FACE) + .fontSize((float) PAGE_NUMBER_SIZE) + .textColor(INK) + .centerText("Page {page} of {pages}") + .showSeparator(false) + .build()); + } + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/MerchantLines.java b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/MerchantLines.java new file mode 100644 index 000000000..4c6dafb21 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/MerchantLines.java @@ -0,0 +1,224 @@ +package com.demcha.compose.document.templates.invoice.presets; + +import com.demcha.compose.document.dsl.PageFlowBuilder; +import com.demcha.compose.document.dsl.SectionBuilder; +import com.demcha.compose.document.dsl.TableBuilder; +import com.demcha.compose.document.node.DocumentNode; +import com.demcha.compose.document.node.LayerAlign; +import com.demcha.compose.document.node.RowVerticalAlign; +import com.demcha.compose.document.node.TextAlign; +import com.demcha.compose.document.style.DocumentInsets; +import com.demcha.compose.document.style.DocumentRowColumn; +import com.demcha.compose.document.style.DocumentStroke; +import com.demcha.compose.document.table.DocumentTableCell; +import com.demcha.compose.document.table.DocumentTableColumn; +import com.demcha.compose.document.table.DocumentTableStyle; +import com.demcha.compose.document.table.DocumentTableTextAnchor; +import com.demcha.compose.document.templates.data.invoice.InvoiceServiceLines; + +import java.util.ArrayList; +import java.util.List; + +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.CELL_SIZE; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.COLUMN_CENTRED; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.COLUMN_SHARES; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.CONTENT_W; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.INK; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.ITEM_TITLE_SIZE; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.LINE_BOX; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.ON_FILL; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.PARTY_BLOCK_H; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.RULE_ROW; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.RULE_SOFT; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.TABLE_HEADER_H; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.TABLE_HEAD_SIZE; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.TABLE_ICON; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.TABLE_ICON_GUTTER; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.TABLE_PAD_CENTRED; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.TABLE_PAD_EDGE; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.TABLE_PAD_HEAD; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.TABLE_PAD_ICON; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.TABLE_ROW_H; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.WHITE; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.bold; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.capGap; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.py; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.style; + +/** + * The line-item table: a dark caps header that repeats on every page it reaches, + * and one bordered row per service line. + * + *

    Each row is one cell

    + * + *

    A cell's stroke draws all four of its edges, which is exactly what this + * design's rows are — a soft box round each one, with no interior verticals. So + * a row is a single cell spanning every column, and the five columns are a + * composed row inside it. Stroking five real cells would draw four verticals the + * design does not have.

    + */ +final class MerchantLines { + + private MerchantLines() { + } + + /** + * The table. + * + * @param page the page flow + * @param serviceLines the lines and their column captions + * @param currencyCode the code the two money captions carry + */ + static void render(PageFlowBuilder page, InvoiceServiceLines serviceLines, + String currencyCode) { + List lines = serviceLines.lines(); + page.addTable(table -> { + table.name("LineItems").width(CONTENT_W); + table.margin(new DocumentInsets(py(667 - 451) - PARTY_BLOCK_H, 0, 0, 0)); + List columns = new ArrayList<>(); + for (double share : COLUMN_SHARES) { + columns.add(DocumentTableColumn.fixed(CONTENT_W * share)); + } + table.columns(columns.toArray(new DocumentTableColumn[0])); + renderHeader(table, serviceLines.columns(), currencyCode); + for (int i = 0; i < lines.size(); i++) { + table.rowCells(List.of(rowCell(lines.get(i), i, currencyCode))); + } + }); + } + + /** + * The captions, with the currency stated once in each money column. + * + *

    The design puts the code in the caption and writes the figures under it + * bare, which is why nothing in the table carries a currency of its own.

    + */ + private static List captions(InvoiceServiceLines.Columns columns, + String currencyCode) { + return List.of( + columns.description(), + columns.servicePeriod(), + columns.quantity(), + MerchantText.captionWithCurrency(columns.unitPrice(), currencyCode), + MerchantText.captionWithCurrency(columns.amount(), currencyCode)); + } + + private static void renderHeader(TableBuilder table, InvoiceServiceLines.Columns columns, + String currencyCode) { + List captions = captions(columns, currencyCode); + double pad = (TABLE_HEADER_H - LINE_BOX * TABLE_HEAD_SIZE) / 2.0; + List cells = new ArrayList<>(); + for (int i = 0; i < captions.size(); i++) { + DocumentInsets insets = COLUMN_CENTRED[i] + ? new DocumentInsets(pad, TABLE_PAD_CENTRED, pad, TABLE_PAD_CENTRED) + : new DocumentInsets(pad, TABLE_PAD_EDGE, pad, TABLE_PAD_HEAD); + DocumentTableStyle cellStyle = DocumentTableStyle.builder() + .padding(insets) + .fillColor(INK) + .stroke(DocumentStroke.of(INK, 0)) + .textStyle(bold(TABLE_HEAD_SIZE, ON_FILL)) + .textAnchor(COLUMN_CENTRED[i] + ? DocumentTableTextAnchor.CENTER + : DocumentTableTextAnchor.CENTER_LEFT) + .build(); + cells.add(new DocumentTableCell(List.of(captions.get(i)), cellStyle)); + } + table.headerCells(cells); + table.repeatHeader(); + } + + /** One service line: a bordered cell across every column, holding a row of five. */ + private static DocumentTableCell rowCell(InvoiceServiceLines.Line line, int index, + String currencyCode) { + double pad = (TABLE_ROW_H - descriptionHeight()) / 2.0; + DocumentTableStyle cellStyle = DocumentTableStyle.builder() + .padding(DocumentInsets.zero()) + .fillColor(WHITE) + .stroke(DocumentStroke.of(RULE_SOFT, RULE_ROW)) + .textStyle(style(CELL_SIZE, INK)) + .build(); + + SectionBuilder holder = new SectionBuilder(); + holder.name("LineRow_" + index + "Holder"); + holder.addRow("LineRow_" + index, row -> { + row.spacing(0) + .verticalAlign(RowVerticalAlign.CENTER) + .padding(new DocumentInsets(pad, 0, pad, 0)); + List widths = new ArrayList<>(); + for (double share : COLUMN_SHARES) { + widths.add(DocumentRowColumn.fixed(CONTENT_W * share)); + } + row.columns(widths.toArray(new DocumentRowColumn[0])); + row.add(descriptionCell(line, index)); + row.addParagraph(p -> p.name("LineService_" + index) + .text(line.servicePeriod()).textStyle(style(CELL_SIZE, INK)) + .align(TextAlign.CENTER)); + row.addParagraph(p -> p.name("LineQuantity_" + index) + .text(MerchantText.quantity(line.quantity())) + .textStyle(style(CELL_SIZE, INK)).align(TextAlign.CENTER)); + row.addParagraph(p -> p.name("LineUnitPrice_" + index) + .text(MerchantText.money(line.unitPrice())) + .textStyle(style(CELL_SIZE, INK)).align(TextAlign.CENTER)); + row.addParagraph(p -> p.name("LineAmount_" + index) + .text(MerchantText.money(line.amount())) + .textStyle(style(CELL_SIZE, INK)).align(TextAlign.CENTER)); + }); + return new DocumentTableCell(List.of(), cellStyle, COLUMN_SHARES.length, 1, + holder.build()); + } + + /** + * The mark and the two-line description beside it. + * + *

    The mark hangs in a fixed gutter and the text takes the rest, so a + * longer product name wraps under itself rather than under the mark. A line + * that names no mark keeps the gutter, so the text lanes stay on their axis + * down the column.

    + */ + private static DocumentNode descriptionCell(InvoiceServiceLines.Line line, int index) { + String token = line.icon(); + // The block is a row and it sits inside one, which the layout compiler + // refuses — so it goes through a LayerStack layer, the same way every + // other horizontal pair on this sheet does. + SectionBuilder inner = new SectionBuilder(); + inner.name("LineDescription_" + index + "Holder"); + inner.addRow("LineDescription_" + index, cell -> { + cell.spacing(0) + .verticalAlign(RowVerticalAlign.CENTER) + .padding(new DocumentInsets(0, 0, 0, TABLE_PAD_ICON)) + .columns(DocumentRowColumn.fixed(TABLE_ICON_GUTTER), + DocumentRowColumn.weight(1)); + cell.addSection("LineIcon_" + index, gutter -> { + gutter.spacing(0); + if (!token.isBlank()) { + gutter.addSvgIcon(MerchantIcons.icon(token), TABLE_ICON); + } + }); + cell.addSection("LineText_" + index, text -> { + text.spacing(capGap(24, ITEM_TITLE_SIZE, true, CELL_SIZE, false)); + text.addParagraph(p -> p + .name("LineTitle_" + index) + .text(line.title()) + .textStyle(bold(ITEM_TITLE_SIZE, INK))); + text.addParagraph(p -> p + .name("LineSubtitle_" + index) + .text(line.description()) + .textStyle(style(CELL_SIZE, INK))); + }); + }); + DocumentNode row = inner.build(); + SectionBuilder layered = new SectionBuilder(); + layered.name("LineDescription_" + index + "Layer"); + layered.addLayerStack(stack -> stack + .name("LineDescription_" + index + "Stack") + .layer(row, LayerAlign.TOP_LEFT, 0)); + return layered.build(); + } + + /** The tallest cell in a row, and therefore what its padding is solved against. */ + private static double descriptionHeight() { + return LINE_BOX * ITEM_TITLE_SIZE + + capGap(24, ITEM_TITLE_SIZE, true, CELL_SIZE, false) + + LINE_BOX * CELL_SIZE; + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/MerchantMasthead.java b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/MerchantMasthead.java new file mode 100644 index 000000000..5e8f2c0a1 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/MerchantMasthead.java @@ -0,0 +1,253 @@ +package com.demcha.compose.document.templates.invoice.presets; + +import com.demcha.compose.document.dsl.ImageBuilder; +import com.demcha.compose.document.dsl.PageFlowBuilder; +import com.demcha.compose.document.dsl.RowBuilder; +import com.demcha.compose.document.dsl.SectionBuilder; +import com.demcha.compose.document.node.DocumentLinkOptions; +import com.demcha.compose.document.node.DocumentNode; +import com.demcha.compose.document.node.InlineImageAlignment; +import com.demcha.compose.document.node.LayerAlign; +import com.demcha.compose.document.node.TextAlign; +import com.demcha.compose.document.style.DocumentInsets; +import com.demcha.compose.document.style.DocumentRowColumn; +import com.demcha.compose.document.templates.core.identity.ContactUri; +import com.demcha.compose.document.templates.data.invoice.InvoiceBrand; +import com.demcha.compose.document.templates.data.invoice.InvoiceContactBlock; +import com.demcha.compose.document.templates.data.invoice.InvoiceMasthead; + +import java.util.List; +import java.util.function.Consumer; + +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.ACCENT; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.BODY_SIZE; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.CONTACT_GAP_PX; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.CONTACT_ICON; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.CONTACT_SIZE; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.CONTENT_W; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.DIVIDER_W; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.INK; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.LEFT_COLUMN_W; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.LOGO_MARGIN_T; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.LOGO_W; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.META_LABEL_W; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.META_PAD_L; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.RULE_SOFT; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.RULE_STRONG; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.RULE_THIN; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.SUPPLIER_NAME_SIZE; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.SUPPLIER_TAX_GAP_PX; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.TITLE_MARGIN_T; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.TITLE_RULE_W; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.TITLE_SIZE; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.TOP_BEARING_REGULAR; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.bold; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.capGap; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.capTop; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.inkGapToImage; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.px; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.py; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.spaces; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.style; + +/** + * The head of the sheet: the lockup against the title over its accent rule, then + * the supplier's details beside the invoice's metadata, split by a hairline. + */ +final class MerchantMasthead { + + private MerchantMasthead() { + } + + /** + * The lockup and the title. + * + *

    The two disagree about where the page starts — the lockup's ink and the + * title's box sit at different heights — so the flow opens at the earlier of + * them and each carries the difference as its own margin. The mark is the + * caller's: the lockup draws the brand's logo at the design's measured width, + * or the brand's name when there is none.

    + */ + static void renderBrandHeader(PageFlowBuilder page, InvoiceBrand brand, + InvoiceMasthead masthead) { + page.addRow("Masthead", row -> { + row.spacing(0) + .columns(DocumentRowColumn.fixed(LOGO_W), DocumentRowColumn.weight(1)); + row.addSection("BrandLockup", cell -> { + cell.spacing(0); + if (brand.logo() != null) { + // The lockup's ink starts two pixels inside the content + // column's own left edge. + cell.add(new ImageBuilder() + .name("BrandLogo") + .source(brand.logo()) + .width(LOGO_W) + .margin(new DocumentInsets(LOGO_MARGIN_T, 0, 0, px(2))) + .build()); + } else if (!brand.name().isBlank()) { + cell.addParagraph(p -> p + .name("BrandWordmark") + .text(brand.name()) + .textStyle(bold(SUPPLIER_NAME_SIZE, INK)) + .margin(new DocumentInsets(LOGO_MARGIN_T, 0, 0, px(2)))); + } + }); + row.addParagraph(p -> p + .name("InvoiceTitle") + .text(masthead.title()) + .textStyle(bold(TITLE_SIZE, INK)) + .align(TextAlign.RIGHT) + .margin(new DocumentInsets(TITLE_MARGIN_T, 0, 0, 0))); + }); + } + + /** The short accent rule under the title, at the design's own inset from the right. */ + static void renderTitleRule(PageFlowBuilder page, double mastheadHeight) { + page.addLine(line -> line + .name("TitleRule") + .horizontal(TITLE_RULE_W) + .thickness(py(2.6)) + .color(ACCENT) + .margin(new DocumentInsets( + Math.max(0, py(122) - MerchantStyles.MARGIN_TOP - mastheadHeight), + 0, 0, CONTENT_W - TITLE_RULE_W))); + } + + /** The supplier's details against the invoice's metadata. */ + static void renderIdentityRow(PageFlowBuilder page, InvoiceContactBlock supplier, + InvoiceMasthead masthead) { + page.addRow("IdentityRow", row -> { + row.spacing(0) + .columns(DocumentRowColumn.fixed(LEFT_COLUMN_W), + DocumentRowColumn.fixed(DIVIDER_W), + DocumentRowColumn.weight(1)) + // The row's top is the divider's; the rule above it occupies a + // whole point whatever it paints. + .margin(new DocumentInsets( + py(144 - 122) - MerchantStyles.RULE_BOX, 0, 0, 0)); + row.addSection("SupplierColumn", column -> renderSupplier(column, supplier)); + row.addSection("IdentityColumnDivider", column -> { + column.spacing(0); + column.addLine(line -> line + .name("IdentityColumnDivider") + .vertical(py(391 - 144)) + .thickness(DIVIDER_W) + .color(RULE_SOFT)); + }); + row.addSection("MetaColumn", column -> renderMeta(column, masthead)); + }); + } + + /** The rule under both columns. */ + static void renderIdentityRule(PageFlowBuilder page, double identityHeight) { + page.addLine(line -> line + .name("IdentityRule") + .horizontal(CONTENT_W) + .thickness(RULE_THIN) + .color(RULE_STRONG) + .margin(new DocumentInsets( + Math.max(0, py(423 - 144) - identityHeight), 0, 0, 0))); + } + + private static void renderSupplier(SectionBuilder column, InvoiceContactBlock supplier) { + column.spacing(0); + column.addParagraph(p -> p + .name("SupplierName") + .text(supplier.legalName()) + .textStyle(bold(SUPPLIER_NAME_SIZE, INK)) + .margin(capTop(154 - 144, SUPPLIER_NAME_SIZE, true))); + + column.addSection("SupplierAddress", block -> { + block.spacing(capGap(25, BODY_SIZE, false, BODY_SIZE, false)) + .margin(new DocumentInsets( + capGap(186 - 154, SUPPLIER_NAME_SIZE, true, BODY_SIZE, false), + 0, 0, 0)); + List lines = supplier.addressLines(); + for (int i = 0; i < lines.size(); i++) { + String line = lines.get(i); + int index = i; + block.addParagraph(p -> p + .name("SupplierAddress_" + index) + .text(line) + .textStyle(style(BODY_SIZE, INK))); + } + }); + + // The three contact rows are the one block on this sheet whose line box + // is not the type: an inline mark taller than the text sets the box, so + // both the pitch and the block's own start are measured against the mark. + column.addSection("SupplierContact", block -> { + block.spacing(py(28.5) - CONTACT_ICON) + .margin(new DocumentInsets( + inkGapToImage(280 - 236, BODY_SIZE, false), 0, 0, 0)); + channel(block, "Website", MerchantIcons.WEBSITE, supplier.website(), + ContactUri.webLink(supplier.website())); + channel(block, "Email", MerchantIcons.EMAIL, supplier.email(), + ContactUri.mailLink(supplier.email())); + channel(block, "Phone", MerchantIcons.PHONE, supplier.phone(), + ContactUri.telLink(supplier.phone())); + }); + + if (!supplier.taxRegistrationNumber().isBlank()) { + column.addParagraph(p -> { + p.name("SupplierTaxId"); + p.inlineText(supplier.taxRegistrationLabel(), style(BODY_SIZE, INK)); + p.inlineText(spaces(SUPPLIER_TAX_GAP_PX, BODY_SIZE), style(BODY_SIZE, INK)); + p.inlineText(supplier.taxRegistrationNumber(), style(BODY_SIZE, INK)); + // Measured from the last contact mark, for the same reason: the + // block above ends at its mark's foot, not at a text box's. + p.margin(new DocumentInsets( + py(377 - 337) - CONTACT_ICON - TOP_BEARING_REGULAR * BODY_SIZE, + 0, 0, 0)); + }); + } + } + + private static void channel(SectionBuilder block, String name, String token, + String value, DocumentLinkOptions link) { + if (value.isBlank()) { + return; + } + block.addParagraph(p -> { + p.name("SupplierContact" + name); + p.inlineSvgIcon(MerchantIcons.icon(token), CONTACT_ICON, InlineImageAlignment.CENTER); + p.inlineText(spaces(CONTACT_GAP_PX, CONTACT_SIZE), style(CONTACT_SIZE, INK)); + if (link == null) { + p.inlineText(value, style(CONTACT_SIZE, INK)); + } else { + p.inlineText(value, style(CONTACT_SIZE, INK), link); + } + }); + } + + private static void renderMeta(SectionBuilder column, InvoiceMasthead masthead) { + column.spacing(capGap(40.8, BODY_SIZE, false, BODY_SIZE, false)) + .padding(new DocumentInsets(0, 0, 0, META_PAD_L)) + .margin(capTop(156 - 144, BODY_SIZE, false)); + List entries = masthead.entries(); + for (int i = 0; i < entries.size(); i++) { + InvoiceMasthead.Entry entry = entries.get(i); + int index = i; + layeredRow(column, "MetaRow_" + index, row -> { + row.spacing(0) + .columns(DocumentRowColumn.fixed(META_LABEL_W), + DocumentRowColumn.weight(1)); + row.addParagraph(p -> p.text(entry.label()).textStyle(style(BODY_SIZE, INK))); + row.addParagraph(p -> p + .text(entry.value()) + .textStyle(style(BODY_SIZE, entry.emphasized() ? ACCENT : INK))); + }); + } + } + + /** A row wrapped in a layer, so it can sit inside a row cell. */ + static void layeredRow(SectionBuilder parent, String name, Consumer spec) { + SectionBuilder holder = new SectionBuilder(); + holder.name(name + "Holder"); + holder.addRow(name, spec); + DocumentNode node = holder.build(); + parent.addLayerStack(stack -> stack + .name(name + "Layer") + .layer(node, LayerAlign.TOP_LEFT, 0)); + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/MerchantParties.java b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/MerchantParties.java new file mode 100644 index 000000000..4bfbf33fe --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/MerchantParties.java @@ -0,0 +1,142 @@ +package com.demcha.compose.document.templates.invoice.presets; + +import com.demcha.compose.document.dsl.PageFlowBuilder; +import com.demcha.compose.document.dsl.SectionBuilder; +import com.demcha.compose.document.dsl.ShapeContainerBuilder; +import com.demcha.compose.document.node.RowVerticalAlign; +import com.demcha.compose.document.style.DocumentInsets; +import com.demcha.compose.document.style.DocumentRowColumn; +import com.demcha.compose.document.templates.data.invoice.InvoiceRecipient; + +import java.util.List; + +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.ACCENT; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.BILL_TO_PAD_L; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.BODY_SIZE; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.DIVIDER_W; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.INK; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.LEFT_COLUMN_W; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.PARTY_DISC_D; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.PARTY_DISC_GLYPH; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.PARTY_GUTTER; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.PARTY_LABEL_SIZE; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.PARTY_NAME_SIZE; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.PARTY_TAX_GAP_PX; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.RULE_BOX; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.RULE_SOFT; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.SHIP_TO_PAD_L; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.bold; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.capGap; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.capTop; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.py; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.spaces; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.style; +import static com.demcha.compose.document.templates.invoice.presets.MerchantMasthead.layeredRow; + +/** + * The two addressed parties, each under a heading opening with a filled disc. + */ +final class MerchantParties { + + private MerchantParties() { + } + + /** + * The parties row. + * + * @param page the page flow + * @param billTo the billed party + * @param shipTo the shipped-to party, or {@code null} when the invoice ships + * nowhere and the column stays empty + */ + static void render(PageFlowBuilder page, InvoiceRecipient billTo, InvoiceRecipient shipTo) { + page.addRow("PartiesRow", row -> { + row.spacing(0) + .columns(DocumentRowColumn.fixed(LEFT_COLUMN_W), + DocumentRowColumn.fixed(DIVIDER_W), + DocumentRowColumn.weight(1)) + // The row's top is the discs'; the rule above it occupies a + // whole point whatever it paints. + .margin(new DocumentInsets(py(451 - 423) - RULE_BOX, 0, 0, 0)); + row.addSection("BillToColumn", column -> + renderParty(column, "BillTo", billTo, MerchantIcons.BILL_TO, BILL_TO_PAD_L)); + row.addSection("PartyColumnDivider", column -> { + column.spacing(0); + // The divider outlasts both columns' text, which is why its own + // length is measured rather than taken from either of them. + column.addLine(line -> line + .name("PartyColumnDivider") + .vertical(py(640 - 452)) + .thickness(DIVIDER_W) + .color(RULE_SOFT) + .margin(new DocumentInsets(py(452 - 451), 0, 0, 0))); + }); + row.addSection("ShipToColumn", column -> { + if (shipTo != null) { + renderParty(column, "ShipTo", shipTo, MerchantIcons.SHIP_TO, SHIP_TO_PAD_L); + } else { + column.spacing(0); + } + }); + }); + } + + private static void renderParty(SectionBuilder column, String id, InvoiceRecipient party, + String token, double padLeft) { + column.spacing(0).padding(new DocumentInsets(0, 0, 0, padLeft)); + + layeredRow(column, id + "Label", row -> { + row.spacing(0) + .verticalAlign(RowVerticalAlign.CENTER) + .columns(DocumentRowColumn.fixed(PARTY_GUTTER), DocumentRowColumn.weight(1)); + row.add(new ShapeContainerBuilder() + .name(id + "Disc") + .circle(PARTY_DISC_D) + .fillColor(ACCENT) + .center(MerchantIcons.icon(token).node(PARTY_DISC_GLYPH)) + .build()); + row.addParagraph(p -> p + .name(id + "LabelText") + .text(party.heading()) + .textStyle(bold(PARTY_LABEL_SIZE, ACCENT))); + }); + + column.addSection(id + "Body", block -> { + block.spacing(0).padding(new DocumentInsets(0, 0, 0, PARTY_GUTTER)); + // The label row is as tall as the disc, so the party name is measured + // from the disc's foot rather than from the label's. + block.addParagraph(p -> p + .name(id + "Name") + .text(party.name()) + .textStyle(bold(PARTY_NAME_SIZE, INK)) + .margin(capTop(498 - 495, PARTY_NAME_SIZE, true))); + + block.addSection(id + "Address", lines -> { + lines.spacing(capGap(24, BODY_SIZE, false, BODY_SIZE, false)) + .margin(new DocumentInsets( + capGap(527 - 498, PARTY_NAME_SIZE, true, BODY_SIZE, false), + 0, 0, 0)); + List address = party.addressLines(); + for (int i = 0; i < address.size(); i++) { + String line = address.get(i); + int index = i; + lines.addParagraph(p -> p + .name(id + "Address_" + index) + .text(line) + .textStyle(style(BODY_SIZE, INK))); + } + }); + + if (!party.registrationNumber().isBlank()) { + block.addParagraph(p -> { + p.name(id + "Registration"); + p.inlineText(party.registrationLabel(), style(BODY_SIZE, INK)); + p.inlineText(spaces(PARTY_TAX_GAP_PX, BODY_SIZE), style(BODY_SIZE, INK)); + p.inlineText(party.registrationNumber(), style(BODY_SIZE, INK)); + p.margin(new DocumentInsets( + capGap(621 - 575, BODY_SIZE, false, BODY_SIZE, false), 0, 0, 0)); + }); + } + }); + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/MerchantSettlement.java b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/MerchantSettlement.java new file mode 100644 index 000000000..850808f03 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/MerchantSettlement.java @@ -0,0 +1,313 @@ +package com.demcha.compose.document.templates.invoice.presets; + +import com.demcha.compose.document.dsl.PageFlowBuilder; +import com.demcha.compose.document.dsl.SectionBuilder; +import com.demcha.compose.document.node.RowVerticalAlign; +import com.demcha.compose.document.node.TextAlign; +import com.demcha.compose.document.style.DocumentInsets; +import com.demcha.compose.document.style.DocumentRowColumn; +import com.demcha.compose.document.style.DocumentStroke; +import com.demcha.compose.document.templates.core.identity.ContactUri; +import com.demcha.compose.document.templates.data.invoice.InvoicePaymentBlock; +import com.demcha.compose.document.templates.data.invoice.InvoiceTotalsBlock; + +import java.util.List; + +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.ACCENT; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.BORDER_SOFT; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.CARD_BOTTOM_Y; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.CARD_GUTTER; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.CARD_ICON; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.CARD_ICON_Y; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.CARD_LABEL_CAP_Y; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.CARD_PAD_L; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.CARD_TOP_Y; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.CARD_VALUE_CAP_Y; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.DUE_LABEL_SIZE; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.DUE_VALUE_SIZE; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.INK; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.LINE_BOX; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.PANEL_BOTTOM_Y; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.PANEL_HEAD_GUTTER; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.PANEL_HEAD_SIZE; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.PANEL_ICON; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.PANEL_ICON_PAD_L; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.PANEL_ICON_Y; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.PANEL_LABEL_W; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.PANEL_LAST_ROW_CAP_Y; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.PANEL_NOTE_CAP_Y; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.PANEL_NOTE_GUTTER; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.PANEL_NOTE_ICON; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.PANEL_NOTE_PITCH_Y; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.PANEL_NOTE_SIZE; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.PANEL_PAD_L; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.PANEL_PAD_R; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.PANEL_RADIUS; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.PANEL_ROW1_CAP_Y; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.PANEL_ROW_PITCH_Y; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.PANEL_ROW_SIZE; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.PANEL_RULE_PAD_L; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.PANEL_RULE_W; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.PANEL_RULE_Y; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.PANEL_TOP_Y; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.PANEL_W; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.RULE_BOX; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.RULE_MEDIUM; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.RULE_SOFT; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.RULE_THIN; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.RULE_TOTALS; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.SUMMARY_GAP_W; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.SURFACE_SOFT; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.TOP_BEARING_BOLD; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.TOP_BEARING_REGULAR; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.TOTALS_DUE_CAP_Y; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.TOTALS_LABEL_SIZE; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.TOTALS_ROW1_CAP_Y; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.TOTALS_ROW_PITCH_Y; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.TOTALS_RULE_W; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.TOTALS_RULE_Y; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.TOTALS_VALUE_PAD_R; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.TOTALS_VALUE_SIZE; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.TOTAL_DUE_LABEL_SIZE; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.TOTAL_DUE_VALUE_SIZE; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.bold; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.capGap; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.capTop; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.py; +import static com.demcha.compose.document.templates.invoice.presets.MerchantStyles.style; +import static com.demcha.compose.document.templates.invoice.presets.MerchantMasthead.layeredRow; + +/** + * The band under the table: the bank panel on the left, and on the right the + * totals over the card carrying the due date. + */ +final class MerchantSettlement { + + private MerchantSettlement() { + } + + /** + * The settlement row. + * + * @param page the page flow + * @param payment the bank details and the due notice + * @param totals the summed rows and the grand total + * @param currencyCode the code the grand total carries + */ + static void render(PageFlowBuilder page, InvoicePaymentBlock payment, + InvoiceTotalsBlock totals, String currencyCode) { + page.addRow("Settlement", row -> { + row.spacing(0) + .columns(DocumentRowColumn.fixed(PANEL_W), + DocumentRowColumn.fixed(SUMMARY_GAP_W), + DocumentRowColumn.weight(1)) + .margin(new DocumentInsets(py(PANEL_TOP_Y - 974), 0, 0, 0)); + row.addSection("PaymentPanel", column -> renderPanel(column, payment)); + row.addSection("SettlementGap", column -> column.spacing(0)); + row.addSection("SettlementRight", column -> { + column.spacing(0); + renderTotals(column, totals, currencyCode); + renderDueCard(column, payment); + }); + }); + } + + private static void renderPanel(SectionBuilder column, InvoicePaymentBlock payment) { + column.spacing(0) + .keepTogether() + .fillColor(SURFACE_SOFT) + .stroke(DocumentStroke.of(BORDER_SOFT, RULE_THIN)) + .cornerRadius(PANEL_RADIUS); + + layeredRow(column, "PaymentHeading", row -> { + row.spacing(0) + .verticalAlign(RowVerticalAlign.CENTER) + .columns(DocumentRowColumn.fixed(PANEL_HEAD_GUTTER), + DocumentRowColumn.weight(1)) + .padding(new DocumentInsets( + py(PANEL_ICON_Y - PANEL_TOP_Y), 0, 0, PANEL_ICON_PAD_L)); + row.add(MerchantIcons.icon(MerchantIcons.BANK).node(PANEL_ICON)); + row.addParagraph(p -> p + .name("PaymentHeadingText") + .text(payment.heading()) + .textStyle(bold(PANEL_HEAD_SIZE, ACCENT))); + }); + + column.addSection("PaymentRows", rows -> { + rows.spacing(capGap(PANEL_ROW_PITCH_Y, PANEL_ROW_SIZE, false, PANEL_ROW_SIZE, false)) + .padding(new DocumentInsets(0, PANEL_PAD_R, 0, PANEL_PAD_L)) + // Measured from the mark, because the heading layer's height + // is the mark's and not the label's line box. + .margin(new DocumentInsets( + py(PANEL_ROW1_CAP_Y - PANEL_ICON_Y) + - TOP_BEARING_REGULAR * PANEL_ROW_SIZE - PANEL_ICON, + 0, 0, 0)); + List fields = payment.fields(); + for (int i = 0; i < fields.size(); i++) { + InvoicePaymentBlock.Field field = fields.get(i); + int index = i; + layeredRow(rows, "PaymentRow_" + index, row -> { + row.spacing(0) + .columns(DocumentRowColumn.fixed(PANEL_LABEL_W), + DocumentRowColumn.weight(1)); + row.addParagraph(p -> p.text(field.label()) + .textStyle(style(PANEL_ROW_SIZE, INK))); + row.addParagraph(p -> { + p.text(field.value()).textStyle(style(PANEL_ROW_SIZE, INK)); + // A bank detail is a reference, not a destination — except + // an address, the one field a reader would act on. + if (field.value().contains("@")) { + p.link(ContactUri.mailLink(field.value())); + } + }); + }); + } + }); + + if (payment.instruction().isBlank()) { + return; + } + column.addLine(line -> line + .name("PaymentPanelRule") + .horizontal(PANEL_RULE_W) + .thickness(RULE_THIN) + .color(RULE_SOFT) + .margin(new DocumentInsets( + py(PANEL_RULE_Y - PANEL_LAST_ROW_CAP_Y) + + TOP_BEARING_REGULAR * PANEL_ROW_SIZE + - LINE_BOX * PANEL_ROW_SIZE, + 0, 0, PANEL_RULE_PAD_L))); + layeredRow(column, "PaymentNote", row -> { + row.spacing(0) + .columns(DocumentRowColumn.fixed(PANEL_NOTE_GUTTER), + DocumentRowColumn.weight(1)) + .padding(new DocumentInsets( + py(PANEL_NOTE_CAP_Y - PANEL_RULE_Y) - RULE_BOX + - TOP_BEARING_REGULAR * PANEL_NOTE_SIZE, + 0, + py(PANEL_BOTTOM_Y - PANEL_NOTE_CAP_Y - PANEL_NOTE_PITCH_Y) + + TOP_BEARING_REGULAR * PANEL_NOTE_SIZE + - LINE_BOX * PANEL_NOTE_SIZE, + PANEL_PAD_L)); + row.add(MerchantIcons.icon(MerchantIcons.INFO).node(PANEL_NOTE_ICON)); + row.addSection("PaymentNoteText", text -> { + text.spacing(capGap(PANEL_NOTE_PITCH_Y, PANEL_NOTE_SIZE, false, + PANEL_NOTE_SIZE, false)); + String[] lines = payment.instruction().split("\n"); + for (int i = 0; i < lines.length; i++) { + String prose = lines[i]; + int index = i; + text.addParagraph(p -> p + .name("PaymentNoteLine_" + index) + .text(prose) + .textStyle(style(PANEL_NOTE_SIZE, INK))); + } + }); + }); + } + + private static void renderTotals(SectionBuilder column, InvoiceTotalsBlock totals, + String currencyCode) { + column.addSection("Totals", block -> { + block.spacing(0) + .keepTogether() + .margin(capTop(TOTALS_ROW1_CAP_Y - PANEL_TOP_Y, TOTALS_LABEL_SIZE, false)); + List rows = totals.rows(); + for (int i = 0; i < rows.size(); i++) { + InvoiceTotalsBlock.Row entry = rows.get(i); + boolean first = i == 0; + int index = i; + layeredRow(block, "TotalsRow_" + index, row -> { + row.spacing(0) + .padding(new DocumentInsets(0, TOTALS_VALUE_PAD_R, 0, 0)) + // The row's box is the value's, which is the taller of + // the two, so the pitch is solved against that and not + // against the label the pitch was measured on. + .margin(new DocumentInsets(first ? 0 + : py(TOTALS_ROW_PITCH_Y) + + TOP_BEARING_REGULAR * TOTALS_LABEL_SIZE + - LINE_BOX * TOTALS_VALUE_SIZE + - TOP_BEARING_REGULAR * TOTALS_LABEL_SIZE, + 0, 0, 0)) + .evenWeights(); + row.addParagraph(p -> p.text(entry.label()) + .textStyle(style(TOTALS_LABEL_SIZE, INK))); + row.addParagraph(p -> p + .text(MerchantText.money(entry.amount())) + .textStyle(style(TOTALS_VALUE_SIZE, INK)) + .align(TextAlign.RIGHT)); + }); + } + block.addLine(line -> line + .name("TotalsRule") + .horizontal(TOTALS_RULE_W) + .thickness(RULE_MEDIUM) + .color(RULE_TOTALS) + // Same correction: the box above this rule is the value's. + .margin(new DocumentInsets( + py(TOTALS_RULE_Y - TOTALS_ROW1_CAP_Y - TOTALS_ROW_PITCH_Y) + + TOP_BEARING_REGULAR * TOTALS_LABEL_SIZE + - LINE_BOX * TOTALS_VALUE_SIZE, + 0, 0, 0))); + layeredRow(block, "TotalDue", row -> { + row.spacing(0) + .verticalAlign(RowVerticalAlign.CENTER) + .padding(new DocumentInsets( + py(TOTALS_DUE_CAP_Y - TOTALS_RULE_Y) - RULE_BOX + - TOP_BEARING_BOLD * TOTAL_DUE_VALUE_SIZE, + TOTALS_VALUE_PAD_R, 0, 0)) + .evenWeights(); + row.addParagraph(p -> p + .name("TotalDueLabel") + .text(totals.totalLabel()) + .textStyle(bold(TOTAL_DUE_LABEL_SIZE, INK))); + row.addParagraph(p -> p + .name("TotalDueValue") + .text(MerchantText.coded(currencyCode, totals.totalAmount())) + .textStyle(bold(TOTAL_DUE_VALUE_SIZE, ACCENT)) + .align(TextAlign.RIGHT)); + }); + }); + } + + private static void renderDueCard(SectionBuilder column, InvoicePaymentBlock payment) { + if (payment.dueNotice().isBlank() && payment.dueNoticeEmphasis().isBlank()) { + return; + } + column.addSection("DueDateCard", panel -> { + panel.spacing(0) + .keepTogether() + .fillColor(SURFACE_SOFT) + .cornerRadius(PANEL_RADIUS) + // Measured from the total's cap, which is what sits above it + // in this column. + .margin(new DocumentInsets( + py(CARD_TOP_Y - TOTALS_DUE_CAP_Y) + + TOP_BEARING_BOLD * TOTAL_DUE_VALUE_SIZE + - LINE_BOX * TOTAL_DUE_VALUE_SIZE, + 0, 0, 0)); + layeredRow(panel, "DueDateRow", row -> { + row.spacing(0) + .verticalAlign(RowVerticalAlign.CENTER) + .columns(DocumentRowColumn.fixed(CARD_GUTTER), + DocumentRowColumn.weight(1)) + .padding(new DocumentInsets( + py(CARD_ICON_Y - CARD_TOP_Y), 0, + py(CARD_BOTTOM_Y - CARD_ICON_Y) - CARD_ICON, CARD_PAD_L)); + row.add(MerchantIcons.icon(MerchantIcons.CALENDAR).node(CARD_ICON)); + row.addSection("DueDateText", text -> { + text.spacing(capGap(CARD_VALUE_CAP_Y - CARD_LABEL_CAP_Y, + DUE_LABEL_SIZE, true, DUE_VALUE_SIZE, true)); + text.addParagraph(p -> p + .name("DueDateLabel") + .text(payment.dueNotice()) + .textStyle(bold(DUE_LABEL_SIZE, INK))); + text.addParagraph(p -> p + .name("DueDateValue") + .text(payment.dueNoticeEmphasis()) + .textStyle(bold(DUE_VALUE_SIZE, INK))); + }); + }); + }); + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/MerchantStyles.java b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/MerchantStyles.java new file mode 100644 index 000000000..494f53885 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/MerchantStyles.java @@ -0,0 +1,358 @@ +package com.demcha.compose.document.templates.invoice.presets; + +import com.demcha.compose.document.api.DocumentPageSize; +import com.demcha.compose.document.style.DocumentColor; +import com.demcha.compose.document.style.DocumentInsets; +import com.demcha.compose.document.style.DocumentTextDecoration; +import com.demcha.compose.document.style.DocumentTextStyle; +import com.demcha.compose.font.FontName; + +/** + * Every measurement, colour and type size of the Merchant invoice. + * + *

    Two scales, and a third for squares

    + * + *

    The design's aspect is not A4's, so one conversion constant cannot serve + * both axes: {@link #px} is horizontal and carries widths and type sizes, + * {@link #py} is vertical and carries heights and pitches, and {@link #sq} is + * their mean for the marks and discs, which cannot match both.

    + * + *

    Sizes are solved from ink, positions from cap tops

    + * + *

    The design's own face is not bundled, so each size is solved from the + * measured ink width of the string it sets and is named here by that string. + * Vertical placement is by cap top rather than by box edge, because ink is what + * a design can be measured on.

    + */ +final class MerchantStyles { + + private MerchantStyles() { + } + + // ------------------------------------------------------------------ + // The page and its scales + // ------------------------------------------------------------------ + + static final DocumentPageSize PAGE = DocumentPageSize.A4; + + static final double PAGE_W = PAGE.width(); + private static final double PAGE_H = PAGE.height(); + + private static final double REF_W = 1054.0; + private static final double REF_H = 1492.0; + + private static final double PX = PAGE_W / REF_W; + private static final double PY = PAGE_H / REF_H; + private static final double SQ = (PX + PY) / 2.0; + + static double px(double designPixels) { + return designPixels * PX; + } + + static double py(double designPixels) { + return designPixels * PY; + } + + static double sq(double designPixels) { + return designPixels * SQ; + } + + static final double MARGIN_L = px(35); + static final double MARGIN_R = px(38); + static final double MARGIN_B_FIRST = py(6); + + /** + * The enumeration's band, and the reason a continuation page reserves more + * than page one: page one ends in a footer band the design already leaves + * room under, where a continuation page ends in table rows, and a table row + * has no hole in its middle for a number to sit in. + */ + static final double PAGE_NUMBER_BAND = 10.0; + static final double MARGIN_B_LATER = PAGE_NUMBER_BAND + py(6); + + static final double CONTENT_W = PAGE_W - MARGIN_L - MARGIN_R; + + // ------------------------------------------------------------------ + // Type + // ------------------------------------------------------------------ + + /** + * The design's own face is not bundled. Gothic A1 is the family every size + * below was solved against; the templates artifact carries no fonts — + * register it on the session, or the engine substitutes and every size is + * solved for type that is not there. + */ + static final FontName FACE = FontName.GOTHIC_A1; + + private static final double FACE_ASCENT = 0.7979; + private static final double FACE_DESCENT = 0.2021; + private static final double CAP_REGULAR = 0.7314; + private static final double CAP_BOLD = 0.7725; + + static final double LINE_BOX = FACE_ASCENT + FACE_DESCENT; + static final double TOP_BEARING_REGULAR = FACE_ASCENT - CAP_REGULAR; + static final double TOP_BEARING_BOLD = FACE_ASCENT - CAP_BOLD; + + /** Gothic A1's space advance, for the two places the design sets a measured gap. */ + private static final double FACE_SPACE_ADVANCE = 0.2441; + + /** + * A size solved from a string's measured ink width. + * + *

    Width, not cap band, and deliberately: an ink width measured over 100 to + * 350 design pixels is good to about 1%, where a cap band measured over 12 is + * good to about 7%. Every size below names the string it was solved from.

    + */ + static double sizeForInk(double designPixelsPerEm) { + return px(designPixelsPerEm); + } + + static double topBearing(double size, boolean bold) { + return (bold ? TOP_BEARING_BOLD : TOP_BEARING_REGULAR) * size; + } + + /** The margin that puts a cap top at a measured design y. */ + static DocumentInsets capTop(double capPx, double size, boolean bold) { + return new DocumentInsets(py(capPx) - topBearing(size, bold), 0, 0, 0); + } + + /** The gap that puts the next cap top {@code deltaPx} below the previous one. */ + static double capGap(double deltaPx, double sizeAbove, boolean boldAbove, + double sizeBelow, boolean boldBelow) { + return py(deltaPx) - LINE_BOX * sizeAbove + topBearing(sizeAbove, boldAbove) + - topBearing(sizeBelow, boldBelow); + } + + /** + * The gap that puts an image's top a measured distance under the cap + * top of the text above it. + * + *

    {@link #capGap} removes a top bearing for the box below, because a text + * box starts above its own first cap. An image does not — its box top is its + * ink top — so that term is not there to remove.

    + */ + static double inkGapToImage(double deltaPx, double sizeAbove, boolean boldAbove) { + return Math.max(0, + py(deltaPx) - LINE_BOX * sizeAbove + topBearing(sizeAbove, boldAbove)); + } + + /** A run of spaces advancing a measured design width at a given size. */ + static String spaces(double widthPx, double size) { + int count = (int) Math.round(px(widthPx) / (FACE_SPACE_ADVANCE * size)); + return " ".repeat(Math.max(1, count)); + } + + // Every size is named by the string it was solved from. + static final double TITLE_SIZE = sizeForInk(64.92); + static final double SUPPLIER_NAME_SIZE = sizeForInk(18.69); + static final double BODY_SIZE = sizeForInk(15.69); + static final double PARTY_LABEL_SIZE = sizeForInk(15.82); + static final double CONTACT_SIZE = sizeForInk(14.74); + static final double PARTY_NAME_SIZE = sizeForInk(17.39); + static final double TABLE_HEAD_SIZE = sizeForInk(14.98); + static final double ITEM_TITLE_SIZE = sizeForInk(16.11); + static final double CELL_SIZE = sizeForInk(14.72); + static final double TOTALS_LABEL_SIZE = sizeForInk(15.18); + static final double TOTALS_VALUE_SIZE = sizeForInk(16.34); + static final double TOTAL_DUE_LABEL_SIZE = sizeForInk(19.16); + static final double TOTAL_DUE_VALUE_SIZE = sizeForInk(25.01); + static final double PANEL_HEAD_SIZE = sizeForInk(15.27); + static final double PANEL_ROW_SIZE = sizeForInk(13.51); + static final double PANEL_NOTE_SIZE = sizeForInk(14.00); + static final double DUE_LABEL_SIZE = sizeForInk(14.83); + static final double DUE_VALUE_SIZE = sizeForInk(20.31); + static final double CLOSING_BOLD_SIZE = sizeForInk(14.49); + static final double CLOSING_REG_SIZE = sizeForInk(14.02); + static final double FOOTER_NAME_SIZE = sizeForInk(13.57); + static final double FOOTER_ADDR_SIZE = sizeForInk(13.72); + static final double FOOTER_SITE_SIZE = sizeForInk(12.75); + static final double PAGE_NUMBER_SIZE = sizeForInk(10.0); + + static DocumentTextStyle style(double size, DocumentColor color) { + return DocumentTextStyle.builder() + .fontName(FACE).size(size).color(color) + .decoration(DocumentTextDecoration.DEFAULT).build(); + } + + static DocumentTextStyle bold(double size, DocumentColor color) { + return DocumentTextStyle.builder() + .fontName(FACE).size(size).color(color) + .decoration(DocumentTextDecoration.BOLD).build(); + } + + // ------------------------------------------------------------------ + // Colour + // ------------------------------------------------------------------ + + static final DocumentColor INK = DocumentColor.rgb(12, 27, 51); + static final DocumentColor ACCENT = DocumentColor.rgb(44, 125, 47); + static final DocumentColor SURFACE_SOFT = DocumentColor.rgb(247, 249, 246); + static final DocumentColor BORDER_SOFT = DocumentColor.rgb(228, 231, 230); + static final DocumentColor RULE_STRONG = DocumentColor.rgb(198, 200, 209); + static final DocumentColor RULE_SOFT = DocumentColor.rgb(221, 223, 228); + static final DocumentColor RULE_TOTALS = DocumentColor.rgb(145, 178, 130); + static final DocumentColor ON_FILL = DocumentColor.WHITE; + static final DocumentColor WHITE = DocumentColor.WHITE; + + // ------------------------------------------------------------------ + // The masthead's top, where two blocks disagree about where the page starts + // ------------------------------------------------------------------ + + /** The lockup's ink and the title's box start at different heights; the flow opens at the earlier. */ + static final double LOGO_INK_TOP = py(41); + static final double TITLE_BOX_TOP = py(49) - TOP_BEARING_BOLD * TITLE_SIZE; + static final double MARGIN_TOP = Math.min(LOGO_INK_TOP, TITLE_BOX_TOP); + static final double LOGO_MARGIN_T = LOGO_INK_TOP - MARGIN_TOP; + static final double TITLE_MARGIN_T = TITLE_BOX_TOP - MARGIN_TOP; + + static final DocumentInsets PAGE_MARGIN_FIRST = + new DocumentInsets(MARGIN_TOP, MARGIN_R, MARGIN_B_FIRST, MARGIN_L); + static final DocumentInsets PAGE_MARGIN_LATER = + new DocumentInsets(MARGIN_TOP, MARGIN_R, MARGIN_B_LATER, MARGIN_L); + + // ------------------------------------------------------------------ + // Rules, discs and blocks + // ------------------------------------------------------------------ + + /** A rule asked for less than a point is laid out as one. */ + static final double RULE_BOX = 1.0; + + static final double RULE_THIN = py(1.6); + static final double RULE_MEDIUM = py(2.2); + static final double RULE_ROW = py(0.8); + static final double DIVIDER_W = px(2); + static final double PANEL_RADIUS = px(10); + static final double TILE_RADIUS = sq(9); + static final double PARTY_DISC_D = sq(44); + static final double PARTY_DISC_GLYPH = sq(23); + + static final double LOGO_W = px(222); + static final double LOGO_H = px(64); + static final double TITLE_RULE_W = px(56); + static final double LEFT_COLUMN_W = px(526 - 35); + static final double CONTACT_ICON = sq(18); + static final double CONTACT_GAP_PX = 19.0; + static final double SUPPLIER_TAX_GAP_PX = 17.0; + static final double PARTY_TAX_GAP_PX = 26.0; + static final double META_PAD_L = px(593 - 528); + static final double META_LABEL_W = px(773 - 593); + static final double PARTY_GUTTER = px(97 - 35); + static final double BILL_TO_PAD_L = px(1); + static final double SHIP_TO_PAD_L = px(560 - 528); + static final double PARTY_BLOCK_H = py(640 - 451); + + // ------------------------------------------------------------------ + // The line-item table + // ------------------------------------------------------------------ + + static final double TABLE_ROW_PITCH_PX = (974 - 707) / 4.0; + static final double TABLE_HEADER_H = py(40); + static final double TABLE_ROW_H = py(TABLE_ROW_PITCH_PX); + static final double TABLE_PAD_HEAD = px(79); + static final double TABLE_PAD_ICON = px(19); + static final double TABLE_PAD_EDGE = px(8); + static final double TABLE_PAD_CENTRED = px(4); + static final double TABLE_ICON = sq(31); + static final double TABLE_ICON_GUTTER = px(110 - 54); + + /** The five columns, as shares of the content width, solved from the design. */ + static final double[] COLUMN_SHARES = {0.308359, 0.200306, 0.135576, 0.183486, 0.172273}; + + /** Only the description is left-aligned; the four columns after it are centred. */ + static final boolean[] COLUMN_CENTRED = {false, true, true, true, true}; + + // ------------------------------------------------------------------ + // Settlement + // ------------------------------------------------------------------ + + static final double PANEL_TOP_Y = 996; + static final double PANEL_ICON_Y = 1009; + static final double PANEL_ROW1_CAP_Y = 1048; + static final double PANEL_ROW_PITCH_Y = 22.5; + static final double PANEL_LAST_ROW_CAP_Y = 1228; + static final double PANEL_RULE_Y = 1254; + static final double PANEL_NOTE_CAP_Y = 1268; + static final double PANEL_NOTE_PITCH_Y = 26; + static final double PANEL_BOTTOM_Y = 1323; + + static final double TOTALS_ROW1_CAP_Y = 1018; + static final double TOTALS_ROW_PITCH_Y = 37; + static final double TOTALS_RULE_Y = 1094; + static final double TOTALS_DUE_CAP_Y = 1123; + + static final double CARD_TOP_Y = 1183; + static final double CARD_BOTTOM_Y = 1287; + static final double CARD_ICON_Y = 1212; + static final double CARD_LABEL_CAP_Y = 1214; + static final double CARD_VALUE_CAP_Y = 1240; + + static final double PANEL_W = px(435 - 35); + static final double SUMMARY_GAP_W = px(555 - 435); + static final double PANEL_PAD_L = px(54 - 35); + static final double PANEL_PAD_R = px(20); + static final double PANEL_ICON = sq(26); + static final double PANEL_HEAD_GUTTER = px(101 - 55); + static final double PANEL_ICON_PAD_L = px(55 - 35); + static final double PANEL_LABEL_W = px(221 - 54); + static final double PANEL_NOTE_ICON = sq(24); + static final double PANEL_NOTE_GUTTER = px(88 - 54); + static final double PANEL_RULE_W = px(418 - 46); + static final double PANEL_RULE_PAD_L = px(46 - 35); + + static final double TOTALS_VALUE_PAD_R = px(1016 - 981); + static final double TOTALS_RULE_W = px(986 - 555); + static final double CARD_ICON = sq(45); + static final double CARD_PAD_L = px(580 - 555); + static final double CARD_GUTTER = px(644 - 580); + + // ------------------------------------------------------------------ + // Closing note and footer + // ------------------------------------------------------------------ + + static final double NOTE_TOP_Y = 1349; + static final double NOTE_LINE1_CAP_Y = 1357; + static final double NOTE_LINE2_CAP_Y = 1379; + static final double NOTE_ICON = sq(30); + static final double NOTE_GUTTER = px(80 - 35); + + static final double FOOTER_RULE_Y = 1412; + static final double FOOTER_NAME_CAP_Y = 1426; + static final double FOOTER_ADDR_CAP_Y = 1447; + static final double FOOTER_SITE_CAP_Y = 1468; + static final double FOOTER_TILE_W = px(105 - 34); + static final double FOOTER_MARK_W = sq(47); + static final double FOOTER_TEXT_PAD_L = px(131 - 105); + /** Where the identity band's own top edge lands: immediately under its rule. */ + static final double FOOTER_ROW_TOP = py(FOOTER_RULE_Y) + RULE_BOX; + + /** A point of slack, so a block solved to the paper's edge still fits inside it. */ + private static final double FIT_TOLERANCE = 1.0; + + static final double FOOTER_TILE_H = + PAGE_H - MARGIN_B_FIRST - FOOTER_ROW_TOP - FIT_TOLERANCE; + + // ------------------------------------------------------------------ + // Block heights, for the rules that are placed under whole blocks + // ------------------------------------------------------------------ + + /** The masthead is as tall as the taller of the lockup and the title. */ + static final double MASTHEAD_H = Math.max( + LOGO_MARGIN_T + LOGO_H, TITLE_MARGIN_T + LINE_BOX * TITLE_SIZE); + + /** + * The identity block's laid-out height, measured from its own top. + * + *

    The supplier column ends with its tax line and the divider ends lower, + * and the two are within half a point of each other — so both are computed + * and the taller wins, rather than one being assumed to outlast the other.

    + */ + static final double IDENTITY_H = Math.max( + py(391 - 144), + py(377 - 144) - TOP_BEARING_REGULAR * BODY_SIZE + LINE_BOX * BODY_SIZE); + + /** The closing note's own height, from its top to the foot of its second line. */ + static final double CLOSING_NOTE_H = + py(NOTE_LINE2_CAP_Y - NOTE_TOP_Y) + - TOP_BEARING_REGULAR * CLOSING_REG_SIZE + + LINE_BOX * CLOSING_REG_SIZE; +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/MerchantText.java b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/MerchantText.java new file mode 100644 index 000000000..a6c89b713 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/MerchantText.java @@ -0,0 +1,59 @@ +package com.demcha.compose.document.templates.invoice.presets; + +import java.math.BigDecimal; +import java.text.NumberFormat; +import java.util.Locale; + +/** + * How the Merchant invoice writes its figures. + * + *

    This design states its currency once in each money column's caption and + * writes the figures under it bare, carrying the code only on the grand total, + * which stands under no caption. The locale is stated rather than inherited, so + * the same data renders the same sheet on any machine.

    + */ +final class MerchantText { + + private MerchantText() { + } + + /** An amount, grouped and to two places, with no currency against it. */ + static String money(BigDecimal value) { + NumberFormat format = NumberFormat.getNumberInstance(Locale.ENGLISH); + format.setGroupingUsed(true); + format.setMinimumFractionDigits(2); + format.setMaximumFractionDigits(2); + return format.format(value); + } + + /** An amount behind its currency code, for the one figure that stands alone. */ + static String coded(String currencyCode, BigDecimal value) { + return currencyCode == null || currencyCode.isBlank() + ? money(value) + : currencyCode.trim() + " " + money(value); + } + + /** A caption with its currency stated once, the way this design labels a money column. */ + static String captionWithCurrency(String caption, String currencyCode) { + return currencyCode == null || currencyCode.isBlank() || caption.isBlank() + ? caption + : caption + " (" + currencyCode.trim() + ")"; + } + + /** + * A count, bare, with no fraction when it has none. + * + *

    A line that counts nothing — a fee charged as it falls rather than per + * unit — prints the design's own dash instead of a zero, because a zero in a + * quantity column reads as none delivered rather than as not counted.

    + */ + static String quantity(BigDecimal value) { + if (value.signum() == 0) { + return "—"; + } + BigDecimal stripped = value.stripTrailingZeros(); + return stripped.scale() <= 0 + ? stripped.toBigInteger().toString() + : stripped.toPlainString(); + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/MeteredClosing.java b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/MeteredClosing.java new file mode 100644 index 000000000..9a01eddb1 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/MeteredClosing.java @@ -0,0 +1,135 @@ +package com.demcha.compose.document.templates.invoice.presets; + +import com.demcha.compose.document.dsl.ParagraphBuilder; +import com.demcha.compose.document.dsl.SectionBuilder; +import com.demcha.compose.document.node.DocumentLinkOptions; +import com.demcha.compose.document.style.DocumentInsets; +import com.demcha.compose.document.style.DocumentRowColumn; +import com.demcha.compose.document.templates.core.identity.ContactUri; +import com.demcha.compose.document.templates.data.invoice.InvoiceContactBlock; +import com.demcha.compose.document.templates.data.invoice.InvoiceNotesBlock; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; + +import static com.demcha.compose.document.templates.invoice.presets.MeteredStyles.CONTENT_W; +import static com.demcha.compose.document.templates.invoice.presets.MeteredStyles.HAIRLINE; +import static com.demcha.compose.document.templates.invoice.presets.MeteredStyles.NOTE_BODY; +import static com.demcha.compose.document.templates.invoice.presets.MeteredStyles.NOTE_ICON; +import static com.demcha.compose.document.templates.invoice.presets.MeteredStyles.NOTE_ICON_GAP; +import static com.demcha.compose.document.templates.invoice.presets.MeteredStyles.NOTE_INSET; +import static com.demcha.compose.document.templates.invoice.presets.MeteredStyles.NOTE_LEAD; +import static com.demcha.compose.document.templates.invoice.presets.MeteredStyles.NOTE_LINE_GAP; +import static com.demcha.compose.document.templates.invoice.presets.MeteredStyles.NOTE_RULE_GAP; +import static com.demcha.compose.document.templates.invoice.presets.MeteredStyles.RULE; +import static com.demcha.compose.document.templates.invoice.presets.MeteredStyles.SEAM_CLOSING_TO_NOTE; + +/** + * The closing note: a rule, a mark, a lead line and the prose under it. + */ +final class MeteredClosing { + + private MeteredClosing() { + } + + /** + * The note. + * + * @param body the page's body section + * @param notes the lead and its prose + * @param supplier the addresses the prose may name + */ + static void render(SectionBuilder body, InvoiceNotesBlock notes, + InvoiceContactBlock supplier) { + if (notes.heading().isBlank() && notes.paragraphs().isEmpty()) { + return; + } + body.addSection("ClosingNote", block -> { + block.margin(SEAM_CLOSING_TO_NOTE); + block.keepTogether(); + block.spacing(NOTE_RULE_GAP); + block.addDivider(d -> d + .name("ClosingNoteRule") + .width(CONTENT_W) + .thickness(HAIRLINE) + .color(RULE)); + block.addRow("ClosingNoteBody", row -> row + .padding(new DocumentInsets(0, 0, 0, NOTE_INSET)) + .columns(DocumentRowColumn.fixed(NOTE_ICON + NOTE_ICON_GAP), + DocumentRowColumn.auto()) + .addSection("ClosingNoteIcon", cell -> cell + .addSvgIcon(MeteredIcons.icon(MeteredIcons.INFO), NOTE_ICON)) + .addSection("ClosingNoteText", text -> { + text.spacing(NOTE_LINE_GAP); + if (!notes.heading().isBlank()) { + text.addParagraph(p -> p + .name("ClosingNoteLead") + .text(notes.heading()) + .textStyle(NOTE_LEAD)); + } + List paragraphs = notes.paragraphs(); + for (int i = 0; i < paragraphs.size(); i++) { + String prose = paragraphs.get(i); + int index = i; + text.addParagraph(p -> { + p.name("ClosingNoteProse_" + index); + writeReachable(p, prose, notes, supplier); + }); + } + })); + }); + } + + /** + * The prose, with every address it names made reachable. + * + *

    The note tells a reader where to go, and the places it can send them — + * the support address, the site — are printed inside the sentence. Written + * as one string the whole sentence would have to carry the target or none of + * it would, so each address it actually contains becomes its own run and the + * text between them stays plain. The glyphs and their order are what the + * sentence gave.

    + */ + private static void writeReachable(ParagraphBuilder paragraph, String prose, + InvoiceNotesBlock notes, InvoiceContactBlock supplier) { + List found = new ArrayList<>(); + addIfPresent(found, prose, notes.contactEmail(), ContactUri.mailLink(notes.contactEmail())); + addIfPresent(found, prose, supplier.email(), ContactUri.mailLink(supplier.email())); + addIfPresent(found, prose, notes.contactPhone(), ContactUri.telLink(notes.contactPhone())); + addIfPresent(found, prose, supplier.website(), ContactUri.webLink(supplier.website())); + found.sort(Comparator.comparingInt(Reachable::at)); + + int cursor = 0; + for (Reachable reachable : found) { + if (reachable.at() < cursor) { + // An address that overlaps one already written — the site inside + // an address, say. The first one wins; writing both would + // duplicate the glyphs. + continue; + } + if (reachable.at() > cursor) { + paragraph.inlineText(prose.substring(cursor, reachable.at()), NOTE_BODY); + } + paragraph.inlineText(reachable.text(), NOTE_BODY, reachable.link()); + cursor = reachable.at() + reachable.text().length(); + } + if (cursor < prose.length()) { + paragraph.inlineText(prose.substring(cursor), NOTE_BODY); + } + } + + private static void addIfPresent(List found, String prose, String text, + DocumentLinkOptions link) { + if (text == null || text.isBlank() || link == null) { + return; + } + int at = prose.indexOf(text); + if (at >= 0) { + found.add(new Reachable(at, text, link)); + } + } + + private record Reachable(int at, String text, DocumentLinkOptions link) { + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/MeteredIcons.java b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/MeteredIcons.java new file mode 100644 index 000000000..503687746 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/MeteredIcons.java @@ -0,0 +1,75 @@ +package com.demcha.compose.document.templates.invoice.presets; + +import com.demcha.compose.document.svg.SvgIcon; + +import java.io.IOException; +import java.io.InputStream; +import java.io.UncheckedIOException; +import java.nio.charset.StandardCharsets; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +/** + * The marks the Metered invoice packages. + * + *

    Five of them the preset chooses itself — which glyph opens the bill-to + * block, the ship-to block, the bank panel, the due-by card and the closing + * note is a property of the design, not of the document. The other five are a + * vocabulary a document may name on a service line through + * {@code InvoiceServiceLines.Line.icon()}.

    + */ +final class MeteredIcons { + + static final String BILL_TO = "bill-to"; + static final String SHIP_TO = "ship-to"; + static final String CARD = "card"; + static final String CALENDAR = "calendar"; + static final String INFO = "info"; + + private static final String ICON_ROOT = "/templates/invoice/metered/icons/"; + + /** The marks a document may name on a service line. */ + static final Set LINE_TOKENS = + Set.of("compute", "storage", "database", "transfer", "support"); + + private static final Set TOKENS = Set.of(BILL_TO, SHIP_TO, CARD, CALENDAR, INFO, + "compute", "storage", "database", "transfer", "support"); + + private static final Map CACHE = new ConcurrentHashMap<>(); + + private MeteredIcons() { + } + + /** + * The packaged mark a token names. + * + * @param token one of this preset's tokens + * @return the parsed icon + * @throws IllegalArgumentException when the token names no packaged mark, + * listing the ones a document may choose — an icon token is data, + * so a wrong one is a data error and not a missing resource + */ + static SvgIcon icon(String token) { + if (!TOKENS.contains(token)) { + throw new IllegalArgumentException( + "Unknown metered invoice icon token '" + token + + "'. This preset packages " + LINE_TOKENS.stream().sorted().toList() + + " for a service line's mark."); + } + return CACHE.computeIfAbsent(token, MeteredIcons::read); + } + + private static SvgIcon read(String token) { + String resourcePath = ICON_ROOT + token + ".svg"; + try (InputStream input = MeteredIcons.class.getResourceAsStream(resourcePath)) { + if (input == null) { + throw new IllegalStateException("Missing metered invoice icon: " + resourcePath); + } + return SvgIcon.parse(new String(input.readAllBytes(), StandardCharsets.UTF_8)); + } catch (IOException e) { + throw new UncheckedIOException( + "Failed to read metered invoice icon: " + resourcePath, e); + } + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/MeteredInvoice.java b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/MeteredInvoice.java new file mode 100644 index 000000000..31f5167e8 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/MeteredInvoice.java @@ -0,0 +1,209 @@ +package com.demcha.compose.document.templates.invoice.presets; + +import com.demcha.compose.document.api.DocumentSession; +import com.demcha.compose.document.api.PageBackgroundFill; +import com.demcha.compose.document.output.DocumentHeaderFooter; +import com.demcha.compose.document.output.DocumentHeaderFooterZone; +import com.demcha.compose.document.output.DocumentPageNumberStyle; +import com.demcha.compose.document.output.DocumentPageNumbering; +import com.demcha.compose.document.style.DocumentInsets; +import com.demcha.compose.document.templates.api.DocumentTemplate; +import com.demcha.compose.document.templates.data.invoice.InvoiceContactBlock; +import com.demcha.compose.document.templates.data.invoice.StructuredInvoiceData; + +import java.util.List; +import java.util.Objects; + +import static com.demcha.compose.document.templates.invoice.presets.MeteredStyles.BAND_GAP; +import static com.demcha.compose.document.templates.invoice.presets.MeteredStyles.FACE; +import static com.demcha.compose.document.templates.invoice.presets.MeteredStyles.FOOTER_BAND_RATIO; +import static com.demcha.compose.document.templates.invoice.presets.MeteredStyles.FOOTER_DARK; +import static com.demcha.compose.document.templates.invoice.presets.MeteredStyles.FOOTER_LINE_ONE_H; +import static com.demcha.compose.document.templates.invoice.presets.MeteredStyles.FOOTER_LINE_TWO_H; +import static com.demcha.compose.document.templates.invoice.presets.MeteredStyles.FOOTER_PAGE_H; +import static com.demcha.compose.document.templates.invoice.presets.MeteredStyles.FOOTER_SIZE; +import static com.demcha.compose.document.templates.invoice.presets.MeteredStyles.INVERSE; +import static com.demcha.compose.document.templates.invoice.presets.MeteredStyles.PAGE; +import static com.demcha.compose.document.templates.invoice.presets.MeteredStyles.PAGE_MARGIN; + +/** + * Metered — an invoice for usage that is billed by the meter: an orange-accented + * masthead, the supplier against the invoice's own metadata, two addressed + * parties, a line-item table whose marks sit on bordered tiles, a closing row + * pairing the bank details against the totals and the due date, and a + * full-bleed dark band along the foot of every page. + * + *

    It flows

    + * + *

    The design shows five service lines and real usage brings dozens, so the + * table is the region that grows: its header repeats on every page it reaches, + * the bottom margin reserves the dark band on every page so no row runs into + * the chrome, and every page carries its number.

    + * + *

    One column, not five

    + * + *

    The table has an outer box and a rule between every row and no interior + * verticals, which is exactly what a single-column table draws: a cell strokes + * all four of its own edges and a one-column table has no interior vertical + * edge. The five columns are a row inside each cell. Built the other way round + * — the box from a stroked section wrapped round the table — every continuation + * page would end in an empty bordered strip.

    + * + *

    A rate is not an amount

    + * + *

    Metered rates are fractions of a currency unit: an hour of compute at + * 0.0680 rounds to 0.07 at an amount's two places, which is a different price + * and multiplies out to a different bill. A unit price is written at its own + * scale, between two and four places; every other figure on the sheet is + * written at two. Figures state their locale rather than inheriting it, so the + * same data renders the same sheet on any machine.

    + * + *

    The lockup is the caller's

    + * + *

    The masthead draws the logo from {@code InvoiceBrand} at the design's + * measured width, or the brand's name when there is no logo. The templates + * artifact carries no mark of its own.

    + * + *

    What the document says, and what the preset chooses

    + * + *

    A service line names its own mark through + * {@code InvoiceServiceLines.Line.icon()}, from this preset's own vocabulary, + * and an unknown token is reported as a data error naming the set. A line that + * names no mark keeps its tile, so the text lanes stay on their axis down the + * column. The party marks, the bank mark, the calendar and the note's mark are + * the preset's: which glyph opens which block is a property of the design + * rather than of the document.

    + * + *

    Fonts

    + * + *

    The sheet is set in Lato throughout. The templates artifact carries no + * fonts — register the family on the session, or the engine substitutes and the + * measured geometry no longer matches the type sitting in it.

    + * + *

    Usage:

    + *
    {@code
    + * DocumentTemplate template = MeteredInvoice.create();
    + * template.compose(session, invoice);
    + * }
    + * + * @since 2.4.0 + */ +public final class MeteredInvoice { + + /** Stable identifier of this preset. */ + public static final String ID = "metered-invoice"; + + /** Human-readable name of this preset. */ + public static final String DISPLAY_NAME = "Metered Invoice"; + + private MeteredInvoice() { + } + + /** + * Creates the template. + * + * @return a template composing a {@link StructuredInvoiceData} + */ + public static DocumentTemplate create() { + return new Template(); + } + + private record Template() implements DocumentTemplate { + + @Override + public String id() { + return ID; + } + + @Override + public String displayName() { + return DISPLAY_NAME; + } + + @Override + public void compose(DocumentSession document, StructuredInvoiceData data) { + Objects.requireNonNull(document, "document"); + Objects.requireNonNull(data, "data"); + + // Every length on the sheet is a share of the design's own grid, so + // the preset sets the page and its margins rather than following + // what the caller configured. + document.pageSize(PAGE); + document.margin(PAGE_MARGIN); + renderFooterBand(document); + renderPageFooter(document, data.supplier()); + + String currency = data.currencyCode(); + document.pageFlow(page -> page + .name("MeteredInvoice") + .padding(DocumentInsets.zero()) + .spacing(0) + .addSection("Body", body -> { + body.spacing(BAND_GAP); + MeteredMasthead.renderBrandHeader(body, data.brand(), data.masthead()); + MeteredMasthead.renderIdentityRow(body, data.supplier(), data.masthead()); + MeteredMasthead.renderParties(body, data.billTo(), data.shipTo()); + MeteredLines.render(body, data.serviceLines(), currency); + MeteredSettlement.render(body, data.payment(), data.totals(), currency); + MeteredClosing.render(body, data.notes(), data.supplier()); + })); + } + + /** + * The band is a page background, not a section fill and not a bleed: it + * has to reach all three bottom edges and repeat on every page whatever + * the content height is. + */ + private static void renderFooterBand(DocumentSession document) { + document.pageBackgrounds( + List.of(PageBackgroundFill.bottomBand(FOOTER_BAND_RATIO, FOOTER_DARK))); + } + + /** + * Repeating chrome. A footer entry carries left, centre and right + * strings and nothing else, so each line of the band is its own entry + * positioned by its distance from the paper's bottom edge: the + * supplier's legal line, its address, and the enumeration on the band's + * own centre line. The first is the disclosure the jurisdiction + * requires when the supplier states one, and its name when it does + * not — either way the band identifies who issued the sheet. + * + *

    Each entry names the sheet's face. The zone's own default is the + * standard-14 face, which would leave the band the one place on the + * sheet in a family the design never uses.

    + */ + private static void renderPageFooter(DocumentSession document, + InvoiceContactBlock supplier) { + legalLine(document, FOOTER_LINE_ONE_H, supplier.legalFootnote().isBlank() + ? supplier.legalName() + : supplier.legalFootnote()); + legalLine(document, FOOTER_LINE_TWO_H, String.join(", ", supplier.addressLines())); + document.footer(DocumentHeaderFooter.builder() + .zone(DocumentHeaderFooterZone.FOOTER) + .height((float) FOOTER_PAGE_H) + .fontName(FACE) + .fontSize((float) FOOTER_SIZE) + .textColor(INVERSE) + .rightText("Page {page} of {pages}") + .numbering(DocumentPageNumbering.builder() + .style(DocumentPageNumberStyle.DECIMAL) + .showOnFirstPage(true) + .build()) + .build()); + } + + private static void legalLine(DocumentSession document, double height, String text) { + if (text.isBlank()) { + return; + } + document.footer(DocumentHeaderFooter.builder() + .zone(DocumentHeaderFooterZone.FOOTER) + .height((float) height) + .fontName(FACE) + .fontSize((float) FOOTER_SIZE) + .textColor(INVERSE) + .leftText(text) + .build()); + } + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/MeteredLines.java b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/MeteredLines.java new file mode 100644 index 000000000..f70813763 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/MeteredLines.java @@ -0,0 +1,144 @@ +package com.demcha.compose.document.templates.invoice.presets; + +import com.demcha.compose.document.dsl.RowBuilder; +import com.demcha.compose.document.dsl.SectionBuilder; +import com.demcha.compose.document.dsl.TableBuilder; +import com.demcha.compose.document.node.DocumentNode; +import com.demcha.compose.document.node.RowVerticalAlign; +import com.demcha.compose.document.node.TextAlign; +import com.demcha.compose.document.style.DocumentInsets; +import com.demcha.compose.document.style.DocumentRowColumn; +import com.demcha.compose.document.table.DocumentTableCell; +import com.demcha.compose.document.table.DocumentTableColumn; +import com.demcha.compose.document.templates.data.invoice.InvoiceServiceLines; + +import java.util.List; + +import static com.demcha.compose.document.templates.invoice.presets.MeteredStyles.BODY_CELL; +import static com.demcha.compose.document.templates.invoice.presets.MeteredStyles.CELL; +import static com.demcha.compose.document.templates.invoice.presets.MeteredStyles.COL_AMOUNT; +import static com.demcha.compose.document.templates.invoice.presets.MeteredStyles.COL_QUANTITY; +import static com.demcha.compose.document.templates.invoice.presets.MeteredStyles.COL_SERVICE; +import static com.demcha.compose.document.templates.invoice.presets.MeteredStyles.COL_UNIT_PRICE; +import static com.demcha.compose.document.templates.invoice.presets.MeteredStyles.CONTENT_W; +import static com.demcha.compose.document.templates.invoice.presets.MeteredStyles.DESC_TEXT_COL; +import static com.demcha.compose.document.templates.invoice.presets.MeteredStyles.DESC_TILE_COL; +import static com.demcha.compose.document.templates.invoice.presets.MeteredStyles.HEADER_CELL; +import static com.demcha.compose.document.templates.invoice.presets.MeteredStyles.LINE_SUB; +import static com.demcha.compose.document.templates.invoice.presets.MeteredStyles.LINE_TEXT_GAP; +import static com.demcha.compose.document.templates.invoice.presets.MeteredStyles.LINE_TITLE; +import static com.demcha.compose.document.templates.invoice.presets.MeteredStyles.SEAM_PARTIES_TO_TABLE; +import static com.demcha.compose.document.templates.invoice.presets.MeteredStyles.TABLE_HEAD; +import static com.demcha.compose.document.templates.invoice.presets.MeteredStyles.TABLE_PAD_L; + +/** + * The metered line-item table: a dark caps header that repeats on every page it + * reaches, and one row per service line with its mark on a bordered tile. + */ +final class MeteredLines { + + private MeteredLines() { + } + + /** + * The table. + * + * @param body the page's body section + * @param serviceLines the lines and their column captions + * @param currencyCode the code every figure is written behind + */ + static void render(SectionBuilder body, InvoiceServiceLines serviceLines, String currencyCode) { + body.addTable(table -> { + table.name("LineItems"); + table.margin(SEAM_PARTIES_TO_TABLE); + table.width(CONTENT_W); + table.columns(DocumentTableColumn.fixed(CONTENT_W)); + table.defaultCellStyle(BODY_CELL); + renderHeader(table, serviceLines.columns()); + List lines = serviceLines.lines(); + for (int i = 0; i < lines.size(); i++) { + table.rowCells(DocumentTableCell + .node(lineRow(lines.get(i), i, currencyCode)) + .withStyle(BODY_CELL)); + } + }); + } + + /** + * The dark band: its fill, its inverse caps, and the instruction that brings + * it back at the top of every page the table reaches. + */ + private static void renderHeader(TableBuilder table, InvoiceServiceLines.Columns columns) { + table.headerCells(DocumentTableCell.node(headerRow(columns)).withStyle(HEADER_CELL)); + table.repeatHeader(); + } + + private static DocumentNode headerRow(InvoiceServiceLines.Columns columns) { + RowBuilder row = new RowBuilder(); + row.name("LineItemsHeaderRow") + .verticalAlign(RowVerticalAlign.CENTER) + .columns(DocumentRowColumn.fixed(DESC_TILE_COL), + DocumentRowColumn.fixed(DESC_TEXT_COL), + DocumentRowColumn.fixed(COL_SERVICE), + DocumentRowColumn.fixed(COL_QUANTITY), + DocumentRowColumn.fixed(COL_UNIT_PRICE), + DocumentRowColumn.fixed(COL_AMOUNT)) + .addSpacer(0) + .addParagraph(p -> p.name("HeadDescription") + .text(columns.description()).textStyle(TABLE_HEAD)) + .addParagraph(p -> p.name("HeadService") + .text(columns.servicePeriod()).textStyle(TABLE_HEAD).align(TextAlign.CENTER)) + .addParagraph(p -> p.name("HeadQuantity") + .text(columns.quantity()).textStyle(TABLE_HEAD).align(TextAlign.CENTER)) + .addParagraph(p -> p.name("HeadUnitPrice") + .text(columns.unitPrice()).textStyle(TABLE_HEAD).align(TextAlign.CENTER)) + .addParagraph(p -> p.name("HeadAmount") + .text(columns.amount()).textStyle(TABLE_HEAD).align(TextAlign.CENTER)); + return row.build(); + } + + /** + * One service line. + * + *

    The mark is the document's: a line names one from this preset's own + * vocabulary through {@code icon()}, and a line that names none keeps the + * tile's space so the text lanes stay on their axis down the column.

    + */ + private static DocumentNode lineRow(InvoiceServiceLines.Line line, int index, + String currencyCode) { + String token = line.icon(); + DocumentNode mark = token == null || token.isBlank() + ? MeteredWidgets.tileSpace(index) + : MeteredWidgets.tile(token, index); + RowBuilder row = new RowBuilder(); + row.name("LineItemRow_" + index) + .verticalAlign(RowVerticalAlign.CENTER) + .columns(DocumentRowColumn.fixed(DESC_TILE_COL), + DocumentRowColumn.fixed(DESC_TEXT_COL), + DocumentRowColumn.fixed(COL_SERVICE), + DocumentRowColumn.fixed(COL_QUANTITY), + DocumentRowColumn.fixed(COL_UNIT_PRICE), + DocumentRowColumn.fixed(COL_AMOUNT)) + .addSection("LineTileCell_" + index, cell -> cell + .padding(new DocumentInsets(0, 0, 0, TABLE_PAD_L)) + .add(mark)) + .addSection("LineText_" + index, text -> text + .spacing(LINE_TEXT_GAP) + .addParagraph(p -> p.name("LineTitle_" + index) + .text(line.title()).textStyle(LINE_TITLE)) + .addParagraph(p -> p.name("LineSubtitle_" + index) + .text(line.description()).textStyle(LINE_SUB))) + .addParagraph(p -> p.name("LineService_" + index) + .text(line.servicePeriod()).textStyle(CELL).align(TextAlign.CENTER)) + .addParagraph(p -> p.name("LineQuantity_" + index) + .text(MeteredText.quantityWithUnit(line.quantity(), line.unit())) + .textStyle(CELL).align(TextAlign.CENTER)) + .addParagraph(p -> p.name("LineUnitPrice_" + index) + .text(MeteredText.codedRate(currencyCode, line.unitPrice())) + .textStyle(CELL).align(TextAlign.CENTER)) + .addParagraph(p -> p.name("LineAmount_" + index) + .text(MeteredText.codedMoney(currencyCode, line.amount())) + .textStyle(CELL).align(TextAlign.CENTER)); + return row.build(); + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/MeteredMasthead.java b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/MeteredMasthead.java new file mode 100644 index 000000000..745311583 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/MeteredMasthead.java @@ -0,0 +1,256 @@ +package com.demcha.compose.document.templates.invoice.presets; + +import com.demcha.compose.document.dsl.ImageBuilder; +import com.demcha.compose.document.dsl.LineBuilder; +import com.demcha.compose.document.dsl.SectionBuilder; +import com.demcha.compose.document.node.HorizontalAlign; +import com.demcha.compose.document.node.RowVerticalAlign; +import com.demcha.compose.document.node.TextAlign; +import com.demcha.compose.document.style.DocumentBorders; +import com.demcha.compose.document.style.DocumentInsets; +import com.demcha.compose.document.style.DocumentRowColumn; +import com.demcha.compose.document.style.DocumentStroke; +import com.demcha.compose.document.templates.core.identity.ContactUri; +import com.demcha.compose.document.templates.data.invoice.InvoiceBrand; +import com.demcha.compose.document.templates.data.invoice.InvoiceContactBlock; +import com.demcha.compose.document.templates.data.invoice.InvoiceMasthead; +import com.demcha.compose.document.templates.data.invoice.InvoiceRecipient; + +import java.util.List; + +import static com.demcha.compose.document.templates.invoice.presets.MeteredStyles.ACCENT_BRIGHT; +import static com.demcha.compose.document.templates.invoice.presets.MeteredStyles.BODY; +import static com.demcha.compose.document.templates.invoice.presets.MeteredStyles.CONTENT_W; +import static com.demcha.compose.document.templates.invoice.presets.MeteredStyles.HAIRLINE; +import static com.demcha.compose.document.templates.invoice.presets.MeteredStyles.IDENTITY_SPLIT; +import static com.demcha.compose.document.templates.invoice.presets.MeteredStyles.LOGO_W; +import static com.demcha.compose.document.templates.invoice.presets.MeteredStyles.META_ACCENT; +import static com.demcha.compose.document.templates.invoice.presets.MeteredStyles.META_BLOCK_INSET; +import static com.demcha.compose.document.templates.invoice.presets.MeteredStyles.META_COLUMN_INSET; +import static com.demcha.compose.document.templates.invoice.presets.MeteredStyles.META_LABEL; +import static com.demcha.compose.document.templates.invoice.presets.MeteredStyles.META_LABEL_W; +import static com.demcha.compose.document.templates.invoice.presets.MeteredStyles.META_PAIR_GAP; +import static com.demcha.compose.document.templates.invoice.presets.MeteredStyles.META_VALUE; +import static com.demcha.compose.document.templates.invoice.presets.MeteredStyles.PARTIES_SPLIT; +import static com.demcha.compose.document.templates.invoice.presets.MeteredStyles.PARTY_EXTRA_GAP; +import static com.demcha.compose.document.templates.invoice.presets.MeteredStyles.PARTY_HEAD_GAP; +import static com.demcha.compose.document.templates.invoice.presets.MeteredStyles.PARTY_INSET; +import static com.demcha.compose.document.templates.invoice.presets.MeteredStyles.PARTY_LABEL_W; +import static com.demcha.compose.document.templates.invoice.presets.MeteredStyles.PARTY_LINE_GAP; +import static com.demcha.compose.document.templates.invoice.presets.MeteredStyles.PARTY_NAME; +import static com.demcha.compose.document.templates.invoice.presets.MeteredStyles.RULE; +import static com.demcha.compose.document.templates.invoice.presets.MeteredStyles.SEAM_CLEARANCE; +import static com.demcha.compose.document.templates.invoice.presets.MeteredStyles.SEAM_IDENTITY_TO_RULE; +import static com.demcha.compose.document.templates.invoice.presets.MeteredStyles.SEAM_RULE_TO_PARTIES; +import static com.demcha.compose.document.templates.invoice.presets.MeteredStyles.SECTION_ICON; +import static com.demcha.compose.document.templates.invoice.presets.MeteredStyles.SECTION_ICON_GAP; +import static com.demcha.compose.document.templates.invoice.presets.MeteredStyles.SECTION_LABEL; +import static com.demcha.compose.document.templates.invoice.presets.MeteredStyles.SHIP_COLUMN_INSET; +import static com.demcha.compose.document.templates.invoice.presets.MeteredStyles.SUPPLIER_BLOCK_INSET; +import static com.demcha.compose.document.templates.invoice.presets.MeteredStyles.SUPPLIER_GROUP_GAP; +import static com.demcha.compose.document.templates.invoice.presets.MeteredStyles.SUPPLIER_LABEL_W; +import static com.demcha.compose.document.templates.invoice.presets.MeteredStyles.SUPPLIER_LINE_GAP; +import static com.demcha.compose.document.templates.invoice.presets.MeteredStyles.SUPPLIER_NAME; +import static com.demcha.compose.document.templates.invoice.presets.MeteredStyles.SUPPLIER_NAME_GAP; +import static com.demcha.compose.document.templates.invoice.presets.MeteredStyles.SUPPLIER_PAIR_GAP; +import static com.demcha.compose.document.templates.invoice.presets.MeteredStyles.TITLE; +import static com.demcha.compose.document.templates.invoice.presets.MeteredStyles.TITLE_RULE_GAP; +import static com.demcha.compose.document.templates.invoice.presets.MeteredStyles.TITLE_RULE_T; +import static com.demcha.compose.document.templates.invoice.presets.MeteredStyles.TITLE_RULE_W; +import static com.demcha.compose.document.templates.invoice.presets.MeteredWidgets.hangingBody; +import static com.demcha.compose.document.templates.invoice.presets.MeteredWidgets.iconHeading; +import static com.demcha.compose.document.templates.invoice.presets.MeteredWidgets.labelledRow; +import static com.demcha.compose.document.templates.invoice.presets.MeteredWidgets.layeredRow; + +/** + * The three bands above the line items: the lockup and the title, the supplier + * against the invoice's own metadata, and the two addressed parties. + */ +final class MeteredMasthead { + + private MeteredMasthead() { + } + + /** + * The lockup on the left and the document's title on the right, over the + * accent rule. + * + *

    The mark is the caller's: the lockup draws the brand's logo at the + * design's measured width, or the brand's name when there is none. The + * templates artifact carries no mark of its own.

    + */ + static void renderBrandHeader(SectionBuilder body, InvoiceBrand brand, InvoiceMasthead masthead) { + body.addRow("BrandHeader", row -> row + .verticalAlign(RowVerticalAlign.CENTER) + .weights(1, 1) + .addSection("BrandMark", mark -> { + if (brand.logo() != null) { + mark.add(new ImageBuilder() + .name("BrandLogo") + .source(brand.logo()) + .width(LOGO_W) + .build()); + } else { + mark.addParagraph(p -> p + .name("BrandName") + .text(brand.name()) + .textStyle(TITLE)); + } + }) + .addSection("InvoiceTitle", title -> title + .spacing(TITLE_RULE_GAP) + .addParagraph(p -> p + .name("InvoiceTitleText") + .text(masthead.title()) + .textStyle(TITLE) + .align(TextAlign.RIGHT)) + .addAligned(HorizontalAlign.RIGHT, new LineBuilder() + .name("InvoiceTitleRule") + .horizontal(TITLE_RULE_W) + .thickness(TITLE_RULE_T) + .color(ACCENT_BRIGHT) + .build()))); + } + + /** The supplier's own details against the invoice's metadata, split by a hairline. */ + static void renderIdentityRow(SectionBuilder body, InvoiceContactBlock supplier, + InvoiceMasthead masthead) { + body.addRow("IdentityRow", row -> row + .weights(IDENTITY_SPLIT, 1 - IDENTITY_SPLIT) + .addSection("SupplierColumn", left -> { + left.borders(DocumentBorders.right(DocumentStroke.of(RULE, HAIRLINE))); + left.padding(new DocumentInsets(0, SEAM_CLEARANCE, 0, 0)); + renderSupplierBlock(left, supplier); + }) + .addSection("MetaColumn", right -> { + right.padding(new DocumentInsets(0, 0, 0, META_COLUMN_INSET)); + renderInvoiceMeta(right, masthead); + })); + body.addDivider(d -> d + .name("IdentityRule") + .width(CONTENT_W) + .thickness(HAIRLINE) + .color(RULE) + .margin(SEAM_IDENTITY_TO_RULE)); + } + + private static void renderSupplierBlock(SectionBuilder column, InvoiceContactBlock supplier) { + column.addSection("SupplierBlock", block -> { + block.margin(SUPPLIER_BLOCK_INSET); + block.spacing(SUPPLIER_GROUP_GAP); + block.addSection("SupplierIdentity", identity -> { + identity.spacing(SUPPLIER_LINE_GAP); + identity.addParagraph(p -> p + .name("SupplierName") + .text(supplier.legalName()) + .textStyle(SUPPLIER_NAME) + .margin(new DocumentInsets(0, 0, SUPPLIER_NAME_GAP, 0))); + List lines = supplier.addressLines(); + for (int i = 0; i < lines.size(); i++) { + String line = lines.get(i); + int index = i; + identity.addParagraph(p -> p + .name("SupplierAddress_" + index) + .text(line) + .textStyle(BODY)); + } + }); + block.addSection("SupplierContacts", contacts -> { + contacts.spacing(SUPPLIER_PAIR_GAP); + int index = 0; + if (!supplier.taxRegistrationNumber().isBlank()) { + labelledRow(contacts, "SupplierContact_" + index++, + supplier.taxRegistrationLabel(), supplier.taxRegistrationNumber(), + SUPPLIER_LABEL_W, BODY, BODY, null); + } + if (!supplier.phone().isBlank()) { + labelledRow(contacts, "SupplierContact_" + index++, + "Phone:", supplier.phone(), + SUPPLIER_LABEL_W, BODY, BODY, ContactUri.telLink(supplier.phone())); + } + if (!supplier.email().isBlank()) { + labelledRow(contacts, "SupplierContact_" + index++, + "Email:", supplier.email(), SUPPLIER_LABEL_W, BODY, BODY, + ContactUri.mailLink(supplier.email())); + } + if (!supplier.website().isBlank()) { + labelledRow(contacts, "SupplierContact_" + index, + "Website:", supplier.website(), SUPPLIER_LABEL_W, BODY, BODY, + ContactUri.webLink(supplier.website())); + } + }); + }); + } + + private static void renderInvoiceMeta(SectionBuilder column, InvoiceMasthead masthead) { + column.addSection("InvoiceMeta", meta -> { + meta.margin(META_BLOCK_INSET); + meta.spacing(META_PAIR_GAP); + List entries = masthead.entries(); + for (int i = 0; i < entries.size(); i++) { + InvoiceMasthead.Entry entry = entries.get(i); + labelledRow(meta, "InvoiceMeta_" + i, entry.label(), entry.value(), + META_LABEL_W, META_LABEL, + entry.emphasized() ? META_ACCENT : META_VALUE, null); + } + }); + } + + /** The billed party against the shipped-to party, split by the same hairline. */ + static void renderParties(SectionBuilder body, InvoiceRecipient billTo, InvoiceRecipient shipTo) { + body.addRow("PartiesRow", row -> row + .margin(SEAM_RULE_TO_PARTIES) + .weights(PARTIES_SPLIT, 1 - PARTIES_SPLIT) + .addSection("BillToColumn", left -> { + left.borders(DocumentBorders.right(DocumentStroke.of(RULE, HAIRLINE))); + left.padding(new DocumentInsets(0, SEAM_CLEARANCE, 0, PARTY_INSET)); + renderParty(left, "BillTo", billTo, MeteredIcons.BILL_TO); + }) + .addSection("ShipToColumn", right -> { + right.padding(new DocumentInsets(0, 0, 0, SHIP_COLUMN_INSET)); + if (shipTo != null) { + renderParty(right, "ShipTo", shipTo, MeteredIcons.SHIP_TO); + } + })); + } + + /** + * The shared body of the two party blocks. The heading's mark hangs in the + * block's own margin: everything under it is left-aligned with the label, + * which is one padding on the body group rather than a margin repeated on + * every line. + */ + private static void renderParty(SectionBuilder column, String id, InvoiceRecipient party, + String iconToken) { + column.addSection(id, block -> { + block.keepTogether(); + block.spacing(PARTY_HEAD_GAP); + iconHeading(block, id + "Heading", iconToken, SECTION_ICON, SECTION_ICON_GAP, + party.heading(), SECTION_LABEL); + block.addSection(id + "Body", lines -> { + lines.padding(hangingBody(SECTION_ICON, SECTION_ICON_GAP)); + lines.spacing(PARTY_LINE_GAP); + lines.addParagraph(p -> p + .name(id + "Name") + .text(party.name()) + .textStyle(PARTY_NAME)); + List address = party.addressLines(); + for (int i = 0; i < address.size(); i++) { + String line = address.get(i); + int index = i; + lines.addParagraph(p -> p + .name(id + "Address_" + index) + .text(line) + .textStyle(BODY)); + } + if (!party.registrationNumber().isBlank()) { + layeredRow(lines, id + "Registration", row -> row + .margin(new DocumentInsets(PARTY_EXTRA_GAP, 0, 0, 0)) + .columns(DocumentRowColumn.fixed(PARTY_LABEL_W), DocumentRowColumn.auto()) + .addParagraph(p -> p.text(party.registrationLabel()).textStyle(BODY)) + .addParagraph(p -> p.text(party.registrationNumber()).textStyle(BODY))); + } + }); + }); + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/MeteredSettlement.java b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/MeteredSettlement.java new file mode 100644 index 000000000..1c917343b --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/MeteredSettlement.java @@ -0,0 +1,173 @@ +package com.demcha.compose.document.templates.invoice.presets; + +import com.demcha.compose.document.dsl.SectionBuilder; +import com.demcha.compose.document.node.DocumentLinkOptions; +import com.demcha.compose.document.node.RowVerticalAlign; +import com.demcha.compose.document.node.TextAlign; +import com.demcha.compose.document.style.DocumentInsets; +import com.demcha.compose.document.style.DocumentRowColumn; +import com.demcha.compose.document.style.DocumentStroke; +import com.demcha.compose.document.style.DocumentTextStyle; +import com.demcha.compose.document.templates.core.identity.ContactUri; +import com.demcha.compose.document.templates.data.invoice.InvoicePaymentBlock; +import com.demcha.compose.document.templates.data.invoice.InvoiceTotalsBlock; + +import java.util.List; + +import static com.demcha.compose.document.templates.invoice.presets.MeteredStyles.ACCENT; +import static com.demcha.compose.document.templates.invoice.presets.MeteredStyles.CLOSING_GUTTER; +import static com.demcha.compose.document.templates.invoice.presets.MeteredStyles.CLOSING_LEFT_W; +import static com.demcha.compose.document.templates.invoice.presets.MeteredStyles.CLOSING_RIGHT_W; +import static com.demcha.compose.document.templates.invoice.presets.MeteredStyles.DUE_ICON; +import static com.demcha.compose.document.templates.invoice.presets.MeteredStyles.DUE_ICON_GAP; +import static com.demcha.compose.document.templates.invoice.presets.MeteredStyles.DUE_LABEL; +import static com.demcha.compose.document.templates.invoice.presets.MeteredStyles.DUE_PADDING; +import static com.demcha.compose.document.templates.invoice.presets.MeteredStyles.DUE_TEXT_GAP; +import static com.demcha.compose.document.templates.invoice.presets.MeteredStyles.DUE_VALUE; +import static com.demcha.compose.document.templates.invoice.presets.MeteredStyles.HAIRLINE; +import static com.demcha.compose.document.templates.invoice.presets.MeteredStyles.PANEL_BORDER; +import static com.demcha.compose.document.templates.invoice.presets.MeteredStyles.PANEL_FILL; +import static com.demcha.compose.document.templates.invoice.presets.MeteredStyles.PANEL_HEAD_GAP; +import static com.demcha.compose.document.templates.invoice.presets.MeteredStyles.PANEL_ICON; +import static com.demcha.compose.document.templates.invoice.presets.MeteredStyles.PANEL_ICON_GAP; +import static com.demcha.compose.document.templates.invoice.presets.MeteredStyles.PANEL_LABEL_W; +import static com.demcha.compose.document.templates.invoice.presets.MeteredStyles.PANEL_PADDING; +import static com.demcha.compose.document.templates.invoice.presets.MeteredStyles.PANEL_RADIUS; +import static com.demcha.compose.document.templates.invoice.presets.MeteredStyles.PANEL_ROW_GAP; +import static com.demcha.compose.document.templates.invoice.presets.MeteredStyles.PANEL_TEXT; +import static com.demcha.compose.document.templates.invoice.presets.MeteredStyles.PANEL_TITLE; +import static com.demcha.compose.document.templates.invoice.presets.MeteredStyles.SEAM_TABLE_TO_CLOSING; +import static com.demcha.compose.document.templates.invoice.presets.MeteredStyles.SUMMARY_GAP; +import static com.demcha.compose.document.templates.invoice.presets.MeteredStyles.SUMMARY_INSET_L; +import static com.demcha.compose.document.templates.invoice.presets.MeteredStyles.SUMMARY_INSET_R; +import static com.demcha.compose.document.templates.invoice.presets.MeteredStyles.SUMMARY_W; +import static com.demcha.compose.document.templates.invoice.presets.MeteredStyles.TOTALS_GAP; +import static com.demcha.compose.document.templates.invoice.presets.MeteredStyles.TOTALS_LABEL; +import static com.demcha.compose.document.templates.invoice.presets.MeteredStyles.TOTALS_RULE_GAP; +import static com.demcha.compose.document.templates.invoice.presets.MeteredStyles.TOTALS_RULE_T; +import static com.demcha.compose.document.templates.invoice.presets.MeteredStyles.TOTALS_VALUE; +import static com.demcha.compose.document.templates.invoice.presets.MeteredStyles.TOTAL_LABEL; +import static com.demcha.compose.document.templates.invoice.presets.MeteredStyles.TOTAL_VALUE; +import static com.demcha.compose.document.templates.invoice.presets.MeteredStyles.WHITE; +import static com.demcha.compose.document.templates.invoice.presets.MeteredWidgets.iconHeading; +import static com.demcha.compose.document.templates.invoice.presets.MeteredWidgets.labelledRow; +import static com.demcha.compose.document.templates.invoice.presets.MeteredWidgets.layeredRow; + +/** + * The band under the table: the bank details on the left, and on the right the + * totals over the card that carries the due date. + */ +final class MeteredSettlement { + + private MeteredSettlement() { + } + + /** + * The closing row. + * + * @param body the page's body section + * @param payment the bank details and the due notice + * @param totals the summed rows and the grand total + * @param currencyCode the code every figure is written behind + */ + static void render(SectionBuilder body, InvoicePaymentBlock payment, + InvoiceTotalsBlock totals, String currencyCode) { + body.addRow("ClosingRow", row -> row + .margin(SEAM_TABLE_TO_CLOSING) + .verticalAlign(RowVerticalAlign.BOTTOM) + .gap(CLOSING_GUTTER) + .columns(DocumentRowColumn.fixed(CLOSING_LEFT_W), + DocumentRowColumn.fixed(CLOSING_RIGHT_W)) + .addSection("PaymentColumn", left -> renderPaymentDetails(left, payment)) + .addSection("SummaryColumn", right -> { + right.spacing(SUMMARY_GAP); + renderTotals(right, totals, currencyCode); + renderDueBy(right, payment); + })); + } + + private static void renderTotals(SectionBuilder column, InvoiceTotalsBlock totals, + String currencyCode) { + column.addSection("Totals", block -> { + block.keepTogether(); + block.padding(new DocumentInsets(0, SUMMARY_INSET_R, 0, SUMMARY_INSET_L)); + block.spacing(TOTALS_GAP); + List rows = totals.rows(); + for (int i = 0; i < rows.size(); i++) { + InvoiceTotalsBlock.Row row = rows.get(i); + totalsRow(block, "TotalsRow_" + i, row.label(), + MeteredText.codedMoney(currencyCode, row.amount()), + TOTALS_LABEL, TOTALS_VALUE); + } + block.addLine(l -> l + .name("TotalsRule") + .horizontal(SUMMARY_W) + .thickness(TOTALS_RULE_T) + .color(ACCENT) + .margin(new DocumentInsets(TOTALS_RULE_GAP, 0, TOTALS_RULE_GAP, 0))); + totalsRow(block, "TotalDue", totals.totalLabel(), + MeteredText.codedMoney(currencyCode, totals.totalAmount()), + TOTAL_LABEL, TOTAL_VALUE); + }); + } + + private static void totalsRow(SectionBuilder parent, String name, String label, String value, + DocumentTextStyle labelStyle, DocumentTextStyle valueStyle) { + layeredRow(parent, name, row -> row + .verticalAlign(RowVerticalAlign.CENTER) + .weights(1, 1) + .addParagraph(p -> p.text(label).textStyle(labelStyle)) + .addParagraph(p -> p.text(value).textStyle(valueStyle).align(TextAlign.RIGHT))); + } + + private static void renderPaymentDetails(SectionBuilder column, InvoicePaymentBlock payment) { + column.addSection("PaymentDetails", card -> { + card.keepTogether(); + card.softPanel(WHITE, PANEL_RADIUS, PANEL_PADDING, + DocumentStroke.of(PANEL_BORDER, HAIRLINE)); + card.spacing(PANEL_ROW_GAP); + iconHeading(card, "PaymentDetailsHeading", MeteredIcons.CARD, + PANEL_ICON, PANEL_ICON_GAP, payment.heading(), PANEL_TITLE); + card.addSection("PaymentDetailsRows", rows -> { + rows.margin(new DocumentInsets(PANEL_HEAD_GAP, 0, 0, 0)); + rows.spacing(PANEL_ROW_GAP); + List fields = payment.fields(); + for (int i = 0; i < fields.size(); i++) { + InvoicePaymentBlock.Field field = fields.get(i); + labelledRow(rows, "PaymentDetail_" + i, field.label(), field.value(), + PANEL_LABEL_W, PANEL_TEXT, PANEL_TEXT, linkFor(field.value())); + } + }); + }); + } + + /** + * A bank detail is a reference, not a destination — except the remittance + * address, which is the one field on the panel a reader would act on. + */ + private static DocumentLinkOptions linkFor(String value) { + return value.contains("@") ? ContactUri.mailLink(value) : null; + } + + private static void renderDueBy(SectionBuilder column, InvoicePaymentBlock payment) { + if (payment.dueNotice().isBlank() && payment.dueNoticeEmphasis().isBlank()) { + return; + } + column.addSection("PaymentDueBy", card -> { + card.keepTogether(); + card.softPanel(PANEL_FILL, PANEL_RADIUS, DUE_PADDING); + layeredRow(card, "PaymentDueByRow", row -> row + .verticalAlign(RowVerticalAlign.CENTER) + .columns(DocumentRowColumn.fixed(DUE_ICON + DUE_ICON_GAP), + DocumentRowColumn.auto()) + .addSection("PaymentDueByIcon", cell -> cell + .addSvgIcon(MeteredIcons.icon(MeteredIcons.CALENDAR), DUE_ICON)) + .addSection("PaymentDueByText", text -> text + .spacing(DUE_TEXT_GAP) + .addParagraph(p -> p.name("PaymentDueByLabel") + .text(payment.dueNotice()).textStyle(DUE_LABEL)) + .addParagraph(p -> p.name("PaymentDueByValue") + .text(payment.dueNoticeEmphasis()).textStyle(DUE_VALUE)))); + }); + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/MeteredStyles.java b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/MeteredStyles.java new file mode 100644 index 000000000..6b12017de --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/MeteredStyles.java @@ -0,0 +1,303 @@ +package com.demcha.compose.document.templates.invoice.presets; + +import com.demcha.compose.document.api.DocumentPageSize; +import com.demcha.compose.document.style.DocumentColor; +import com.demcha.compose.document.style.DocumentInsets; +import com.demcha.compose.document.style.DocumentStroke; +import com.demcha.compose.document.style.DocumentTextDecoration; +import com.demcha.compose.document.style.DocumentTextStyle; +import com.demcha.compose.document.table.DocumentTableStyle; +import com.demcha.compose.font.FontName; + +/** + * Every measurement, colour and type size of the Metered invoice. + * + *

    Geometry is relational. Every width is a fraction of {@link #CONTENT_W} and + * every length taken off the design is written as {@code px(n)} — the design's + * own pixel — so it still reads as the thing that was measured while scaling + * with the page. Two lengths are independent of the grid: the icon tile and the + * hairline weight.

    + */ +final class MeteredStyles { + + private MeteredStyles() { + } + + // ------------------------------------------------------------------ + // The page and its grid + // ------------------------------------------------------------------ + + static final DocumentPageSize PAGE = DocumentPageSize.A4; + + private static final double PAGE_W = PAGE.width(); + private static final double PAGE_H = PAGE.height(); + + /** The design image's pixel width — the scale every measurement below is in. */ + private static final double DESIGN_PX_WIDTH = 1055.0; + + /** Points per design pixel. The one conversion the whole preset goes through. */ + private static final double PX = PAGE_W / DESIGN_PX_WIDTH; + + static double px(double designPixels) { + return designPixels * PX; + } + + /** + * The side margins are not equal: every full-width element runs from x=35.5 + * to x=1011.5 of a 1055 px sheet, a 35.5 px left margin against a 43.5 px + * right one. Built symmetric at the mean it costs 4 px of horizontal error + * on every glyph in the document to save 8 px on one edge, so the asymmetry + * is kept as measured. + */ + static final double MARGIN_LEFT = px(35.5); + static final double MARGIN_RIGHT = px(43.5); + static final double TOP_MARGIN = px(44); + + /** The dark band's share of the page height; passed straight to the background. */ + static final double FOOTER_BAND_RATIO = 72.0 / 1491.0; + static final double FOOTER_BAND_H = PAGE_H * FOOTER_BAND_RATIO; + + /** The band plus clearance, so no continuation page runs its last row into the chrome. */ + static final double BOTTOM_MARGIN = FOOTER_BAND_H + px(8); + + /** Every column width on the page is a fraction of this. */ + static final double CONTENT_W = PAGE_W - MARGIN_LEFT - MARGIN_RIGHT; + + static final DocumentInsets PAGE_MARGIN = + new DocumentInsets(TOP_MARGIN, MARGIN_RIGHT, BOTTOM_MARGIN, MARGIN_LEFT); + + /** + * Every rule, border and table stroke on the page. + * + *

    The design's hairlines are one pixel of a 1055 px image, which is + * 0.6 pt — and a 0.6 pt stroke rasterises to less than a pixel at preview + * resolution, so half the table's row rules come out at partial opacity and + * the two panel outlines do not register at all. They are a pixel of ink, + * not a pixel of geometry; this is the weight that reproduces them.

    + */ + static final double HAIRLINE = px(1.7); + + // ------------------------------------------------------------------ + // Seams between the stacked bands + // ------------------------------------------------------------------ + + /** The base seam. The ones that differ carry the difference as their own margin. */ + static final double BAND_GAP = px(22); + + static final DocumentInsets SEAM_IDENTITY_TO_RULE = seam(14); + static final DocumentInsets SEAM_RULE_TO_PARTIES = seam(9); + static final DocumentInsets SEAM_PARTIES_TO_TABLE = seam(12); + static final DocumentInsets SEAM_TABLE_TO_CLOSING = seam(0); + static final DocumentInsets SEAM_CLOSING_TO_NOTE = seam(3); + + /** The two columns of the identity row start at different heights. */ + static final DocumentInsets SUPPLIER_BLOCK_INSET = seam(6); + static final DocumentInsets META_BLOCK_INSET = seam(11); + + private static DocumentInsets seam(double extraPixels) { + return new DocumentInsets(px(extraPixels), 0, 0, 0); + } + + // ------------------------------------------------------------------ + // Column splits + // ------------------------------------------------------------------ + + /** Identity row: supplier | hairline | invoice metadata. */ + static final double IDENTITY_SPLIT = 0.431; + static final double SEAM_CLEARANCE = px(20); + static final double META_COLUMN_INSET = px(141); + + /** Parties row: bill-to | hairline | ship-to. */ + static final double PARTIES_SPLIT = 0.494; + static final double SHIP_COLUMN_INSET = px(74); + + /** Closing row: payment card | gutter | totals over the due-by card. */ + static final double CLOSING_LEFT_W = CONTENT_W * (431.0 / 976.0); + static final double CLOSING_RIGHT_W = CONTENT_W * (422.0 / 976.0); + static final double CLOSING_GUTTER = px(113); + + /** The totals rows sit inside their column, not flush with the panel below them. */ + static final double SUMMARY_INSET_L = px(6); + static final double SUMMARY_INSET_R = px(7.5); + static final double SUMMARY_W = CLOSING_RIGHT_W - SUMMARY_INSET_L - SUMMARY_INSET_R; + static final double TOTALS_RULE_GAP = px(3.5); + + /** The five table columns, solved from the header-label centres. */ + static final double COL_DESCRIPTION = CONTENT_W * 0.2945; + static final double COL_SERVICE = CONTENT_W * 0.2352; + static final double COL_QUANTITY = CONTENT_W * 0.1288; + static final double COL_UNIT_PRICE = CONTENT_W * 0.1759; + static final double COL_AMOUNT = CONTENT_W * 0.1656; + + static final double ICON_TILE = px(44); + static final double ICON_TILE_RADIUS = px(10); + static final double LINE_ICON = px(25); + static final double TABLE_PAD_L = px(17.5); + static final double TILE_GAP = px(24); + + /** Splits the description column into the tile's lane and the text's lane. */ + static final double DESC_TILE_COL = TABLE_PAD_L + ICON_TILE + TILE_GAP; + static final double DESC_TEXT_COL = COL_DESCRIPTION - DESC_TILE_COL; + + static final double ROW_PAD_Y = px(14.3); + static final double HEADER_PAD_Y = px(14.5); + static final double LINE_TEXT_GAP = px(1); + + // ------------------------------------------------------------------ + // Cards and marks + // ------------------------------------------------------------------ + + static final double PANEL_RADIUS = px(10); + static final double PANEL_PADDING = px(21); + static final double DUE_PADDING = px(20); + + static final double SECTION_ICON = px(24); + static final double SECTION_ICON_GAP = px(15); + static final double PANEL_ICON = px(26); + static final double PANEL_ICON_GAP = px(5); + static final double DUE_ICON = px(36); + static final double DUE_ICON_GAP = px(22); + static final double NOTE_ICON = px(24); + static final double NOTE_ICON_GAP = px(19); + + /** Bill-to's mark and the note's mark hang inside the content edge, not on it. */ + static final double PARTY_INSET = px(7.5); + static final double NOTE_INSET = px(5.5); + + /** The masthead lockup's measured width. */ + static final double LOGO_W = px(222); + + /** Label columns: four pair lists, four widths, each stated once. */ + static final double SUPPLIER_LABEL_W = px(85); + static final double META_LABEL_W = px(153); + static final double PANEL_LABEL_W = px(172); + static final double PARTY_LABEL_W = px(71); + + // Line pitches, as the gap a section puts between its children. + static final double SUPPLIER_LINE_GAP = px(4.5); + static final double SUPPLIER_NAME_GAP = px(6); + static final double SUPPLIER_GROUP_GAP = px(24); + static final double SUPPLIER_PAIR_GAP = px(6.2); + static final double META_PAIR_GAP = px(16); + static final double PARTY_HEAD_GAP = px(9); + static final double PARTY_LINE_GAP = px(5); + static final double PARTY_EXTRA_GAP = px(14); + static final double TOTALS_GAP = px(15); + static final double TOTALS_RULE_T = px(2); + static final double SUMMARY_GAP = px(29); + static final double PANEL_HEAD_GAP = px(4); + static final double PANEL_ROW_GAP = px(4.7); + static final double DUE_TEXT_GAP = px(0); + static final double NOTE_RULE_GAP = px(22.5); + static final double NOTE_LINE_GAP = px(5); + static final double TITLE_RULE_GAP = px(17); + static final double TITLE_RULE_W = px(71); + static final double TITLE_RULE_T = px(4); + + // ------------------------------------------------------------------ + // Page chrome + // ------------------------------------------------------------------ + + /** + * Repeating chrome is positioned by its distance from the paper's bottom + * edge, so the two legal lines are two footer entries at two heights and the + * enumeration is a third on the band's own centre line. + */ + static final double FOOTER_LINE_ONE_H = 32.7; + static final double FOOTER_LINE_TWO_H = 21.5; + static final double FOOTER_PAGE_H = 27; + static final double FOOTER_SIZE = 7; + + // ------------------------------------------------------------------ + // Colour + // ------------------------------------------------------------------ + + static final DocumentColor INK = DocumentColor.rgb(11, 18, 28); + static final DocumentColor ACCENT = DocumentColor.rgb(251, 122, 0); + static final DocumentColor ACCENT_BRIGHT = DocumentColor.rgb(255, 140, 5); + static final DocumentColor BAND_DARK = DocumentColor.rgb(17, 26, 41); + static final DocumentColor FOOTER_DARK = DocumentColor.rgb(7, 18, 27); + static final DocumentColor INVERSE = DocumentColor.rgb(255, 255, 255); + static final DocumentColor PANEL_FILL = DocumentColor.rgb(248, 248, 248); + static final DocumentColor PANEL_BORDER = DocumentColor.rgb(235, 235, 235); + static final DocumentColor RULE = DocumentColor.rgb(222, 223, 226); + static final DocumentColor TABLE_RULE = DocumentColor.rgb(224, 225, 227); + static final DocumentColor TABLE_BORDER = DocumentColor.rgb(237, 237, 237); + static final DocumentColor WHITE = DocumentColor.WHITE; + + /** + * The sheet is set in Lato throughout, chosen by measurement against the + * design's own grotesque. The templates artifact carries no fonts — + * register the family on the session, or the engine substitutes and the + * measured geometry no longer matches the type sitting in it. + */ + static final FontName FACE = FontName.LATO; + + // ------------------------------------------------------------------ + // Type + // ------------------------------------------------------------------ + + static final DocumentTextStyle TITLE = bold(29, INK); + static final DocumentTextStyle SUPPLIER_NAME = bold(10.5, INK); + static final DocumentTextStyle BODY = plain(9.1, INK); + static final DocumentTextStyle META_LABEL = plain(8.6, INK); + static final DocumentTextStyle META_VALUE = plain(9.1, INK); + static final DocumentTextStyle META_ACCENT = bold(9.7, ACCENT); + static final DocumentTextStyle SECTION_LABEL = bold(7.9, ACCENT); + static final DocumentTextStyle PARTY_NAME = bold(10.5, INK); + static final DocumentTextStyle TABLE_HEAD = bold(7.5, INVERSE); + static final DocumentTextStyle LINE_TITLE = bold(10, INK); + static final DocumentTextStyle LINE_SUB = plain(8.6, INK); + static final DocumentTextStyle CELL = plain(8.2, INK); + static final DocumentTextStyle TOTALS_LABEL = plain(8.6, INK); + static final DocumentTextStyle TOTALS_VALUE = plain(8.6, INK); + static final DocumentTextStyle TOTAL_LABEL = bold(11, INK); + static final DocumentTextStyle TOTAL_VALUE = bold(14.3, ACCENT); + static final DocumentTextStyle PANEL_TITLE = bold(8.75, ACCENT); + static final DocumentTextStyle PANEL_TEXT = plain(8.1, INK); + static final DocumentTextStyle DUE_LABEL = bold(8, INK); + static final DocumentTextStyle DUE_VALUE = bold(11.4, INK); + static final DocumentTextStyle NOTE_LEAD = bold(9.1, INK); + static final DocumentTextStyle NOTE_BODY = plain(8.1, INK); + + static DocumentTextStyle plain(double size, DocumentColor color) { + return style(size, color, DocumentTextDecoration.DEFAULT); + } + + static DocumentTextStyle bold(double size, DocumentColor color) { + return style(size, color, DocumentTextDecoration.BOLD); + } + + private static DocumentTextStyle style(double size, DocumentColor color, + DocumentTextDecoration decoration) { + return DocumentTextStyle.builder() + .fontName(FACE) + .size(size) + .color(color) + .decoration(decoration) + .build(); + } + + // ------------------------------------------------------------------ + // Table cells + // ------------------------------------------------------------------ + + /** + * A table's rules are drawn per cell and cover all four of that cell's + * edges. A five-column stroked table therefore draws four internal + * verticals the design does not have, so the table is one column wide and + * each cell carries a composed row of six fixed sub-columns. That leaves + * exactly the topology the design has: an outer box and a rule between + * rows. + */ + static final DocumentTableStyle HEADER_CELL = DocumentTableStyle.builder() + .padding(new DocumentInsets(HEADER_PAD_Y, 0, HEADER_PAD_Y, 0)) + .fillColor(BAND_DARK) + .stroke(DocumentStroke.of(TABLE_BORDER, HAIRLINE)) + .build(); + + static final DocumentTableStyle BODY_CELL = DocumentTableStyle.builder() + .padding(new DocumentInsets(ROW_PAD_Y, 0, ROW_PAD_Y, 0)) + .stroke(DocumentStroke.of(TABLE_RULE, HAIRLINE)) + .build(); +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/MeteredText.java b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/MeteredText.java new file mode 100644 index 000000000..f393504af --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/MeteredText.java @@ -0,0 +1,75 @@ +package com.demcha.compose.document.templates.invoice.presets; + +import java.math.BigDecimal; +import java.text.NumberFormat; +import java.util.Locale; + +/** + * How the Metered invoice writes its figures. + * + *

    The locale is stated rather than inherited, so the same data renders the + * same sheet on any machine.

    + */ +final class MeteredText { + + /** A metered rate is quoted to four places; an amount to two. */ + private static final int RATE_MAX_DIGITS = 4; + private static final int MONEY_DIGITS = 2; + + private MeteredText() { + } + + /** An amount, grouped and to two places. */ + private static String money(BigDecimal value) { + return decimal(value, MONEY_DIGITS, MONEY_DIGITS); + } + + /** + * A unit price at the precision it is quoted at. + * + *

    Metered rates are fractions of a currency unit — an hour of compute at + * 0.0680 rounds to 0.07 at an amount's two places, which is a different + * price and multiplies out to a different bill. A rate is written at its own + * scale, never at fewer than two places and never at more than four.

    + * + * @param value the rate + * @return the rate as the sheet prints it + */ + private static String rate(BigDecimal value) { + int digits = Math.min(Math.max(value.scale(), MONEY_DIGITS), RATE_MAX_DIGITS); + return decimal(value, digits, digits); + } + + /** An amount behind its currency code, the way every figure on the sheet is written. */ + static String codedMoney(String currencyCode, BigDecimal value) { + return code(currencyCode, money(value)); + } + + /** A rate behind its currency code. */ + static String codedRate(String currencyCode, BigDecimal value) { + return code(currencyCode, rate(value)); + } + + /** A count and the thing it counts, or the count alone when the line names no unit. */ + static String quantityWithUnit(BigDecimal value, String unit) { + BigDecimal stripped = value.stripTrailingZeros(); + String written = stripped.scale() <= 0 + ? stripped.toBigInteger().toString() + : stripped.toPlainString(); + return unit == null || unit.isBlank() ? written : written + " " + unit; + } + + private static String code(String currencyCode, String written) { + return currencyCode == null || currencyCode.isBlank() + ? written + : currencyCode.trim() + " " + written; + } + + private static String decimal(BigDecimal value, int min, int max) { + NumberFormat format = NumberFormat.getNumberInstance(Locale.ENGLISH); + format.setGroupingUsed(true); + format.setMinimumFractionDigits(min); + format.setMaximumFractionDigits(max); + return format.format(value); + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/MeteredWidgets.java b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/MeteredWidgets.java new file mode 100644 index 000000000..a01dac897 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/MeteredWidgets.java @@ -0,0 +1,135 @@ +package com.demcha.compose.document.templates.invoice.presets; + +import com.demcha.compose.document.dsl.RowBuilder; +import com.demcha.compose.document.dsl.SectionBuilder; +import com.demcha.compose.document.dsl.ShapeContainerBuilder; +import com.demcha.compose.document.node.DocumentLinkOptions; +import com.demcha.compose.document.node.DocumentNode; +import com.demcha.compose.document.node.LayerAlign; +import com.demcha.compose.document.node.SpacerNode; +import com.demcha.compose.document.node.RowVerticalAlign; +import com.demcha.compose.document.style.DocumentInsets; +import com.demcha.compose.document.style.DocumentRowColumn; +import com.demcha.compose.document.style.DocumentStroke; +import com.demcha.compose.document.style.DocumentTextStyle; + +import java.util.function.Consumer; + +import static com.demcha.compose.document.templates.invoice.presets.MeteredStyles.HAIRLINE; +import static com.demcha.compose.document.templates.invoice.presets.MeteredStyles.ICON_TILE; +import static com.demcha.compose.document.templates.invoice.presets.MeteredStyles.ICON_TILE_RADIUS; +import static com.demcha.compose.document.templates.invoice.presets.MeteredStyles.LINE_ICON; +import static com.demcha.compose.document.templates.invoice.presets.MeteredStyles.PANEL_BORDER; +import static com.demcha.compose.document.templates.invoice.presets.MeteredStyles.WHITE; + +/** + * The shapes the Metered invoice repeats: a horizontal pair inside a column, a + * label/value pair over a fixed label lane, a mark beside a caption, and the + * bordered tile a service mark sits in. + */ +final class MeteredWidgets { + + private MeteredWidgets() { + } + + /** + * A row wrapped in a layer, so it can sit inside a row cell. + * + *

    A row nested in a row cell is refused by the layout compiler, and + * through a section too — it is the ancestry that matters, not the immediate + * parent. A row wrapped in a LayerStack layer lays out horizontally there. + * Every horizontal pair inside one of this page's three two-column rows goes + * through here; the ones at the top of the flow do not need it.

    + * + * @param parent the section the pair belongs to + * @param name the pair's name, which its holder and layer extend + * @param spec the row itself + */ + static void layeredRow(SectionBuilder parent, String name, Consumer spec) { + SectionBuilder holder = new SectionBuilder(); + holder.name(name + "Holder"); + holder.addRow(name, spec); + DocumentNode node = holder.build(); + parent.addLayerStack(stack -> stack + .name(name + "Layer") + .layer(node, LayerAlign.TOP_LEFT, 0)); + } + + /** + * One label/value pair over a fixed label column, so every value in a list + * starts on the same axis. Four lists on this page use it at four widths. + * + * @param parent the section the pair belongs to + * @param name the pair's name + * @param label the label text + * @param value the value text + * @param labelWidth the label lane's width + * @param labelStyle the label's type + * @param valueStyle the value's type + * @param link a target for the value, or {@code null} when it is not + * something a reader can act on + */ + static void labelledRow(SectionBuilder parent, String name, String label, String value, + double labelWidth, + DocumentTextStyle labelStyle, DocumentTextStyle valueStyle, + DocumentLinkOptions link) { + layeredRow(parent, name, row -> row + .columns(DocumentRowColumn.fixed(labelWidth), DocumentRowColumn.auto()) + .addParagraph(p -> p.text(label).textStyle(labelStyle)) + .addParagraph(p -> { + p.text(value).textStyle(valueStyle); + if (link != null) { + p.link(link); + } + })); + } + + /** A mark and a caption on one centre line, with the gap owned by the column. */ + static void iconHeading(SectionBuilder parent, String name, String iconToken, + double iconSize, double gap, String text, + DocumentTextStyle textStyle) { + layeredRow(parent, name, row -> row + .verticalAlign(RowVerticalAlign.CENTER) + .columns(DocumentRowColumn.fixed(iconSize + gap), DocumentRowColumn.auto()) + .addSection(name + "Icon", cell -> cell.addSvgIcon(MeteredIcons.icon(iconToken), iconSize)) + .addParagraph(p -> p.name(name + "Label").text(text).textStyle(textStyle))); + } + + /** + * The bordered square a service mark lives in — the tile is laid out, the + * mark is its content. + * + * @param token the mark's token + * @param index the line's index, which names the tile + * @return the tile + */ + static DocumentNode tile(String token, int index) { + ShapeContainerBuilder tile = new ShapeContainerBuilder(); + tile.name("LineIconTile_" + index) + .roundedRect(ICON_TILE, ICON_TILE, ICON_TILE_RADIUS) + .fillColor(WHITE) + .stroke(DocumentStroke.of(PANEL_BORDER, HAIRLINE)) + .center(MeteredIcons.icon(token).node(LINE_ICON)); + return tile.build(); + } + + /** + * The tile's space without the tile, for a line that names no mark. + * + *

    Drawing an empty bordered box would be a mark of its own — the design + * has none — so the lane keeps its width and its height and stays blank, + * which leaves every text lane down the column on the same axis.

    + * + * @param index the line's index, which names the space + * @return a node the size of a tile + */ + static DocumentNode tileSpace(int index) { + return new SpacerNode("LineIconSpace_" + index, ICON_TILE, ICON_TILE, + DocumentInsets.zero(), DocumentInsets.zero()); + } + + /** A section padded to hold a block's body under a hanging mark. */ + static DocumentInsets hangingBody(double iconSize, double gap) { + return new DocumentInsets(0, 0, 0, iconSize + gap); + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/ObsidianCards.java b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/ObsidianCards.java new file mode 100644 index 000000000..904dd049e --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/ObsidianCards.java @@ -0,0 +1,171 @@ +package com.demcha.compose.document.templates.invoice.presets; + +import com.demcha.compose.document.dsl.PageFlowBuilder; +import com.demcha.compose.document.dsl.SectionBuilder; +import com.demcha.compose.document.node.HorizontalAlign; +import com.demcha.compose.document.node.RowVerticalAlign; +import com.demcha.compose.document.style.DocumentInsets; +import com.demcha.compose.document.style.DocumentRowColumn; +import com.demcha.compose.document.templates.core.identity.ContactUri; +import com.demcha.compose.document.templates.data.invoice.InvoiceNotesBlock; +import com.demcha.compose.document.templates.data.invoice.InvoicePaymentBlock; + +import java.util.List; + +import static com.demcha.compose.document.templates.invoice.presets.ObsidianStyles.ACCENT; +import static com.demcha.compose.document.templates.invoice.presets.ObsidianStyles.CARD_BODY_SIZE; +import static com.demcha.compose.document.templates.invoice.presets.ObsidianStyles.CARD_GUTTER; +import static com.demcha.compose.document.templates.invoice.presets.ObsidianStyles.CARD_HEAD_SIZE; +import static com.demcha.compose.document.templates.invoice.presets.ObsidianStyles.CARD_ICON; +import static com.demcha.compose.document.templates.invoice.presets.ObsidianStyles.CARD_ICON_COL; +import static com.demcha.compose.document.templates.invoice.presets.ObsidianStyles.CARD_PAD_H; +import static com.demcha.compose.document.templates.invoice.presets.ObsidianStyles.CARD_RADIUS; +import static com.demcha.compose.document.templates.invoice.presets.ObsidianStyles.CARD_STROKE; +import static com.demcha.compose.document.templates.invoice.presets.ObsidianStyles.HALF; +import static com.demcha.compose.document.templates.invoice.presets.ObsidianStyles.INK; +import static com.demcha.compose.document.templates.invoice.presets.ObsidianStyles.MUTED; +import static com.demcha.compose.document.templates.invoice.presets.ObsidianStyles.PAY_LABEL_SIZE; +import static com.demcha.compose.document.templates.invoice.presets.ObsidianStyles.PAY_VALUE_SIZE; +import static com.demcha.compose.document.templates.invoice.presets.ObsidianStyles.SURFACE; +import static com.demcha.compose.document.templates.invoice.presets.ObsidianStyles.bold; +import static com.demcha.compose.document.templates.invoice.presets.ObsidianStyles.capGap; +import static com.demcha.compose.document.templates.invoice.presets.ObsidianStyles.capTop; +import static com.demcha.compose.document.templates.invoice.presets.ObsidianStyles.plain; +import static com.demcha.compose.document.templates.invoice.presets.ObsidianStyles.px; +import static com.demcha.compose.document.templates.invoice.presets.ObsidianStyles.topBearing; +import static com.demcha.compose.document.templates.invoice.presets.ObsidianWidgets.glyph; +import static com.demcha.compose.document.templates.invoice.presets.ObsidianWidgets.layeredRow; +import static com.demcha.compose.document.templates.invoice.presets.ObsidianWidgets.linked; + +/** + * The two information cards under the totals: the notes beside the payment + * details, each opening with a marked heading. + */ +final class ObsidianCards { + + private ObsidianCards() { + } + + /** + * The cards row. + * + * @param page the page flow + * @param notes the note the card on the left carries + * @param payment the payment details the card on the right carries + */ + static void render(PageFlowBuilder page, InvoiceNotesBlock notes, + InvoicePaymentBlock payment) { + page.addRow("InfoCardsRow", row -> { + row.spacing(CARD_GUTTER) + .verticalAlign(RowVerticalAlign.TOP) + .margin(new DocumentInsets(px(15), 0, 0, 0)) + .weights(HALF, HALF); + row.addSection("NotesCard", cell -> renderNotes(cell, notes)); + row.addSection("PaymentCard", cell -> renderPayment(cell, payment)); + }); + } + + private static void renderNotes(SectionBuilder cell, InvoiceNotesBlock notes) { + chrome(cell, 1375 - 1331, CARD_BODY_SIZE); + head(cell, "Notes", ObsidianIcons.NOTES, notes.heading()); + cell.addSection("NotesBody", body -> { + body.spacing(0).padding(new DocumentInsets(0, 0, 0, CARD_ICON_COL - CARD_PAD_H)); + List paragraphs = notes.paragraphs(); + for (int i = 0; i < paragraphs.size(); i++) { + String prose = paragraphs.get(i); + int index = i; + double above = index == 0 + ? px(1222 - 1182) - CARD_ICON - topBearing(CARD_BODY_SIZE, false) + : capGap(1308 - 1267, CARD_BODY_SIZE, false, CARD_BODY_SIZE, false); + double gapAbove = Math.max(0, above); + body.addParagraph(p -> p + .name("NotesParagraph_" + index) + .text(prose) + .textStyle(plain(CARD_BODY_SIZE, MUTED)) + .lineSpacing(Math.max(0, px(22.5) - CARD_BODY_SIZE)) + .margin(new DocumentInsets(gapAbove, 0, 0, 0))); + } + }); + } + + private static void renderPayment(SectionBuilder cell, InvoicePaymentBlock payment) { + chrome(cell, 1375 - 1339, PAY_VALUE_SIZE); + head(cell, "Payment", ObsidianIcons.PAYMENT, payment.heading()); + if (!payment.instruction().isBlank()) { + cell.addParagraph(p -> p + .name("PaymentIntro") + .text(payment.instruction()) + .textStyle(plain(CARD_BODY_SIZE, MUTED)) + .lineSpacing(Math.max(0, px(19.4) - CARD_BODY_SIZE)) + .margin(new DocumentInsets(Math.max(0, px(1221 - 1182) - CARD_ICON + - topBearing(CARD_BODY_SIZE, false)), 0, 0, 0))); + } + cell.addSection("PaymentFields", fields -> { + fields.spacing(Math.max(0, px(22.7) - PAY_VALUE_SIZE)) + .margin(new DocumentInsets(Math.max(0, capGap( + 1271 - 1240, CARD_BODY_SIZE, false, PAY_LABEL_SIZE, false)), + 0, 0, 0)); + List entries = payment.fields(); + for (int i = 0; i < entries.size(); i++) { + InvoicePaymentBlock.Field field = entries.get(i); + int index = i; + String name = "PaymentField_" + index; + layeredRow(fields, name, row -> { + row.spacing(0) + .columns(DocumentRowColumn.fixed(px(150)), + DocumentRowColumn.weight(1)); + row.addParagraph(p -> p + .name(name + "Label") + .text(field.label()) + .textStyle(plain(PAY_LABEL_SIZE, MUTED))); + row.addParagraph(p -> linked(p + .name(name + "Value") + .text(field.value()) + .textStyle(plain(PAY_VALUE_SIZE, INK)), + // A bank detail is a reference, not a destination — + // except an address, the one field a reader would act on. + field.value().contains("@") + ? ContactUri.mailLink(field.value()) + : null)); + }); + } + }); + } + + /** + * A card's fill, border and lower padding. + * + *

    The padding is solved from the last line's cap to the card's measured + * bottom, so a card with a different number of lines still closes the same + * distance under the last of them.

    + */ + private static void chrome(SectionBuilder cell, double lastCapToBottomPx, double lastSize) { + cell.spacing(0) + .fillColor(SURFACE) + .stroke(CARD_STROKE) + .cornerRadius(CARD_RADIUS) + .padding(new DocumentInsets(0, CARD_PAD_H, + Math.max(0, capGap(lastCapToBottomPx, lastSize, false, 0, false)), + CARD_PAD_H)) + .keepTogether(); + } + + private static void head(SectionBuilder cell, String prefix, String token, String heading) { + layeredRow(cell, prefix + "Head", row -> { + row.spacing(0) + .verticalAlign(RowVerticalAlign.TOP) + .margin(new DocumentInsets(px(1182 - 1157), 0, 0, 0)) + .columns(DocumentRowColumn.fixed(CARD_ICON_COL - CARD_PAD_H), + DocumentRowColumn.weight(1)); + row.addSection(prefix + "Icon", cell2 -> cell2 + .spacing(0) + .add(glyph(ObsidianIcons.icon(token), token, CARD_ICON, + HorizontalAlign.LEFT))); + row.addParagraph(p -> p + .name(prefix + "Heading") + .text(heading) + .textStyle(bold(CARD_HEAD_SIZE, ACCENT)) + .margin(capTop(1187 - 1182, CARD_HEAD_SIZE, true))); + }); + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/ObsidianClosing.java b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/ObsidianClosing.java new file mode 100644 index 000000000..4aa7a5d96 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/ObsidianClosing.java @@ -0,0 +1,142 @@ +package com.demcha.compose.document.templates.invoice.presets; + +import com.demcha.compose.document.dsl.ImageBuilder; +import com.demcha.compose.document.dsl.PageFlowBuilder; +import com.demcha.compose.document.node.DocumentNode; +import com.demcha.compose.document.node.RowVerticalAlign; +import com.demcha.compose.document.node.TextAlign; +import com.demcha.compose.document.style.DocumentInsets; +import com.demcha.compose.document.style.DocumentRowColumn; +import com.demcha.compose.document.templates.core.identity.ContactUri; +import com.demcha.compose.document.templates.data.invoice.InvoiceBrand; +import com.demcha.compose.document.templates.data.invoice.InvoiceContactBlock; +import com.demcha.compose.document.templates.data.invoice.InvoicePaymentBlock; + +import static com.demcha.compose.document.templates.invoice.presets.ObsidianStyles.ACCENT; +import static com.demcha.compose.document.templates.invoice.presets.ObsidianStyles.CLOSING_RIGHT_SIZE; +import static com.demcha.compose.document.templates.invoice.presets.ObsidianStyles.CLOSING_SIZE; +import static com.demcha.compose.document.templates.invoice.presets.ObsidianStyles.DISC_CLOSING; +import static com.demcha.compose.document.templates.invoice.presets.ObsidianStyles.INK; +import static com.demcha.compose.document.templates.invoice.presets.ObsidianStyles.LINE_BOX; +import static com.demcha.compose.document.templates.invoice.presets.ObsidianStyles.MUTED; +import static com.demcha.compose.document.templates.invoice.presets.ObsidianStyles.bold; +import static com.demcha.compose.document.templates.invoice.presets.ObsidianStyles.plain; +import static com.demcha.compose.document.templates.invoice.presets.ObsidianStyles.px; +import static com.demcha.compose.document.templates.invoice.presets.ObsidianWidgets.disc; +import static com.demcha.compose.document.templates.invoice.presets.ObsidianWidgets.initials; +import static com.demcha.compose.document.templates.invoice.presets.ObsidianWidgets.initialsDisc; + +/** + * The closing band: a disc, the sign-off and the due sentence beside it, and the + * issuer's own identity against the right margin. + */ +final class ObsidianClosing { + + private ObsidianClosing() { + } + + /** + * The band. + * + *

    Bottom-aligned, not centred. Measured, the disc and both text columns + * end on the same line — they sit on the disc's lower edge rather than on its + * middle, and centring them lifts every line a few pixels.

    + * + * @param page the page flow + * @param brand the issuer's brand, whose logo the disc carries + * @param supplier the issuer + * @param payment the sign-off and the due sentence + */ + static void render(PageFlowBuilder page, InvoiceBrand brand, + InvoiceContactBlock supplier, InvoicePaymentBlock payment) { + page.addRow("ClosingBand", row -> { + row.spacing(0) + .verticalAlign(RowVerticalAlign.BOTTOM) + .padding(new DocumentInsets(0, px(6), 0, px(5))) + .columns(DocumentRowColumn.fixed(px(73)), + DocumentRowColumn.weight(1), + DocumentRowColumn.fixed(px(160))); + row.addSection("ClosingDisc", cell -> cell.spacing(0).add(closingDisc(brand, supplier))); + row.addSection("ClosingText", text -> { + text.spacing(Math.max(0, px(1448 - 1423) - LINE_BOX * CLOSING_SIZE)); + if (!payment.signOff().isBlank()) { + text.addParagraph(p -> p + .name("ClosingSignOff") + .text(payment.signOff()) + .textStyle(bold(CLOSING_SIZE, INK))); + } + if (!payment.dueNotice().isBlank()) { + text.addParagraph(p -> { + p.name("ClosingDue"); + writeEmphasised(p, payment.dueNotice(), payment.dueNoticeEmphasis()); + }); + } + }); + row.addSection("ClosingContact", text -> { + text.spacing(Math.max(0, px(1448 - 1423) - LINE_BOX * CLOSING_RIGHT_SIZE)); + text.addParagraph(p -> p + .name("ClosingName") + .text(supplier.legalName()) + .textStyle(plain(CLOSING_RIGHT_SIZE, INK)) + .align(TextAlign.RIGHT)); + if (!supplier.email().isBlank()) { + text.addParagraph(p -> { + p.name("ClosingContact").align(TextAlign.RIGHT); + p.inlineText(supplier.email(), plain(CLOSING_RIGHT_SIZE, MUTED), + ContactUri.mailLink(supplier.email())); + }); + } + }); + }); + } + + /** The same mark the issuer's card carries, at the band's smaller size. */ + private static DocumentNode closingDisc(InvoiceBrand brand, InvoiceContactBlock supplier) { + double diameter = px(46); + if (brand.logo() != null) { + return disc("ClosingDiscShape", DISC_CLOSING, new ImageBuilder() + .name("ClosingDiscLogo") + .source(brand.logo()) + .width(px(21)) + .build(), diameter); + } + String monogram = (brand.monogramTop() + brand.monogramBottom()).trim(); + String letters = monogram.isBlank() + ? initials(brand.name().isBlank() ? supplier.legalName() : brand.name()) + : monogram; + return ObsidianWidgets.disc("ClosingDiscShape", DISC_CLOSING, + new com.demcha.compose.document.dsl.ParagraphBuilder() + .name("ClosingDiscInitials") + .text(letters) + .textStyle(bold(CLOSING_SIZE, INK)) + .align(TextAlign.CENTER) + .build(), + diameter); + } + + /** + * The due sentence, with the date inside it set in the accent. + * + *

    The design colours the date rather than breaking it out, so it is a run + * of the same sentence: the emphasis names a substring, the preset finds it, + * and the text around it stays as it was written. A sentence that does not + * contain the emphasis is written whole — the sheet still says what it + * says.

    + */ + private static void writeEmphasised(com.demcha.compose.document.dsl.ParagraphBuilder paragraph, + String prose, String emphasis) { + int at = emphasis.isBlank() ? -1 : prose.indexOf(emphasis); + if (at < 0) { + paragraph.inlineText(prose, plain(CLOSING_SIZE, MUTED)); + return; + } + if (at > 0) { + paragraph.inlineText(prose.substring(0, at), plain(CLOSING_SIZE, MUTED)); + } + paragraph.inlineText(emphasis, bold(CLOSING_SIZE, ACCENT)); + int after = at + emphasis.length(); + if (after < prose.length()) { + paragraph.inlineText(prose.substring(after), plain(CLOSING_SIZE, MUTED)); + } + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/ObsidianIcons.java b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/ObsidianIcons.java new file mode 100644 index 000000000..acb2134ec --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/ObsidianIcons.java @@ -0,0 +1,55 @@ +package com.demcha.compose.document.templates.invoice.presets; + +import com.demcha.compose.document.svg.SvgIcon; + +import java.io.IOException; +import java.io.InputStream; +import java.io.UncheckedIOException; +import java.nio.charset.StandardCharsets; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * The two marks the Obsidian invoice packages, one for each of its information + * cards. Both are the preset's: which glyph opens the notes and which opens the + * payment details is a property of the design, not of the invoice. + * + *

    The party discs carry no packaged mark. The supplier's shows the caller's + * logo when there is one and the brand's initials otherwise; the billed party's + * shows initials taken from its name.

    + */ +final class ObsidianIcons { + + static final String NOTES = "notes"; + static final String PAYMENT = "payment"; + + private static final String ICON_ROOT = "/templates/invoice/obsidian/icons/"; + + private static final Map CACHE = new ConcurrentHashMap<>(); + + private ObsidianIcons() { + } + + /** + * A packaged mark by name. + * + * @param token the mark's name + * @return the parsed icon + */ + static SvgIcon icon(String token) { + return CACHE.computeIfAbsent(token, ObsidianIcons::read); + } + + private static SvgIcon read(String token) { + String resourcePath = ICON_ROOT + token + ".svg"; + try (InputStream input = ObsidianIcons.class.getResourceAsStream(resourcePath)) { + if (input == null) { + throw new IllegalStateException("Missing obsidian invoice icon: " + resourcePath); + } + return SvgIcon.parse(new String(input.readAllBytes(), StandardCharsets.UTF_8)); + } catch (IOException e) { + throw new UncheckedIOException( + "Failed to read obsidian invoice icon: " + resourcePath, e); + } + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/ObsidianInvoice.java b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/ObsidianInvoice.java new file mode 100644 index 000000000..ead09b9b3 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/ObsidianInvoice.java @@ -0,0 +1,187 @@ +package com.demcha.compose.document.templates.invoice.presets; + +import com.demcha.compose.document.api.DocumentSession; +import com.demcha.compose.document.api.PageMarginRule; +import com.demcha.compose.document.output.DocumentHeaderFooter; +import com.demcha.compose.document.output.DocumentHeaderFooterZone; +import com.demcha.compose.document.style.DocumentInsets; +import com.demcha.compose.document.templates.api.DocumentTemplate; +import com.demcha.compose.document.templates.data.invoice.StructuredInvoiceData; + +import java.util.List; +import java.util.Objects; + +import static com.demcha.compose.document.templates.invoice.presets.ObsidianStyles.CONTENT_W; +import static com.demcha.compose.document.templates.invoice.presets.ObsidianStyles.CONTINUATION_MARGIN; +import static com.demcha.compose.document.templates.invoice.presets.ObsidianStyles.FACE; +import static com.demcha.compose.document.templates.invoice.presets.ObsidianStyles.HAIRLINE; +import static com.demcha.compose.document.templates.invoice.presets.ObsidianStyles.META_VALUE_SIZE; +import static com.demcha.compose.document.templates.invoice.presets.ObsidianStyles.MUTED; +import static com.demcha.compose.document.templates.invoice.presets.ObsidianStyles.PAGE; +import static com.demcha.compose.document.templates.invoice.presets.ObsidianStyles.PAGE_BG; +import static com.demcha.compose.document.templates.invoice.presets.ObsidianStyles.PAGE_MARGIN; +import static com.demcha.compose.document.templates.invoice.presets.ObsidianStyles.PAGE_NUMBER_BAND; +import static com.demcha.compose.document.templates.invoice.presets.ObsidianStyles.PAGE_NUMBER_SIZE; +import static com.demcha.compose.document.templates.invoice.presets.ObsidianStyles.RULE_BOX; +import static com.demcha.compose.document.templates.invoice.presets.ObsidianStyles.blockGap; +import static com.demcha.compose.document.templates.invoice.presets.ObsidianStyles.px; +import static com.demcha.compose.document.templates.invoice.presets.ObsidianWidgets.rule; + +/** + * Obsidian — a dark invoice built from rounded cards: a wordmark over the + * document's name against a ruled metadata stack, the issuer and the billed + * party on discs in cards of their own, a line-item table in a third, the totals + * in a fourth, the notes and payment details side by side in a fifth and sixth, + * and a closing band under a hairline. + * + *

    The sheet is dark, and it is the page that is dark

    + * + *

    The fill is set on the page rather than on the flow. A container's fill is + * bounded by its content height, so a continuation page carrying three line + * items would show two thirds of a white sheet under them; the page background + * covers the paper whatever the content does.

    + * + *

    Sizes are solved from ink, not from cap height

    + * + *

    The design's own face is not bundled, and matching a cap height alone would + * still set every string to the wrong width. Each size here is solved from the + * measured ink width of the string it sets, with the substitute's width ratio + * applied once — and every size is named, in the styles, by the string it came + * from. Vertical positions are cap tops rather than box edges, because ink is + * what the design can be measured on.

    + * + *

    Six columns inside one

    + * + *

    A cell's stroke draws all four of its edges, so a six-column table would + * draw five verticals this design does not have. The table is one column wide + * and each cell carries a composed row of six, with the rule under a row drawn + * inside that row's own content — which is what lets the last row leave the rule + * out and take the card's lower padding instead.

    + * + *

    The tax column is worked out, not asked for

    + * + *

    This design gives the tax its own money column where the model carries a + * rate. The figure is not a second thing to state: a line already gives the + * quantity, the unit price and the total it comes to, so the tax is the + * difference between what was charged and what the goods cost. Reading it off + * the line is also what keeps the column and the total consistent when either + * moves.

    + * + *

    The marks are the caller's, and there are two of them

    + * + *

    The issuer's disc — in its party card and again in the closing band — + * carries the logo from {@code InvoiceBrand} when there is one, the brand's own + * monogram when it states one, and initials taken from the name otherwise. The + * billed party's disc always shows initials taken from its name. The templates + * artifact carries no mark of its own; the masthead sets the brand's name as + * type, which is what this design does with it.

    + * + *

    The closing band is the design's tightest place

    + * + *

    Its right column is the design's own measured width, which holds an + * address of about twenty characters. A longer one wraps there rather than + * overflowing — correct, but it makes the band a line taller, and on a sheet + * whose content already reaches the foot that is enough to carry the band to a + * second page. The design leaves very little under it.

    + * + *

    Fonts

    + * + *

    The sheet is set in Gothic A1, which is the family every size above was + * solved against. The templates artifact carries no fonts — register it on the + * session, or the engine substitutes and every size is solved for type that is + * not there.

    + * + *

    Usage:

    + *
    {@code
    + * DocumentTemplate template = ObsidianInvoice.create();
    + * template.compose(session, invoice);
    + * }
    + * + * @since 2.4.0 + */ +public final class ObsidianInvoice { + + /** Stable identifier of this preset. */ + public static final String ID = "obsidian-invoice"; + + /** Human-readable name of this preset. */ + public static final String DISPLAY_NAME = "Obsidian Invoice"; + + private ObsidianInvoice() { + } + + /** + * Creates the template. + * + * @return a template composing a {@link StructuredInvoiceData} + */ + public static DocumentTemplate create() { + return new Template(); + } + + private record Template() implements DocumentTemplate { + + @Override + public String id() { + return ID; + } + + @Override + public String displayName() { + return DISPLAY_NAME; + } + + @Override + public void compose(DocumentSession document, StructuredInvoiceData data) { + Objects.requireNonNull(document, "document"); + Objects.requireNonNull(data, "data"); + + document.pageSize(PAGE); + document.margin(PAGE_MARGIN); + document.pageMargins(List.of(PageMarginRule.from(2, CONTINUATION_MARGIN))); + // Not a fill on the flow: a container's fill is bounded by content + // height, and a continuation page holding three line items would show + // two thirds of a white sheet under them. + document.pageBackground(PAGE_BG); + renderPageNumber(document); + + String currency = data.currencyCode(); + document.pageFlow(page -> { + page.name("ObsidianInvoice").padding(DocumentInsets.zero()).spacing(0); + ObsidianMasthead.render(page, data.brand(), data.masthead()); + page.addLine(line -> rule(line, "HeaderDivider", CONTENT_W, HAIRLINE) + .margin(new DocumentInsets( + Math.max(0, blockGap(28.5, META_VALUE_SIZE, 0)), 0, px(27), 0))); + ObsidianParties.render(page, data.brand(), data.supplier(), data.billTo()); + ObsidianLines.render(page, data.serviceLines(), currency); + ObsidianSettlement.render(page, data.totals(), currency); + ObsidianCards.render(page, data.notes(), data.payment()); + page.addLine(line -> rule(line, "FooterDivider", CONTENT_W, HAIRLINE) + .margin(new DocumentInsets(px(23), 0, + Math.max(0, px(1417 - 1398.5) - RULE_BOX), 0))); + ObsidianClosing.render(page, data.brand(), data.supplier(), data.payment()); + }); + } + + /** + * The enumeration. The design has none — it is one sheet — but the + * document flows, and a reader holding page 1 of 2 has to be able to tell + * that page 2 is missing. + * + *

    The face is stated because the zone's own default is the standard-14 + * one, which would leave the number the single line on the sheet in a + * family the design never uses.

    + */ + private static void renderPageNumber(DocumentSession document) { + document.footer(DocumentHeaderFooter.builder() + .zone(DocumentHeaderFooterZone.FOOTER) + .centerText("Page {page} of {pages}") + .height((float) PAGE_NUMBER_BAND) + .fontName(FACE) + .fontSize((float) PAGE_NUMBER_SIZE) + .textColor(MUTED) + .showSeparator(false) + .build()); + } + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/ObsidianLines.java b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/ObsidianLines.java new file mode 100644 index 000000000..72317d388 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/ObsidianLines.java @@ -0,0 +1,203 @@ +package com.demcha.compose.document.templates.invoice.presets; + +import com.demcha.compose.document.dsl.PageFlowBuilder; +import com.demcha.compose.document.dsl.RowBuilder; +import com.demcha.compose.document.dsl.SectionBuilder; +import com.demcha.compose.document.dsl.TableBuilder; +import com.demcha.compose.document.node.DocumentNode; +import com.demcha.compose.document.node.RowVerticalAlign; +import com.demcha.compose.document.node.TextAlign; +import com.demcha.compose.document.style.DocumentInsets; +import com.demcha.compose.document.style.DocumentRowColumn; +import com.demcha.compose.document.table.DocumentTableCell; +import com.demcha.compose.document.table.DocumentTableColumn; +import com.demcha.compose.document.templates.data.invoice.InvoiceServiceLines; + +import java.util.List; + +import static com.demcha.compose.document.templates.invoice.presets.ObsidianStyles.CARD_RADIUS; +import static com.demcha.compose.document.templates.invoice.presets.ObsidianStyles.CARD_STROKE; +import static com.demcha.compose.document.templates.invoice.presets.ObsidianStyles.CELL_PAD_L; +import static com.demcha.compose.document.templates.invoice.presets.ObsidianStyles.CELL_PAD_R; +import static com.demcha.compose.document.templates.invoice.presets.ObsidianStyles.CELL_STYLE; +import static com.demcha.compose.document.templates.invoice.presets.ObsidianStyles.COLUMN_SHARES; +import static com.demcha.compose.document.templates.invoice.presets.ObsidianStyles.FIGURE_SIZE; +import static com.demcha.compose.document.templates.invoice.presets.ObsidianStyles.HAIRLINE; +import static com.demcha.compose.document.templates.invoice.presets.ObsidianStyles.HAIRLINE_STRONG; +import static com.demcha.compose.document.templates.invoice.presets.ObsidianStyles.INK; +import static com.demcha.compose.document.templates.invoice.presets.ObsidianStyles.ITEM_SUB_SIZE; +import static com.demcha.compose.document.templates.invoice.presets.ObsidianStyles.ITEM_TITLE_SIZE; +import static com.demcha.compose.document.templates.invoice.presets.ObsidianStyles.LINE_BOX; +import static com.demcha.compose.document.templates.invoice.presets.ObsidianStyles.MUTED; +import static com.demcha.compose.document.templates.invoice.presets.ObsidianStyles.RULE_BOX; +import static com.demcha.compose.document.templates.invoice.presets.ObsidianStyles.SURFACE; +import static com.demcha.compose.document.templates.invoice.presets.ObsidianStyles.TABLE_HEAD_SIZE; +import static com.demcha.compose.document.templates.invoice.presets.ObsidianStyles.TABLE_INSET; +import static com.demcha.compose.document.templates.invoice.presets.ObsidianStyles.TABLE_W; +import static com.demcha.compose.document.templates.invoice.presets.ObsidianStyles.capGap; +import static com.demcha.compose.document.templates.invoice.presets.ObsidianStyles.capTop; +import static com.demcha.compose.document.templates.invoice.presets.ObsidianStyles.plain; +import static com.demcha.compose.document.templates.invoice.presets.ObsidianStyles.px; +import static com.demcha.compose.document.templates.invoice.presets.ObsidianStyles.topBearing; +import static com.demcha.compose.document.templates.invoice.presets.ObsidianWidgets.rule; + +/** + * The line-item table, inside its own rounded card. + * + *

    Six columns inside one

    + * + *

    A cell's stroke draws all four of its edges, so a six-column table would + * draw five verticals this design does not have. The table is one column wide + * and each cell carries a composed row of six, with the rule under a row drawn + * inside that row's own content — which is also what lets the last row leave the + * rule out and take the card's lower padding instead.

    + */ +final class ObsidianLines { + + private ObsidianLines() { + } + + /** + * The table. + * + * @param page the page flow + * @param serviceLines the lines and their column captions + * @param currencyCode the code every figure carries as a mark + */ + static void render(PageFlowBuilder page, InvoiceServiceLines serviceLines, + String currencyCode) { + List lines = serviceLines.lines(); + page.addSection("LineItemsCard", card -> { + card.spacing(0) + .fillColor(SURFACE) + .stroke(CARD_STROKE) + .cornerRadius(CARD_RADIUS) + .padding(new DocumentInsets(0, TABLE_INSET, 0, TABLE_INSET)) + .margin(new DocumentInsets(px(28), 0, 0, 0)); + card.addTable(table -> { + table.name("LineItems") + .width(TABLE_W) + .columns(DocumentTableColumn.fixed(TABLE_W)) + .defaultCellStyle(CELL_STYLE); + renderHeader(table, serviceLines.columns()); + for (int i = 0; i < lines.size(); i++) { + table.rowCells(DocumentTableCell + .node(lineCell(lines.get(i), i, i == lines.size() - 1, currencyCode)) + .withStyle(CELL_STYLE)); + } + }); + }); + } + + private static void renderHeader(TableBuilder table, InvoiceServiceLines.Columns columns) { + SectionBuilder cell = new SectionBuilder(); + cell.name("LineItemsHeaderCell").spacing(0); + cell.addRow("LineItemsHeader", row -> { + row.spacing(0) + .verticalAlign(RowVerticalAlign.TOP) + .margin(capTop(596 - 573, TABLE_HEAD_SIZE, false)) + .columns(columnWidths()); + headCell(row, "HeadIndex", columns.index(), TextAlign.LEFT); + headCell(row, "HeadDescription", columns.description(), TextAlign.LEFT); + headCell(row, "HeadQuantity", columns.quantity(), TextAlign.CENTER); + headCell(row, "HeadUnitPrice", columns.unitPrice(), TextAlign.RIGHT); + headCell(row, "HeadTax", columns.vat(), TextAlign.RIGHT); + headCell(row, "HeadAmount", columns.amount(), TextAlign.RIGHT); + }); + cell.addLine(line -> rule(line, "LineItemsHeaderRule", TABLE_W, HAIRLINE_STRONG) + .margin(new DocumentInsets( + px(626 - 596) - LINE_BOX * TABLE_HEAD_SIZE + + topBearing(TABLE_HEAD_SIZE, false), + 0, 0, 0))); + table.headerCells(DocumentTableCell.node(cell.build()).withStyle(CELL_STYLE)) + .repeatHeader(); + } + + private static void headCell(RowBuilder row, String name, String label, TextAlign align) { + row.addParagraph(p -> p + .name(name) + .text(label) + .textStyle(plain(TABLE_HEAD_SIZE, MUTED)) + .align(align) + .margin(new DocumentInsets(0, + align == TextAlign.RIGHT ? CELL_PAD_R : 0, 0, + align == TextAlign.LEFT ? CELL_PAD_L : 0))); + } + + private static DocumentNode lineCell(InvoiceServiceLines.Line line, int index, boolean last, + String currencyCode) { + int number = line.lineNumber() > 0 ? line.lineNumber() : index + 1; + SectionBuilder cell = new SectionBuilder(); + cell.name("LineCell_" + index).spacing(0); + cell.addRow("Line_" + index, row -> { + row.spacing(0) + .verticalAlign(RowVerticalAlign.TOP) + // A row's title sits a measured distance below the rule above + // it every time, and the rule's own thickness is part of that. + .margin(new DocumentInsets( + px(18) - RULE_BOX - topBearing(ITEM_TITLE_SIZE, false), 0, 0, 0)) + .columns(columnWidths()); + row.addParagraph(p -> p + .name("LineIndex_" + index) + .text(Integer.toString(number)) + .textStyle(plain(ITEM_TITLE_SIZE, MUTED)) + .margin(new DocumentInsets(0, 0, 0, CELL_PAD_L))); + row.addSection("LineDescription_" + index, text -> { + text.spacing(0).padding(new DocumentInsets(0, 0, 0, CELL_PAD_L)); + text.addParagraph(p -> p + .name("LineTitle_" + index) + .text(line.title()) + .textStyle(plain(ITEM_TITLE_SIZE, INK))); + text.addParagraph(p -> p + .name("LineSubtitle_" + index) + .text(line.description()) + .textStyle(plain(ITEM_SUB_SIZE, MUTED)) + .margin(new DocumentInsets(Math.max(0, capGap( + 669 - 644, ITEM_TITLE_SIZE, false, ITEM_SUB_SIZE, false)), + 0, 0, 0))); + }); + valueCell(row, "LineQuantity_" + index, + ObsidianText.quantity(line.quantity()), TextAlign.CENTER); + valueCell(row, "LineUnitPrice_" + index, + ObsidianText.money(currencyCode, line.unitPrice()), TextAlign.RIGHT); + valueCell(row, "LineTax_" + index, + ObsidianText.lineTax(currencyCode, line.quantity(), line.unitPrice(), + line.amount()), + TextAlign.RIGHT); + valueCell(row, "LineAmount_" + index, + ObsidianText.money(currencyCode, line.amount()), TextAlign.RIGHT); + }); + if (last) { + cell.addSpacer(spacer -> spacer.height(px(983 - 967))); + } else { + cell.addLine(line2 -> rule(line2, "LineRule_" + index, TABLE_W, HAIRLINE) + .margin(new DocumentInsets( + Math.max(0, px(697.5 - 669) - LINE_BOX * ITEM_SUB_SIZE + + topBearing(ITEM_SUB_SIZE, false)), + 0, 0, 0))); + } + return cell.build(); + } + + private static void valueCell(RowBuilder row, String name, String value, TextAlign align) { + row.addParagraph(p -> p + .name(name) + .text(value) + .textStyle(plain(FIGURE_SIZE, INK)) + .align(align) + .margin(new DocumentInsets(0, align == TextAlign.RIGHT ? CELL_PAD_R : 0, 0, 0))); + } + + /** The six widths, with the last taking the remainder so they sum exactly. */ + private static DocumentRowColumn[] columnWidths() { + DocumentRowColumn[] columns = new DocumentRowColumn[COLUMN_SHARES.length]; + double assigned = 0; + for (int i = 0; i < COLUMN_SHARES.length - 1; i++) { + double width = TABLE_W * COLUMN_SHARES[i]; + assigned += width; + columns[i] = DocumentRowColumn.fixed(width); + } + columns[COLUMN_SHARES.length - 1] = DocumentRowColumn.fixed(TABLE_W - assigned); + return columns; + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/ObsidianMasthead.java b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/ObsidianMasthead.java new file mode 100644 index 000000000..a3bf240bb --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/ObsidianMasthead.java @@ -0,0 +1,141 @@ +package com.demcha.compose.document.templates.invoice.presets; + +import com.demcha.compose.document.dsl.PageFlowBuilder; +import com.demcha.compose.document.dsl.SectionBuilder; +import com.demcha.compose.document.node.RowVerticalAlign; +import com.demcha.compose.document.node.TextAlign; +import com.demcha.compose.document.style.DocumentInsets; +import com.demcha.compose.document.style.DocumentRowColumn; +import com.demcha.compose.document.templates.data.invoice.InvoiceBrand; +import com.demcha.compose.document.templates.data.invoice.InvoiceMasthead; + +import java.util.List; + +import static com.demcha.compose.document.templates.invoice.presets.ObsidianStyles.ACCENT; +import static com.demcha.compose.document.templates.invoice.presets.ObsidianStyles.HAIRLINE; +import static com.demcha.compose.document.templates.invoice.presets.ObsidianStyles.HALF; +import static com.demcha.compose.document.templates.invoice.presets.ObsidianStyles.INK; +import static com.demcha.compose.document.templates.invoice.presets.ObsidianStyles.LINE_BOX; +import static com.demcha.compose.document.templates.invoice.presets.ObsidianStyles.META_COL_W; +import static com.demcha.compose.document.templates.invoice.presets.ObsidianStyles.META_LABEL_SIZE; +import static com.demcha.compose.document.templates.invoice.presets.ObsidianStyles.META_NUMBER_SIZE; +import static com.demcha.compose.document.templates.invoice.presets.ObsidianStyles.META_VALUE_SIZE; +import static com.demcha.compose.document.templates.invoice.presets.ObsidianStyles.MUTED; +import static com.demcha.compose.document.templates.invoice.presets.ObsidianStyles.RULE_BOX; +import static com.demcha.compose.document.templates.invoice.presets.ObsidianStyles.TITLE_SIZE; +import static com.demcha.compose.document.templates.invoice.presets.ObsidianStyles.WORDMARK_SIZE; +import static com.demcha.compose.document.templates.invoice.presets.ObsidianStyles.bold; +import static com.demcha.compose.document.templates.invoice.presets.ObsidianStyles.capGap; +import static com.demcha.compose.document.templates.invoice.presets.ObsidianStyles.capTop; +import static com.demcha.compose.document.templates.invoice.presets.ObsidianStyles.plain; +import static com.demcha.compose.document.templates.invoice.presets.ObsidianStyles.px; +import static com.demcha.compose.document.templates.invoice.presets.ObsidianStyles.topBearing; +import static com.demcha.compose.document.templates.invoice.presets.ObsidianWidgets.layeredRow; +import static com.demcha.compose.document.templates.invoice.presets.ObsidianWidgets.rule; + +/** + * The head of the sheet: the wordmark over the document's name on the left, + * against a metadata stack on the right whose rows sit under their own rules. + */ +final class ObsidianMasthead { + + /** + * The first row's number stands alone above the ruled rows, so the stack has + * one more measured position than it has entries. Rule tops and row cap tops, + * both measured: the pitch is even to within a pixel, but stating each keeps + * them separable if one moves. + */ + private static final double[] RULE_AT = {117.5, 160.5, 199.5, 239.5}; + private static final double[] ROW_AT = {135, 175, 215, 255}; + + private ObsidianMasthead() { + } + + /** + * The masthead. + * + *

    The brand's name is the wordmark — set, not drawn, because the design + * sets it as type. A caller whose mark is a drawing puts it on the party disc + * below instead, which is where this design carries one.

    + */ + static void render(PageFlowBuilder page, InvoiceBrand brand, InvoiceMasthead masthead) { + page.addRow("Masthead", row -> { + row.spacing(0) + .verticalAlign(RowVerticalAlign.TOP) + .columns(DocumentRowColumn.weight(1), DocumentRowColumn.fixed(META_COL_W)); + row.addSection("BrandColumn", cell -> renderBrand(cell, brand, masthead)); + row.addSection("InvoiceMeta", cell -> renderMeta(cell, masthead)); + }); + } + + private static void renderBrand(SectionBuilder cell, InvoiceBrand brand, + InvoiceMasthead masthead) { + cell.spacing(0); + cell.addParagraph(p -> p + .name("BrandWordmark") + .text(brand.name()) + .textStyle(bold(WORDMARK_SIZE, INK)) + .margin(capTop(1, WORDMARK_SIZE, true))); + cell.addParagraph(p -> p + .name("InvoiceTitle") + .text(masthead.title()) + .textStyle(bold(TITLE_SIZE, INK)) + // Measured from the content box's top, less the wordmark's own + // line box, which is what the title is being placed under. + .margin(capTop(195 - 46, TITLE_SIZE, true, LINE_BOX * WORDMARK_SIZE))); + } + + /** + * The metadata stack. + * + *

    The first entry is the one the design sets apart: its label sits above + * its value in the accent and neither carries a rule, so an invoice whose + * first entry is its number reads as a heading for the stack rather than as + * its first row. The rest are ruled label/value pairs.

    + */ + private static void renderMeta(SectionBuilder cell, InvoiceMasthead masthead) { + List entries = masthead.entries(); + cell.spacing(0); + if (entries.isEmpty()) { + return; + } + InvoiceMasthead.Entry lead = entries.get(0); + cell.addParagraph(p -> p + .name("MetaNumberLabel") + .text(lead.label()) + .textStyle(plain(META_LABEL_SIZE, MUTED)) + .margin(capTop(52 - 46, META_LABEL_SIZE, false))); + cell.addParagraph(p -> p + .name("MetaNumber") + .text(lead.value()) + .textStyle(bold(META_NUMBER_SIZE, ACCENT)) + .margin(capTop(82 - 52, META_NUMBER_SIZE, false, LINE_BOX * META_LABEL_SIZE))); + + for (int i = 1; i < entries.size() && i <= RULE_AT.length; i++) { + InvoiceMasthead.Entry entry = entries.get(i); + int at = i - 1; + String name = "MetaRow_" + at; + double gapAboveRule = at == 0 + ? capGap(RULE_AT[0] - 82, META_NUMBER_SIZE, true, 0, false) + : capGap(RULE_AT[at] - ROW_AT[at - 1], META_VALUE_SIZE, false, 0, false); + cell.addLine(line -> rule(line, name + "Rule", META_COL_W, HAIRLINE) + .margin(new DocumentInsets(Math.max(0, gapAboveRule), 0, 0, 0))); + double gapBelowRule = + px(ROW_AT[at] - RULE_AT[at]) - RULE_BOX - topBearing(META_VALUE_SIZE, false); + layeredRow(cell, name, row -> { + row.spacing(0) + .margin(new DocumentInsets(Math.max(0, gapBelowRule), 0, 0, 0)) + .weights(HALF, HALF); + row.addParagraph(p -> p + .name(name + "Label") + .text(entry.label()) + .textStyle(plain(META_VALUE_SIZE, MUTED))); + row.addParagraph(p -> p + .name(name + "Value") + .text(entry.value()) + .textStyle(plain(META_VALUE_SIZE, entry.emphasized() ? ACCENT : INK)) + .align(TextAlign.RIGHT)); + }); + } + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/ObsidianParties.java b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/ObsidianParties.java new file mode 100644 index 000000000..5861cb58e --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/ObsidianParties.java @@ -0,0 +1,166 @@ +package com.demcha.compose.document.templates.invoice.presets; + +import com.demcha.compose.document.dsl.ImageBuilder; +import com.demcha.compose.document.dsl.PageFlowBuilder; +import com.demcha.compose.document.dsl.SectionBuilder; +import com.demcha.compose.document.node.DocumentLinkOptions; +import com.demcha.compose.document.node.DocumentNode; +import com.demcha.compose.document.node.RowVerticalAlign; +import com.demcha.compose.document.style.DocumentColor; +import com.demcha.compose.document.style.DocumentInsets; +import com.demcha.compose.document.style.DocumentRowColumn; +import com.demcha.compose.document.templates.core.identity.ContactUri; +import com.demcha.compose.document.templates.data.invoice.InvoiceBrand; +import com.demcha.compose.document.templates.data.invoice.InvoiceContactBlock; +import com.demcha.compose.document.templates.data.invoice.InvoiceRecipient; + +import java.util.List; + +import static com.demcha.compose.document.templates.invoice.presets.ObsidianStyles.ACCENT; +import static com.demcha.compose.document.templates.invoice.presets.ObsidianStyles.BODY_SIZE; +import static com.demcha.compose.document.templates.invoice.presets.ObsidianStyles.CARD_GUTTER; +import static com.demcha.compose.document.templates.invoice.presets.ObsidianStyles.CARD_PAD_H; +import static com.demcha.compose.document.templates.invoice.presets.ObsidianStyles.CARD_RADIUS; +import static com.demcha.compose.document.templates.invoice.presets.ObsidianStyles.CARD_STROKE; +import static com.demcha.compose.document.templates.invoice.presets.ObsidianStyles.DISC_CLIENT; +import static com.demcha.compose.document.templates.invoice.presets.ObsidianStyles.DISC_D; +import static com.demcha.compose.document.templates.invoice.presets.ObsidianStyles.DISC_SUPPLIER; +import static com.demcha.compose.document.templates.invoice.presets.ObsidianStyles.HALF; +import static com.demcha.compose.document.templates.invoice.presets.ObsidianStyles.INK; +import static com.demcha.compose.document.templates.invoice.presets.ObsidianStyles.MUTED; +import static com.demcha.compose.document.templates.invoice.presets.ObsidianStyles.PARTY_LABEL_SIZE; +import static com.demcha.compose.document.templates.invoice.presets.ObsidianStyles.PARTY_NAME_SIZE; +import static com.demcha.compose.document.templates.invoice.presets.ObsidianStyles.PARTY_TEXT_INDENT; +import static com.demcha.compose.document.templates.invoice.presets.ObsidianStyles.SURFACE; +import static com.demcha.compose.document.templates.invoice.presets.ObsidianStyles.bold; +import static com.demcha.compose.document.templates.invoice.presets.ObsidianStyles.capGap; +import static com.demcha.compose.document.templates.invoice.presets.ObsidianStyles.capTop; +import static com.demcha.compose.document.templates.invoice.presets.ObsidianStyles.plain; +import static com.demcha.compose.document.templates.invoice.presets.ObsidianStyles.px; +import static com.demcha.compose.document.templates.invoice.presets.ObsidianWidgets.initials; +import static com.demcha.compose.document.templates.invoice.presets.ObsidianWidgets.initialsDisc; +import static com.demcha.compose.document.templates.invoice.presets.ObsidianWidgets.layeredRow; + +/** + * The two party cards: the issuer beside the billed party, each opening with a + * filled disc. + */ +final class ObsidianParties { + + /** The address lines' measured cap tops; the first is taken off the name. */ + private static final double[] LINE_AT = {420, 446, 471}; + + private ObsidianParties() { + } + + /** + * The parties row. + * + * @param page the page flow + * @param brand the issuer's brand, whose logo the supplier's disc carries + * @param supplier the issuer + * @param billTo the billed party + */ + static void render(PageFlowBuilder page, InvoiceBrand brand, + InvoiceContactBlock supplier, InvoiceRecipient billTo) { + page.addRow("PartiesRow", row -> { + row.spacing(CARD_GUTTER).verticalAlign(RowVerticalAlign.TOP).weights(HALF, HALF); + row.addSection("SupplierCard", cell -> renderCard(cell, "Supplier", "From", + supplierDisc(brand, supplier), supplier.legalName(), + supplier.addressLines(), supplier.email(), + ContactUri.mailLink(supplier.email()))); + row.addSection("BillToCard", cell -> renderCard(cell, "BillTo", billTo.heading(), + initialsDisc("BillToDisc", DISC_CLIENT, initials(billTo.name())), + billTo.name(), billTo.addressLines(), billTo.email(), + ContactUri.mailLink(billTo.email()))); + }); + } + + /** + * The issuer's disc: the caller's logo when there is one, the brand's own + * monogram when it states one, and initials taken from the name otherwise. + * + *

    This design carries the issuer's mark here rather than in the masthead, + * where it sets the name as type instead — so a caller with a drawing to show + * has exactly one place to put it.

    + */ + private static DocumentNode supplierDisc(InvoiceBrand brand, InvoiceContactBlock supplier) { + if (brand.logo() != null) { + return ObsidianWidgets.disc("SupplierDisc", DISC_SUPPLIER, + new ImageBuilder() + .name("SupplierDiscLogo") + .source(brand.logo()) + .width(ObsidianStyles.DISC_GLYPH) + .build(), + DISC_D); + } + String monogram = (brand.monogramTop() + brand.monogramBottom()).trim(); + String letters = monogram.isBlank() + ? initials(brand.name().isBlank() ? supplier.legalName() : brand.name()) + : monogram; + return initialsDisc("SupplierDisc", DISC_SUPPLIER, letters); + } + + private static void renderCard(SectionBuilder cell, String id, String label, + DocumentNode disc, String name, List addressLines, + String contact, DocumentLinkOptions link) { + cell.spacing(0) + .fillColor(SURFACE) + .stroke(CARD_STROKE) + .cornerRadius(CARD_RADIUS) + .padding(new DocumentInsets(0, CARD_PAD_H, px(26), CARD_PAD_H)) + .keepTogether(); + cell.addParagraph(p -> p + .name(id + "Label") + .text(label) + .textStyle(bold(PARTY_LABEL_SIZE, ACCENT)) + .margin(capTop(355 - 325, PARTY_LABEL_SIZE, true))); + layeredRow(cell, id + "Body", row -> { + row.spacing(0) + .verticalAlign(RowVerticalAlign.TOP) + .margin(new DocumentInsets( + capGap(388 - 355, PARTY_LABEL_SIZE, true, 0, false), 0, 0, 0)) + .columns(DocumentRowColumn.fixed(PARTY_TEXT_INDENT), + DocumentRowColumn.weight(1)); + row.addSection(id + "Disc", cell2 -> cell2.spacing(0).add(disc)); + row.addSection(id + "Text", text -> { + text.spacing(0); + text.addParagraph(p -> p + .name(id + "Name") + .text(name) + .textStyle(bold(PARTY_NAME_SIZE, INK)) + .margin(capTop(392 - 388, PARTY_NAME_SIZE, true))); + for (int i = 0; i < addressLines.size() && i < LINE_AT.length; i++) { + String line = addressLines.get(i); + int index = i; + double above = index == 0 + ? capGap(LINE_AT[0] - 392, PARTY_NAME_SIZE, true, BODY_SIZE, false) + : capGap(LINE_AT[index] - LINE_AT[index - 1], BODY_SIZE, false, + BODY_SIZE, false); + double gapAbove = Math.max(0, above); + text.addParagraph(p -> p + .name(id + "AddressLine_" + index) + .text(line) + .textStyle(plain(BODY_SIZE, MUTED)) + .margin(new DocumentInsets(gapAbove, 0, 0, 0))); + } + if (!contact.isBlank()) { + text.addParagraph(p -> { + p.name(id + "Contact").margin(new DocumentInsets(Math.max(0, + capGap(504 - 471, BODY_SIZE, false, BODY_SIZE, false)), 0, 0, 0)); + if (link == null) { + p.text(contact).textStyle(plain(BODY_SIZE, MUTED)); + } else { + p.inlineText(contact, plain(BODY_SIZE, MUTED), link); + } + }); + } + }); + }); + } + + /** The disc colour the issuer's card uses, for the closing band to match. */ + static DocumentColor supplierTone() { + return DISC_SUPPLIER; + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/ObsidianSettlement.java b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/ObsidianSettlement.java new file mode 100644 index 000000000..ddd926bd3 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/ObsidianSettlement.java @@ -0,0 +1,141 @@ +package com.demcha.compose.document.templates.invoice.presets; + +import com.demcha.compose.document.dsl.PageFlowBuilder; +import com.demcha.compose.document.dsl.SectionBuilder; +import com.demcha.compose.document.node.RowVerticalAlign; +import com.demcha.compose.document.node.TextAlign; +import com.demcha.compose.document.style.DocumentInsets; +import com.demcha.compose.document.templates.data.invoice.InvoiceTotalsBlock; + +import java.util.List; + +import static com.demcha.compose.document.templates.invoice.presets.ObsidianStyles.ACCENT; +import static com.demcha.compose.document.templates.invoice.presets.ObsidianStyles.CARD_GUTTER; +import static com.demcha.compose.document.templates.invoice.presets.ObsidianStyles.CARD_RADIUS; +import static com.demcha.compose.document.templates.invoice.presets.ObsidianStyles.CARD_STROKE; +import static com.demcha.compose.document.templates.invoice.presets.ObsidianStyles.CONTENT_W; +import static com.demcha.compose.document.templates.invoice.presets.ObsidianStyles.HAIRLINE; +import static com.demcha.compose.document.templates.invoice.presets.ObsidianStyles.HALF; +import static com.demcha.compose.document.templates.invoice.presets.ObsidianStyles.INK; +import static com.demcha.compose.document.templates.invoice.presets.ObsidianStyles.MUTED; +import static com.demcha.compose.document.templates.invoice.presets.ObsidianStyles.RULE_BOX; +import static com.demcha.compose.document.templates.invoice.presets.ObsidianStyles.SURFACE; +import static com.demcha.compose.document.templates.invoice.presets.ObsidianStyles.TOTALS_CARD_BOTTOM; +import static com.demcha.compose.document.templates.invoice.presets.ObsidianStyles.TOTALS_CARD_TOP; +import static com.demcha.compose.document.templates.invoice.presets.ObsidianStyles.TOTALS_DUE_AT; +import static com.demcha.compose.document.templates.invoice.presets.ObsidianStyles.TOTALS_LABEL_SIZE; +import static com.demcha.compose.document.templates.invoice.presets.ObsidianStyles.TOTALS_PAD_L; +import static com.demcha.compose.document.templates.invoice.presets.ObsidianStyles.TOTALS_PAD_R; +import static com.demcha.compose.document.templates.invoice.presets.ObsidianStyles.TOTALS_ROW_AT; +import static com.demcha.compose.document.templates.invoice.presets.ObsidianStyles.TOTALS_RULE_AT; +import static com.demcha.compose.document.templates.invoice.presets.ObsidianStyles.TOTALS_RULE_OVERHANG; +import static com.demcha.compose.document.templates.invoice.presets.ObsidianStyles.TOTALS_VALUE_SIZE; +import static com.demcha.compose.document.templates.invoice.presets.ObsidianStyles.TOTAL_DUE_LABEL_SIZE; +import static com.demcha.compose.document.templates.invoice.presets.ObsidianStyles.TOTAL_DUE_VALUE_SIZE; +import static com.demcha.compose.document.templates.invoice.presets.ObsidianStyles.bold; +import static com.demcha.compose.document.templates.invoice.presets.ObsidianStyles.capGap; +import static com.demcha.compose.document.templates.invoice.presets.ObsidianStyles.plain; +import static com.demcha.compose.document.templates.invoice.presets.ObsidianStyles.px; +import static com.demcha.compose.document.templates.invoice.presets.ObsidianStyles.topBearing; +import static com.demcha.compose.document.templates.invoice.presets.ObsidianWidgets.layeredRow; +import static com.demcha.compose.document.templates.invoice.presets.ObsidianWidgets.rule; + +/** + * The totals card, on the right half of the sheet under the table. + */ +final class ObsidianSettlement { + + private ObsidianSettlement() { + } + + /** + * The totals row. + * + * @param page the page flow + * @param totals the summed rows and the grand total + * @param currencyCode the code every figure carries as a mark + */ + static void render(PageFlowBuilder page, InvoiceTotalsBlock totals, String currencyCode) { + page.addRow("TotalsRow", row -> { + row.spacing(CARD_GUTTER).verticalAlign(RowVerticalAlign.TOP).weights(HALF, HALF); + row.addSection("TotalsSpacer", cell -> cell.spacing(0)); + row.addSection("TotalsCard", cell -> renderCard(cell, totals, currencyCode)); + }); + } + + private static void renderCard(SectionBuilder cell, InvoiceTotalsBlock totals, + String currencyCode) { + cell.spacing(0) + .fillColor(SURFACE) + .stroke(CARD_STROKE) + .cornerRadius(CARD_RADIUS) + .padding(new DocumentInsets(0, TOTALS_PAD_R, + Math.max(0, capGap(TOTALS_CARD_BOTTOM - TOTALS_DUE_AT, + TOTAL_DUE_VALUE_SIZE, true, 0, false)), + TOTALS_PAD_L)) + .margin(new DocumentInsets(px(13), 0, 0, 0)) + .keepTogether(); + + List rows = totals.rows(); + for (int i = 0; i < rows.size(); i++) { + InvoiceTotalsBlock.Row entry = rows.get(i); + int index = i; + String name = "TotalsRow_" + index; + double above = index == 0 + ? px(TOTALS_ROW_AT[0] - TOTALS_CARD_TOP) + - topBearing(TOTALS_LABEL_SIZE, false) + : capGap(TOTALS_ROW_AT[1] - TOTALS_ROW_AT[0], + TOTALS_VALUE_SIZE, false, TOTALS_VALUE_SIZE, false); + double gapAbove = Math.max(0, above); + layeredRow(cell, name, row -> { + row.spacing(0) + .margin(new DocumentInsets(gapAbove, 0, 0, 0)) + .weights(HALF, HALF); + row.addParagraph(p -> p + .name(name + "Label") + .text(entry.label()) + .textStyle(plain(TOTALS_LABEL_SIZE, MUTED))); + row.addParagraph(p -> p + .name(name + "Value") + .text(ObsidianText.money(currencyCode, entry.amount())) + .textStyle(plain(TOTALS_VALUE_SIZE, INK)) + .align(TextAlign.RIGHT)); + }); + } + + // Wider than the text it separates, which is the design's own + // arrangement: the card is padded to the text, and the rule reaches back + // out to the card's inner edge on both sides. + cell.addLine(line -> rule(line, "TotalsRule", + innerWidth() + 2 * TOTALS_RULE_OVERHANG, HAIRLINE) + .margin(new DocumentInsets( + Math.max(0, capGap(TOTALS_RULE_AT - TOTALS_ROW_AT[1], + TOTALS_VALUE_SIZE, false, 0, false)), + -TOTALS_RULE_OVERHANG, 0, -TOTALS_RULE_OVERHANG))); + + layeredRow(cell, "TotalDue", row -> { + row.spacing(0) + // Centred, not baseline-aligned: the label's ink band and the + // larger value's share a centre line, measured. + .verticalAlign(RowVerticalAlign.CENTER) + .margin(new DocumentInsets( + Math.max(0, px(TOTALS_DUE_AT - TOTALS_RULE_AT) - RULE_BOX + - topBearing(TOTAL_DUE_VALUE_SIZE, true)), + 0, 0, 0)) + .weights(HALF, HALF); + row.addParagraph(p -> p + .name("TotalDueLabel") + .text(totals.totalLabel()) + .textStyle(bold(TOTAL_DUE_LABEL_SIZE, INK))); + row.addParagraph(p -> p + .name("TotalDueValue") + .text(ObsidianText.money(currencyCode, totals.totalAmount())) + .textStyle(bold(TOTAL_DUE_VALUE_SIZE, ACCENT)) + .align(TextAlign.RIGHT)); + }); + } + + private static double innerWidth() { + return (CONTENT_W - CARD_GUTTER) * HALF - TOTALS_PAD_L - TOTALS_PAD_R; + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/ObsidianStyles.java b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/ObsidianStyles.java new file mode 100644 index 000000000..21b176ec9 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/ObsidianStyles.java @@ -0,0 +1,250 @@ +package com.demcha.compose.document.templates.invoice.presets; + +import com.demcha.compose.document.api.DocumentPageSize; +import com.demcha.compose.document.style.DocumentColor; +import com.demcha.compose.document.style.DocumentInsets; +import com.demcha.compose.document.style.DocumentStroke; +import com.demcha.compose.document.style.DocumentTextDecoration; +import com.demcha.compose.document.style.DocumentTextStyle; +import com.demcha.compose.document.table.DocumentTableStyle; +import com.demcha.compose.font.FontName; + +/** + * Every measurement, colour and type size of the Obsidian invoice. + * + *

    Sizes are solved from ink, not from cap height

    + * + *

    The other presets in this family size their type from a measured cap + * height. This design's own face is not bundled, so a cap-height match would + * still set every string to the wrong width; here each size is solved from the + * measured ink width of the string it sets, and the substitute face's + * own width ratio is applied once. Every size below is named by the string it + * was solved from.

    + * + *

    Positions are cap tops, not box edges

    + * + *

    The design can be measured on where ink starts, and a text box begins above + * its cap band by the face's top bearing. {@link #capTop} and {@link #capGap} + * convert a measured cap position into the margin that puts it there, so the + * numbers in the preset stay the numbers that were measured.

    + */ +final class ObsidianStyles { + + private ObsidianStyles() { + } + + // ------------------------------------------------------------------ + // The page + // ------------------------------------------------------------------ + + static final DocumentPageSize PAGE = DocumentPageSize.A4; + + private static final double PAGE_W = PAGE.width(); + private static final double DESIGN_PX_WIDTH = 1055.0; + private static final double PX = PAGE_W / DESIGN_PX_WIDTH; + + static double px(double designPixels) { + return designPixels * PX; + } + + static final double MARGIN_T = px(46); + static final double MARGIN_L = px(46); + static final double MARGIN_R = px(48); + static final double MARGIN_B_FIRST = px(24); + static final double MARGIN_B_LATER = px(58); + static final double PAGE_NUMBER_BAND = px(20); + + static final DocumentInsets PAGE_MARGIN = + new DocumentInsets(MARGIN_T, MARGIN_R, MARGIN_B_FIRST, MARGIN_L); + + /** + * A continuation page reserves more at the foot than the first, because the + * first page's last block is the closing band and a continuation page's is a + * table row, which would otherwise sit on the enumeration. + */ + static final DocumentInsets CONTINUATION_MARGIN = + new DocumentInsets(MARGIN_T, MARGIN_R, MARGIN_B_LATER, MARGIN_L); + + static final double CONTENT_W = PAGE_W - MARGIN_L - MARGIN_R; + static final double CARD_GUTTER = px(38); + static final double HALF = 0.5; + + // ------------------------------------------------------------------ + // Colour — a dark sheet + // ------------------------------------------------------------------ + + static final DocumentColor PAGE_BG = DocumentColor.rgb(22, 23, 26); + static final DocumentColor SURFACE = DocumentColor.rgb(27, 28, 32); + static final DocumentColor SURFACE_BORDER = DocumentColor.rgb(38, 39, 43); + static final DocumentColor HAIRLINE = DocumentColor.rgb(42, 43, 48); + static final DocumentColor HAIRLINE_STRONG = DocumentColor.rgb(88, 94, 102); + static final DocumentColor INK = DocumentColor.WHITE; + static final DocumentColor MUTED = DocumentColor.rgb(174, 178, 185); + static final DocumentColor ACCENT = DocumentColor.rgb(127, 140, 255); + static final DocumentColor DISC_SUPPLIER = DocumentColor.rgb(114, 80, 226); + static final DocumentColor DISC_CLIENT = DocumentColor.rgb(79, 105, 216); + static final DocumentColor DISC_CLOSING = DocumentColor.rgb(110, 120, 250); + + // ------------------------------------------------------------------ + // Type + // ------------------------------------------------------------------ + + /** + * The design's own face is not bundled. Gothic A1 is the family whose widths + * the sizes below are solved against; the templates artifact carries no fonts + * — register it on the session, or the engine substitutes and every size is + * solved for type that is not there. + */ + static final FontName FACE = FontName.GOTHIC_A1; + + /** The substitute's ink width against the design's, measured across the sheet. */ + private static final double FACE_WIDTH_SPLIT = 0.98; + + /** Gothic A1's line box, ascender, descender and cap heights, read off the TTFs. */ + static final double LINE_BOX = 1.0; + private static final double FACE_ASCENT = 0.7979; + private static final double FACE_DESCENT = 0.2021; + private static final double CAP_REGULAR = 0.7314; + private static final double CAP_BOLD = 0.7725; + private static final double TOP_BEARING_REGULAR = FACE_ASCENT - CAP_REGULAR; + private static final double TOP_BEARING_BOLD = FACE_ASCENT - CAP_BOLD; + + /** A size whose ink reproduces a string measured at {@code solvedDesignPx}. */ + static double sizeForInk(double solvedDesignPx) { + return px(solvedDesignPx * FACE_WIDTH_SPLIT); + } + + static double topBearing(double size, boolean bold) { + return (bold ? TOP_BEARING_BOLD : TOP_BEARING_REGULAR) * size; + } + + static double bottomBearing(double size) { + return FACE_DESCENT * size; + } + + /** The margin that puts a cap top at a measured design y. */ + static DocumentInsets capTop(double capPx, double size, boolean bold) { + return capTop(capPx, size, bold, 0); + } + + /** The same, under a box of a stated height. */ + static DocumentInsets capTop(double capPx, double size, boolean bold, + double precedingBoxHeight) { + return new DocumentInsets( + px(capPx) - topBearing(size, bold) - precedingBoxHeight, 0, 0, 0); + } + + /** The gap that puts the next cap top {@code deltaPx} below the previous one. */ + static double capGap(double deltaPx, double sizeAbove, boolean boldAbove, + double sizeBelow, boolean boldBelow) { + return px(deltaPx) - LINE_BOX * sizeAbove + topBearing(sizeAbove, boldAbove) + - topBearing(sizeBelow, boldBelow); + } + + /** The gap between two blocks, measured between their ink rather than their boxes. */ + static double blockGap(double inkGapPx, double sizeAbove, double sizeBelow) { + return px(inkGapPx) + - (sizeAbove > 0 ? bottomBearing(sizeAbove) : 0) + - (sizeBelow > 0 ? topBearing(sizeBelow, false) : 0); + } + + // Every size is named by the string it was solved from. + static final double WORDMARK_SIZE = sizeForInk(47.5); + static final double TITLE_SIZE = sizeForInk(74.7); + static final double META_LABEL_SIZE = sizeForInk(14.7); + static final double META_NUMBER_SIZE = px(19.9); + static final double META_VALUE_SIZE = sizeForInk(16.6); + static final double PARTY_LABEL_SIZE = sizeForInk(16.1); + static final double PARTY_NAME_SIZE = sizeForInk(17.4); + static final double BODY_SIZE = sizeForInk(17.2); + static final double TABLE_HEAD_SIZE = sizeForInk(16.0); + static final double ITEM_TITLE_SIZE = sizeForInk(16.7); + static final double ITEM_SUB_SIZE = sizeForInk(15.7); + static final double FIGURE_SIZE = sizeForInk(16.9); + static final double TOTALS_LABEL_SIZE = sizeForInk(16.0); + + /** The totals figures are measurably wider than the table's. */ + static final double TOTALS_VALUE_SIZE = sizeForInk(18.3); + + static final double TOTAL_DUE_LABEL_SIZE = sizeForInk(17.5); + static final double TOTAL_DUE_VALUE_SIZE = sizeForInk(32.2); + static final double CARD_HEAD_SIZE = sizeForInk(15.6); + static final double CARD_BODY_SIZE = sizeForInk(15.2); + static final double PAY_LABEL_SIZE = sizeForInk(14.8); + static final double PAY_VALUE_SIZE = sizeForInk(15.5); + static final double CLOSING_SIZE = sizeForInk(15.2); + static final double CLOSING_RIGHT_SIZE = sizeForInk(16.1); + static final double PAGE_NUMBER_SIZE = px(12); + + static DocumentTextStyle style(double size, DocumentColor color, + DocumentTextDecoration decoration) { + return DocumentTextStyle.builder() + .fontName(FACE) + .size(size) + .color(color) + .decoration(decoration) + .build(); + } + + static DocumentTextStyle plain(double size, DocumentColor color) { + return style(size, color, DocumentTextDecoration.DEFAULT); + } + + static DocumentTextStyle bold(double size, DocumentColor color) { + return style(size, color, DocumentTextDecoration.BOLD); + } + + // ------------------------------------------------------------------ + // Rules, cards and discs + // ------------------------------------------------------------------ + + static final double RULE_THIN = px(1.2); + + /** A rule asked for less than a point is laid out as one. */ + static final double RULE_BOX = 1.0; + + static final double CARD_RADIUS = px(12); + static final double CARD_PAD_H = px(24); + static final double CARD_BORDER = px(1.1); + static final DocumentStroke CARD_STROKE = DocumentStroke.of(SURFACE_BORDER, CARD_BORDER); + + static final double DISC_D = px(63); + static final double DISC_GLYPH = px(29); + static final double PARTY_TEXT_INDENT = px(111); + static final double META_COL_W = px(385); + + static final double TOTALS_CARD_TOP = 996; + static final double TOTALS_CARD_BOTTOM = 1142; + static final double[] TOTALS_ROW_AT = {1014, 1048}; + static final double TOTALS_RULE_AT = 1078.5; + static final double TOTALS_DUE_AT = 1097; + + /** The totals card is padded to its text, and the rule reaches back out past it. */ + static final double TOTALS_RULE_OVERHANG = px(7); + static final double TOTALS_PAD_L = px(17) + TOTALS_RULE_OVERHANG; + static final double TOTALS_PAD_R = px(23) + TOTALS_RULE_OVERHANG; + + static final double CARD_ICON_COL = px(117 - 46); + static final double CARD_ICON = px(25); + + // ------------------------------------------------------------------ + // The line-item table + // ------------------------------------------------------------------ + + static final double TABLE_INSET = px(22); + static final double TABLE_W = CONTENT_W - 2 * TABLE_INSET; + static final double[] COLUMN_SHARES = {0.0578, 0.4177, 0.0763, 0.1527, 0.1527, 0.1428}; + static final double CELL_PAD_L = px(2); + static final double CELL_PAD_R = px(10); + + /** + * The table's cells fill and stroke in the card's own surface colour, so the + * table reads as part of the card rather than as a grid drawn on it. The rules + * the design does show are drawn inside each row's own content. + */ + static final DocumentTableStyle CELL_STYLE = DocumentTableStyle.builder() + .fillColor(SURFACE) + .stroke(DocumentStroke.of(SURFACE, RULE_THIN)) + .padding(DocumentInsets.zero()) + .build(); +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/ObsidianText.java b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/ObsidianText.java new file mode 100644 index 000000000..50c01ee03 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/ObsidianText.java @@ -0,0 +1,83 @@ +package com.demcha.compose.document.templates.invoice.presets; + +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.text.NumberFormat; +import java.util.Currency; +import java.util.Locale; + +/** + * How the Obsidian invoice writes its figures, and the one figure it works out + * for itself. + * + *

    Money is written with the currency's mark against the digits, so every + * figure names its own currency and no column states one. The locale is stated + * rather than inherited, so the same data renders the same sheet on any + * machine.

    + */ +final class ObsidianText { + + private ObsidianText() { + } + + /** + * An amount behind its currency mark. + * + * @param currencyCode the code the invoice is billed in + * @param value the amount + * @return the amount as the sheet prints it + */ + static String money(String currencyCode, BigDecimal value) { + return mark(currencyCode) + amount(value); + } + + /** + * The tax carried on one line. + * + *

    This design gives the tax its own money column, where the model carries + * a rate. The figure is not a second thing to state: a line already gives the + * quantity, the unit price and the total it comes to, so the tax is the + * difference between what was charged and what the goods cost. Reading it off + * the line rather than asking for it again is also what keeps the column and + * the total consistent when either moves.

    + * + * @param currencyCode the code the invoice is billed in + * @param quantity how much was delivered + * @param unitPrice the price per unit + * @param amount the line total + * @return the tax as the sheet prints it + */ + static String lineTax(String currencyCode, BigDecimal quantity, BigDecimal unitPrice, + BigDecimal amount) { + BigDecimal net = quantity.multiply(unitPrice); + BigDecimal tax = amount.subtract(net).max(BigDecimal.ZERO); + return money(currencyCode, tax.setScale(2, RoundingMode.HALF_UP)); + } + + /** A count, bare, with no fraction when it has none. */ + static String quantity(BigDecimal value) { + BigDecimal stripped = value.stripTrailingZeros(); + return stripped.scale() <= 0 + ? stripped.toBigInteger().toString() + : stripped.toPlainString(); + } + + private static String mark(String currencyCode) { + if (currencyCode == null || currencyCode.isBlank()) { + return ""; + } + try { + return Currency.getInstance(currencyCode.trim()).getSymbol(Locale.ENGLISH); + } catch (IllegalArgumentException notACurrency) { + return currencyCode.trim(); + } + } + + private static String amount(BigDecimal value) { + NumberFormat format = NumberFormat.getNumberInstance(Locale.ENGLISH); + format.setGroupingUsed(true); + format.setMinimumFractionDigits(2); + format.setMaximumFractionDigits(2); + return format.format(value); + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/ObsidianWidgets.java b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/ObsidianWidgets.java new file mode 100644 index 000000000..bdb9dd1af --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/ObsidianWidgets.java @@ -0,0 +1,122 @@ +package com.demcha.compose.document.templates.invoice.presets; + +import com.demcha.compose.document.dsl.LineBuilder; +import com.demcha.compose.document.dsl.ParagraphBuilder; +import com.demcha.compose.document.dsl.RowBuilder; +import com.demcha.compose.document.dsl.SectionBuilder; +import com.demcha.compose.document.dsl.ShapeContainerBuilder; +import com.demcha.compose.document.node.DocumentLinkOptions; +import com.demcha.compose.document.node.DocumentNode; +import com.demcha.compose.document.node.HorizontalAlign; +import com.demcha.compose.document.node.LayerAlign; +import com.demcha.compose.document.node.TextAlign; +import com.demcha.compose.document.style.DocumentColor; +import com.demcha.compose.document.svg.SvgIcon; + +import java.util.Locale; +import java.util.function.Consumer; + +import static com.demcha.compose.document.templates.invoice.presets.ObsidianStyles.DISC_D; +import static com.demcha.compose.document.templates.invoice.presets.ObsidianStyles.DISC_GLYPH; +import static com.demcha.compose.document.templates.invoice.presets.ObsidianStyles.INK; +import static com.demcha.compose.document.templates.invoice.presets.ObsidianStyles.PARTY_NAME_SIZE; +import static com.demcha.compose.document.templates.invoice.presets.ObsidianStyles.RULE_THIN; +import static com.demcha.compose.document.templates.invoice.presets.ObsidianStyles.bold; + +/** + * The shapes the Obsidian invoice repeats: a horizontal pair inside a column, a + * filled disc, a hairline, and the initials a party's disc falls back to. + */ +final class ObsidianWidgets { + + private ObsidianWidgets() { + } + + /** + * A row wrapped in a layer, so it can sit inside a row cell. + * + *

    A row nested in a row cell is refused by the layout compiler, and + * through a section too. A row wrapped in a LayerStack layer lays out + * horizontally there.

    + */ + static void layeredRow(SectionBuilder parent, String name, Consumer spec) { + SectionBuilder holder = new SectionBuilder(); + holder.name(name + "Holder"); + holder.addRow(name, spec); + DocumentNode node = holder.build(); + parent.addLayerStack(stack -> stack + .name(name + "Layer") + .layer(node, LayerAlign.TOP_LEFT, 0)); + } + + /** A hairline of the sheet's own weight. */ + static LineBuilder rule(LineBuilder line, String name, double width, DocumentColor color) { + return line.name(name).horizontal(width).thickness(RULE_THIN).color(color); + } + + /** A packaged mark, boxed so it can sit in a row cell or inside a disc. */ + static DocumentNode glyph(SvgIcon icon, String name, double size, HorizontalAlign align) { + return new SectionBuilder() + .name("Glyph_" + name) + .spacing(0) + .addSvgIcon(icon, size, align) + .build(); + } + + /** A party's disc, carrying a mark. */ + static DocumentNode disc(String name, DocumentColor fill, DocumentNode content, double diameter) { + return new ShapeContainerBuilder() + .name(name) + .circle(diameter) + .fillColor(fill) + .center(content) + .build(); + } + + /** A party's disc, carrying initials. */ + static DocumentNode initialsDisc(String name, DocumentColor fill, String initials) { + return disc(name, fill, new ParagraphBuilder() + .name(name + "Initials") + .text(initials) + .textStyle(bold(PARTY_NAME_SIZE, INK)) + .align(TextAlign.CENTER) + .build(), DISC_D); + } + + /** A mark's disc at the party size. */ + static DocumentNode glyphDisc(String name, DocumentColor fill, SvgIcon icon) { + return disc(name, fill, glyph(icon, name, DISC_GLYPH, HorizontalAlign.CENTER), DISC_D); + } + + /** + * The initials a disc shows when there is no mark to show instead. + * + *

    The first letter of each of the first two words, which is what the + * design's own client disc is. A name of one word gives one letter, and a + * name of none gives nothing — the disc is still drawn, because it is the + * design's shape and not a container for the letters.

    + * + * @param name the party's name + * @return up to two upper-case letters + */ + static String initials(String name) { + if (name == null || name.isBlank()) { + return ""; + } + StringBuilder initials = new StringBuilder(); + for (String word : name.trim().split("\\s+")) { + if (!word.isEmpty() && Character.isLetter(word.charAt(0))) { + initials.append(Character.toUpperCase(word.charAt(0))); + } + if (initials.length() == 2) { + break; + } + } + return initials.toString().toUpperCase(Locale.ENGLISH); + } + + /** Gives a paragraph a target when there is one. */ + static ParagraphBuilder linked(ParagraphBuilder paragraph, DocumentLinkOptions link) { + return link == null ? paragraph : paragraph.link(link); + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/PaymentsClosing.java b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/PaymentsClosing.java new file mode 100644 index 000000000..545f76b00 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/PaymentsClosing.java @@ -0,0 +1,275 @@ +package com.demcha.compose.document.templates.invoice.presets; + +import com.demcha.compose.document.dsl.PageFlowBuilder; +import com.demcha.compose.document.dsl.SectionBuilder; +import com.demcha.compose.document.node.DocumentLinkOptions; +import com.demcha.compose.document.node.HorizontalAlign; +import com.demcha.compose.document.node.RowVerticalAlign; +import com.demcha.compose.document.style.DocumentBorders; +import com.demcha.compose.document.style.DocumentInsets; +import com.demcha.compose.document.style.DocumentRowColumn; +import com.demcha.compose.document.style.DocumentStroke; +import com.demcha.compose.document.style.DocumentTextDecoration; +import com.demcha.compose.document.templates.core.identity.ContactUri; +import com.demcha.compose.document.templates.data.invoice.InvoiceNotesBlock; +import com.demcha.compose.document.templates.data.invoice.InvoicePaymentBlock; + +import java.util.Locale; + +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.ACCENT; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.BODY; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.DIVIDER; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.FOOTER_DUE_ICON; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.FOOTER_DUE_ICON_COL; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.FOOTER_DUE_ICON_INSET; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.FOOTER_DUE_TITLE_SIZE; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.FOOTER_RULE_GAP_ABOVE; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.FOOTER_RULE_GAP_BELOW; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.FOOTER_RULE_OVERHANG_L; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.FOOTER_RULE_OVERHANG_R; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.FOOTER_RULE_W; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.FOOTER_SPLIT_X; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.FOOTER_SUB_SIZE; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.FOOTER_SUPPORT_CELL_INSET; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.FOOTER_SUPPORT_ICON; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.FOOTER_SUPPORT_ICON_COL; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.FOOTER_SUPPORT_TITLE_SIZE; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.INK; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.LINE_BOX; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.MUTED; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.NOTES_DISC_D; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.NOTES_DISC_INSET; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.NOTES_GLYPH; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.NOTES_LABEL_SIZE; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.NOTES_PITCH; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.NOTES_SURFACE; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.NOTES_TEXT_INDENT; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.RULE_STRONG; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.RULE_THIN; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.TEXT_TOP_BEARING; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.capOffset; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.gap; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.glyphPad; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.px; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.style; + +/** + * What closes the sheet: the notes, the rule under them, and the two-cell + * document footer. + * + *

    The footer is body content rather than page chrome: it carries glyphs and + * a reachable address, and a header/footer zone takes only strings. Page + * identity on a continuation page is carried by the page number instead.

    + */ +final class PaymentsClosing { + + private PaymentsClosing() { + } + + /** + * Draws the closing blocks. + * + * @param page the page flow + * @param notes the note block and the contacts the footer offers + * @param payment the due notice and the closing line + */ + static void render(PageFlowBuilder page, InvoiceNotesBlock notes, + InvoicePaymentBlock payment) { + if (!notes.paragraphs().isEmpty()) { + renderNotes(page, notes); + } + renderFooterRule(page); + renderFooter(page, notes, payment); + } + + /** + * Glyph column and text column as two cells, so the second body line + * returns to the text axis rather than to the glyph's. The notes introduce + * the footer, and a page that ends on one and opens on the other reads as + * two fragments — so they are kept together. + */ + private static void renderNotes(PageFlowBuilder page, InvoiceNotesBlock notes) { + page.addSection("Notes", block -> { + block.spacing(0) + .padding(DocumentInsets.zero()) + .margin(new DocumentInsets(px(1280 - 1252), 0, 0, NOTES_DISC_INSET)) + .keepWithNext(); + block.addRow("NotesRow", row -> { + row.spacing(0) + .verticalAlign(RowVerticalAlign.TOP) + .columns(DocumentRowColumn.fixed(NOTES_TEXT_INDENT), + DocumentRowColumn.weight(1)); + row.addSection("NotesDisc", disc -> disc + .spacing(0) + .add(PaymentsWidgets.disc(PaymentsIcons.DOCUMENT, NOTES_SURFACE, + NOTES_DISC_D, NOTES_GLYPH))); + row.addSection("NotesText", text -> { + text.spacing(0); + text.addParagraph(p -> p + .name("NotesLabel") + .text(notes.heading()) + .textStyle(style(NOTES_LABEL_SIZE, ACCENT, + DocumentTextDecoration.BOLD)) + .margin(new DocumentInsets( + capOffset(1285 - 1280, NOTES_LABEL_SIZE), 0, 0, 0))); + text.addSection("NotesBody", lines -> { + lines.spacing(gap(NOTES_PITCH, PaymentsStyles.NOTES_BODY_SIZE)) + .margin(new DocumentInsets( + px(1307 - 1285) - LINE_BOX * NOTES_LABEL_SIZE + + TEXT_TOP_BEARING * (NOTES_LABEL_SIZE + - PaymentsStyles.NOTES_BODY_SIZE), 0, 0, 0)); + PaymentsWidgets.textLines(lines, "NotesLine", notes.paragraphs(), + PaymentsStyles.NOTES_BODY_SIZE, MUTED); + }); + }); + }); + }); + } + + private static void renderFooterRule(PageFlowBuilder page) { + page.addLine(line -> line + .name("FooterRule") + .horizontal(FOOTER_RULE_W) + .thickness(RULE_THIN) + .color(RULE_STRONG) + // One design px past the content box on the left, two on the + // right — measured, and small enough to look like a rounding + // error until the renderer refuses a line wider than its box. + .margin(new DocumentInsets( + FOOTER_RULE_GAP_ABOVE, + -FOOTER_RULE_OVERHANG_R, + FOOTER_RULE_GAP_BELOW, + -FOOTER_RULE_OVERHANG_L))); + } + + private static void renderFooter(PageFlowBuilder page, InvoiceNotesBlock notes, + InvoicePaymentBlock payment) { + page.addRow("DocumentFooter", row -> { + row.spacing(0) + .verticalAlign(RowVerticalAlign.TOP) + .columns(DocumentRowColumn.fixed(FOOTER_SPLIT_X), + DocumentRowColumn.weight(1)); + row.addSection("FooterDue", cell -> renderDue(cell, payment)); + row.addSection("FooterSupport", cell -> { + cell.spacing(0) + .borders(DocumentBorders.left(DocumentStroke.of(DIVIDER, RULE_THIN))) + .padding(new DocumentInsets(0, 0, 0, FOOTER_SUPPORT_CELL_INSET)); + renderSupport(cell, notes, payment); + }); + }); + } + + /** When the money is wanted, and what to quote when sending it. */ + private static void renderDue(SectionBuilder cell, InvoicePaymentBlock payment) { + cell.spacing(0).padding(new DocumentInsets(0, 0, 0, FOOTER_DUE_ICON_INSET)); + PaymentsWidgets.layeredRow(cell, "FooterDueRow", row -> { + row.spacing(0) + .verticalAlign(RowVerticalAlign.TOP) + .margin(new DocumentInsets(px(1404 - 1397), 0, 0, 0)) + .columns(DocumentRowColumn.fixed(FOOTER_DUE_ICON_COL), + DocumentRowColumn.weight(1)); + row.addSection("FooterDueIcon", glyph -> glyph + .spacing(0) + // A disc centres its glyph, so the icon box's own padding + // cancels there. A bare glyph leading a text row does not: + // the padding sits between the column's edge and the ink and + // has to be taken back off. + .margin(new DocumentInsets(-glyphPad(37), 0, 0, -glyphPad(37))) + .add(PaymentsWidgets.glyphNode(PaymentsIcons.CALENDAR, FOOTER_DUE_ICON, + HorizontalAlign.LEFT))); + row.addSection("FooterDueText", text -> { + text.spacing(0); + text.addParagraph(p -> p + .name("FooterDueTitle") + .text(dueTitle(payment)) + .textStyle(style(FOOTER_DUE_TITLE_SIZE, INK, + DocumentTextDecoration.BOLD)) + .margin(new DocumentInsets( + capOffset(1408 - 1404, FOOTER_DUE_TITLE_SIZE), 0, 0, 0))); + if (!payment.instruction().isBlank()) { + text.addParagraph(p -> p + .name("FooterDueSubtitle") + .text(payment.instruction()) + .textStyle(style(FOOTER_SUB_SIZE, BODY, + DocumentTextDecoration.DEFAULT)) + .margin(new DocumentInsets( + px(1428 - 1408) - LINE_BOX * FOOTER_DUE_TITLE_SIZE + + TEXT_TOP_BEARING + * (FOOTER_DUE_TITLE_SIZE - FOOTER_SUB_SIZE), + 0, 0, 0))); + } + }); + }); + } + + /** + * The design sets this line in capitals, so the preset uppercases whichever + * of the two due strings the document filled — the emphasis when it has one, + * the notice itself otherwise. + */ + private static String dueTitle(InvoicePaymentBlock payment) { + String text = payment.dueNoticeEmphasis().isBlank() + ? payment.dueNotice() + : payment.dueNoticeEmphasis(); + return text.toUpperCase(Locale.ROOT); + } + + /** Who to ask, and how to reach them. */ + private static void renderSupport(SectionBuilder cell, InvoiceNotesBlock notes, + InvoicePaymentBlock payment) { + PaymentsWidgets.layeredRow(cell, "FooterSupportRow", row -> { + row.spacing(0) + .verticalAlign(RowVerticalAlign.TOP) + .margin(new DocumentInsets(px(1405 - 1397), 0, 0, 0)) + .columns(DocumentRowColumn.fixed(FOOTER_SUPPORT_ICON_COL), + DocumentRowColumn.weight(1)); + row.addSection("FooterSupportIcon", glyph -> glyph + .spacing(0) + .margin(new DocumentInsets(-glyphPad(39), 0, 0, -glyphPad(39))) + .add(PaymentsWidgets.glyphNode(PaymentsIcons.SUPPORT, FOOTER_SUPPORT_ICON, + HorizontalAlign.LEFT))); + row.addSection("FooterSupportText", text -> { + text.spacing(0); + if (!payment.signOff().isBlank()) { + text.addParagraph(p -> p + .name("FooterSupportTitle") + .text(payment.signOff()) + .textStyle(style(FOOTER_SUPPORT_TITLE_SIZE, INK, + DocumentTextDecoration.BOLD)) + .margin(new DocumentInsets( + capOffset(1408 - 1405, FOOTER_SUPPORT_TITLE_SIZE), 0, 0, 0))); + } + // Separate runs, so each address reaches the PDF as a real + // annotation rather than as coloured text. + text.addParagraph(p -> { + p.name("FooterSupportContacts"); + boolean hasEmail = !notes.contactEmail().isBlank(); + if (hasEmail) { + p.inlineText(notes.contactEmail(), + style(FOOTER_SUB_SIZE, ACCENT, DocumentTextDecoration.DEFAULT), + new DocumentLinkOptions("mailto:" + notes.contactEmail())); + } + if (!notes.contactPhone().isBlank()) { + if (hasEmail) { + p.inlineText(" | ", style(FOOTER_SUB_SIZE, MUTED, + DocumentTextDecoration.DEFAULT)); + } + String dialled = ContactUri.tel(notes.contactPhone()); + if (dialled == null) { + p.inlineText(notes.contactPhone(), style(FOOTER_SUB_SIZE, BODY, + DocumentTextDecoration.DEFAULT)); + } else { + p.inlineText(notes.contactPhone(), style(FOOTER_SUB_SIZE, BODY, + DocumentTextDecoration.DEFAULT), + new DocumentLinkOptions(dialled)); + } + } + p.margin(new DocumentInsets( + px(1432 - 1408) - LINE_BOX * FOOTER_SUPPORT_TITLE_SIZE + + TEXT_TOP_BEARING + * (FOOTER_SUPPORT_TITLE_SIZE - FOOTER_SUB_SIZE), 0, 0, 0)); + }); + }); + }); + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/PaymentsIcons.java b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/PaymentsIcons.java new file mode 100644 index 000000000..3d59df9ee --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/PaymentsIcons.java @@ -0,0 +1,94 @@ +package com.demcha.compose.document.templates.invoice.presets; + +import com.demcha.compose.document.svg.SvgIcon; + +import java.io.IOException; +import java.io.InputStream; +import java.io.UncheckedIOException; +import java.nio.charset.StandardCharsets; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Classpath loader for the Payments invoice marks: the two party glyphs, the + * six a service line may take, and the four the preset places itself — the + * bank on the payment card, the document on the notes, the calendar on the due + * block and the headset on the support block. + * + *

    They ship inside the templates artifact under + * {@code templates/invoice/payments/icons/} as SVG, so they scale with the box + * they are drawn into rather than being resampled. Parsed icons are cached per + * token, so a sheet that repeats a mark reads each file once per JVM.

    + * + *

    The set is coloured, not tinted

    + * + *

    Each glyph carries its colour in its own markup, in one of the design's + * two tints: ink for the marks that sit inside a lavender disc, accent for the + * ones that stand on the page. A token therefore names a glyph and the + * tint it is drawn in, which is why {@link #HEADSET} and {@link #SUPPORT} are + * the same drawing under two names — one opens a service line inside a disc, + * the other closes the sheet on the page.

    + * + *

    {@link #LINE_TOKENS} is the vocabulary a document names through + * {@code InvoiceServiceLines.Line.icon()}. It is scoped to this preset — + * another preset packages its own set.

    + */ +final class PaymentsIcons { + + static final String BILL_TO = "bill-to"; + static final String SHIP_TO = "ship-to"; + static final String BANK = "bank"; + static final String DOCUMENT = "document"; + static final String CALENDAR = "calendar"; + static final String SUPPORT = "support"; + static final String HEADSET = "headset"; + + private static final String ICON_ROOT = "/templates/invoice/payments/icons/"; + + /** Every mark this preset packages. */ + private static final Set TOKENS = Set.of( + BILL_TO, SHIP_TO, BANK, DOCUMENT, CALENDAR, SUPPORT, + "card", "card-settings", "shield", "globe", "mobile", HEADSET); + + /** The marks a document may name on a service line. */ + static final Set LINE_TOKENS = Set.of( + "card", "card-settings", "shield", "globe", "mobile", HEADSET); + + private static final Map CACHE = new ConcurrentHashMap<>(); + + private PaymentsIcons() { + } + + /** + * Reads one mark. + * + * @param token one of the packaged tokens + * @return the parsed icon + * @throws IllegalArgumentException when the token names no packaged mark, + * listing the ones a document may choose — an icon token is data, + * so a wrong one is a data error + */ + static SvgIcon icon(String token) { + if (!TOKENS.contains(token)) { + throw new IllegalArgumentException( + "Unknown payments invoice icon token '" + token + + "'. This preset packages " + LINE_TOKENS.stream().sorted().toList() + + " for a service line's mark."); + } + return CACHE.computeIfAbsent(token, PaymentsIcons::read); + } + + private static SvgIcon read(String token) { + String resourcePath = ICON_ROOT + token + ".svg"; + try (InputStream input = PaymentsIcons.class.getResourceAsStream(resourcePath)) { + if (input == null) { + throw new IllegalStateException("Missing payments invoice icon: " + resourcePath); + } + return SvgIcon.parse(new String(input.readAllBytes(), StandardCharsets.UTF_8)); + } catch (IOException e) { + throw new UncheckedIOException( + "Failed to read payments invoice icon: " + resourcePath, e); + } + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/PaymentsInvoice.java b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/PaymentsInvoice.java new file mode 100644 index 000000000..0ceed8df3 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/PaymentsInvoice.java @@ -0,0 +1,172 @@ +package com.demcha.compose.document.templates.invoice.presets; + +import com.demcha.compose.document.api.DocumentSession; +import com.demcha.compose.document.api.PageMarginRule; +import com.demcha.compose.document.output.DocumentHeaderFooter; +import com.demcha.compose.document.output.DocumentHeaderFooterZone; +import com.demcha.compose.document.style.DocumentInsets; +import com.demcha.compose.document.templates.api.DocumentTemplate; +import com.demcha.compose.document.templates.data.invoice.StructuredInvoiceData; + +import java.util.List; +import java.util.Objects; + +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.CONTINUATION_MARGIN; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.FACE; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.MUTED; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.PAGE; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.PAGE_MARGIN; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.PAGE_NUMBER_BAND; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.PAGE_NUMBER_SIZE; + +/** + * Payments — a lavender-and-navy invoice: a masthead crossed by a diagonal + * band, a half-split issuer and metadata header, two addressed parties, a + * marked line-item table, a settlement row pairing bank details against the + * totals, a note block and a two-cell document footer. + * + *

    It flows

    + * + *

    Unlike most presets in this package the sheet is not drawn for one page. + * The design shows six service lines and a real month of usage brings dozens, + * so the table is the region that grows: its header repeats on every page it + * reaches, a continuation page reserves a deeper bottom margin than page one, + * and every page carries its number. Pagination here is load-bearing rather + * than incidental — a financial record that runs over has to make a missing + * page detectable.

    + * + *

    The lockup is the caller's

    + * + *

    The design's mark occupies a measured 136 × 55.3 design px box beside the + * title. What fills it comes from {@code InvoiceBrand}: a document that brings + * a logo has it drawn to that height, so a wider or narrower mark keeps the + * design's optical weight; a document that brings only a name has the name set + * as a wordmark. The templates artifact carries no mark of its own.

    + * + *

    One column, not five

    + * + *

    The table has an outer box and a rule between every row and no interior + * verticals, which is exactly what a single-column table draws — a cell strokes + * all four of its own edges, and a one-column table has no interior vertical + * edge. The five columns are a row inside each cell, on shares of the table's + * width. Building it the other way round, with the box from a stroked section + * wrapped round the table, is wrong on a document that paginates: a section's + * box fills its page fragment rather than hugging its rows, so every + * continuation page would end with an empty bordered strip.

    + * + *

    Every horizontal pair inside a cell is a wrapped row

    + * + *

    A row nested directly in a row cell is refused by the layout compiler, so + * each such pair — a party's mark and its label, a line's mark and its text, + * the card's fields, the totals grid — is a row wrapped in a single layer of a + * stack.

    + * + *

    What the document says, and what the preset chooses

    + * + *

    A service line names its own mark through + * {@code InvoiceServiceLines.Line.icon()}, from this preset's own vocabulary, + * and an unknown token is reported as a data error naming the set. A line with + * no token is drawn without a mark. The other four marks are the preset's: + * which glyph opens the bill-to block, the payment card, the notes and the two + * footer cells is a property of the design rather than of the document.

    + * + *

    Figures are written with the locale stated rather than inherited, so the + * same data renders the same sheet on any machine. The currency is named once + * in the amount column's header and its symbol carried on each figure.

    + * + *

    Fonts

    + * + *

    The sheet is set in Lato throughout. The templates artifact carries no + * fonts — register the family on the session, or the engine substitutes and the + * measured geometry no longer matches the type sitting in it.

    + * + *

    Usage:

    + *
    {@code
    + * DocumentTemplate template = PaymentsInvoice.create();
    + * template.compose(session, invoice);
    + * }
    + * + * @since 2.4.0 + */ +public final class PaymentsInvoice { + + /** Stable identifier of this preset. */ + public static final String ID = "payments-invoice"; + + /** Human-readable name of this preset. */ + public static final String DISPLAY_NAME = "Payments Invoice"; + + private PaymentsInvoice() { + } + + /** + * Creates the template. + * + * @return a template composing a {@link StructuredInvoiceData} + */ + public static DocumentTemplate create() { + return new Template(); + } + + private record Template() implements DocumentTemplate { + + @Override + public String id() { + return ID; + } + + @Override + public String displayName() { + return DISPLAY_NAME; + } + + @Override + public void compose(DocumentSession document, StructuredInvoiceData data) { + Objects.requireNonNull(document, "document"); + Objects.requireNonNull(data, "data"); + + // Every length on the sheet is a share of the design's own grid, so + // the preset sets the page and its margins rather than following + // what the caller configured. + document.pageSize(PAGE); + document.margin(PAGE_MARGIN); + document.pageMargins(List.of(PageMarginRule.from(2, CONTINUATION_MARGIN))); + renderPageNumber(document); + + String currency = data.currencyCode(); + document.pageFlow(page -> { + page.name("PaymentsInvoice").padding(DocumentInsets.zero()).spacing(0); + PaymentsMasthead.render(page, data.brand(), data.supplier(), data.masthead()); + PaymentsParties.render(page, data.billTo(), data.shipTo()); + PaymentsLines.render(page, data.serviceLines(), currency); + PaymentsSettlement.render(page, data.payment(), data.totals(), currency); + PaymentsClosing.render(page, data.notes(), data.payment()); + }); + } + + /** + * The only chrome the sheet has, and an addition rather than a + * reproduction: the design is one page and carries no number. + * + *

    The height is stated because a default band would take more than + * the bottom margin leaves and push the footer onto a second page. + * Sized to the margin, the band sits inside it rather than taking + * content space, and the type is sized to the band rather than the + * other way round.

    + */ + private static void renderPageNumber(DocumentSession document) { + document.footer(DocumentHeaderFooter.builder() + .zone(DocumentHeaderFooterZone.FOOTER) + .centerText("Page {page} of {pages}") + // The zone's own default is the standard-14 face, which + // would leave the page number the one line on the sheet in + // a family the design never uses. + .fontName(FACE) + .height((float) PAGE_NUMBER_BAND) + .fontSize((float) PAGE_NUMBER_SIZE) + .textColor(MUTED) + .showSeparator(false) + .build()); + } + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/PaymentsLines.java b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/PaymentsLines.java new file mode 100644 index 000000000..62186a101 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/PaymentsLines.java @@ -0,0 +1,237 @@ +package com.demcha.compose.document.templates.invoice.presets; + +import com.demcha.compose.document.dsl.PageFlowBuilder; +import com.demcha.compose.document.dsl.RowBuilder; +import com.demcha.compose.document.dsl.TableBuilder; +import com.demcha.compose.document.node.DocumentNode; +import com.demcha.compose.document.node.RowVerticalAlign; +import com.demcha.compose.document.node.TextAlign; +import com.demcha.compose.document.style.DocumentColor; +import com.demcha.compose.document.style.DocumentInsets; +import com.demcha.compose.document.style.DocumentRowColumn; +import com.demcha.compose.document.style.DocumentTextDecoration; +import com.demcha.compose.document.table.DocumentTableCell; +import com.demcha.compose.document.table.DocumentTableColumn; +import com.demcha.compose.document.templates.data.invoice.InvoiceServiceLines; + +import java.util.List; + +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.ACCENT_SURFACE; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.AMOUNT_PAD_R; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.BODY; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.CELL_AMOUNT_SIZE; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.CELL_PAD_R; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.CELL_SIZE; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.COLUMN_SHARES; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.CONTENT_W; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.DESC_PAD_L; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.DISC_D; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.HEADER_CELL_STYLE; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.INK; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.ITEM_CELL_INSET; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.ITEM_CELL_STYLE; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.ITEM_DISC_GAP; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.ITEM_SUB_PITCH; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.ITEM_SUB_SIZE; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.ITEM_TEXT_W; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.ITEM_TITLE_SIZE; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.LINE_BOX; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.MUTED; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.TABLE_HEAD_SIZE; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.TABLE_HEAD_SMALL_SIZE; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.TEXT_TOP_BEARING; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.gap; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.px; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.style; + +/** + * The service lines. + * + *

    One column, not five

    + * + *

    The design's table has an outer box and a rule between every row and + * no interior verticals — which is exactly the rule topology a + * single-column table draws, because a cell strokes all four of its own edges + * and a one-column table has no interior vertical edge to draw. The five + * columns are a row inside each cell, laid out on shares of the table's width, + * so they stay aligned down the page without the engine drawing a line between + * them.

    + * + *

    The alternative — unstroked cells, separators as line rows, and the outer + * box from a stroked section wrapped round the table — is wrong on a document + * that paginates: a section's box fills its page fragment rather than hugging + * its rows, so every continuation page would end with an empty bordered strip + * below its last line. Cell borders have no such problem, because the engine + * redraws each page's cell edges from the same styles and every fragment closes + * on its own last row.

    + */ +final class PaymentsLines { + + private PaymentsLines() { + } + + /** + * Draws the table. + * + * @param page the page flow + * @param serviceLines the columns and the lines + * @param currencyCode the ISO code the figures are stated in + */ + static void render(PageFlowBuilder page, InvoiceServiceLines serviceLines, + String currencyCode) { + page.addSection("LineItemsBox", box -> { + box.spacing(0) + .padding(DocumentInsets.zero()) + .margin(new DocumentInsets(px(596 - 567), 0, px(1060 - 1042), 0)); + box.addTable(table -> { + table.name("LineItems") + .width(CONTENT_W) + .columns(DocumentTableColumn.fixed(CONTENT_W)); + renderHeader(table, serviceLines.columns(), currencyCode); + List lines = serviceLines.lines(); + for (int i = 0; i < lines.size(); i++) { + table.rowCells(DocumentTableCell + .node(renderLine(lines.get(i), i, currencyCode)) + .withStyle(ITEM_CELL_STYLE)); + } + }); + }); + } + + /** + * The header repeats on every page its table reaches. It is one cell like + * every other row, so its own stroke draws the table's top edge and the rule + * under the band, and its fill is the tint. + */ + private static void renderHeader(TableBuilder table, InvoiceServiceLines.Columns columns, + String currencyCode) { + RowBuilder row = new RowBuilder(); + row.name("LineItemsHeader").spacing(0).columns(columnWidths()); + row.addParagraph(p -> p + .name("HeadDescription") + .text(columns.description()) + .textStyle(style(TABLE_HEAD_SIZE, INK, DocumentTextDecoration.BOLD)) + .margin(new DocumentInsets(0, 0, 0, DESC_PAD_L))); + headCell(row, "HeadQuantity", columns.quantity(), TextAlign.CENTER, 0); + headCell(row, "HeadUnitPrice", columns.unitPrice(), TextAlign.RIGHT, CELL_PAD_R); + headCell(row, "HeadVat", columns.vat(), TextAlign.CENTER, 0); + // Two runs: the currency qualifier is set smaller than the label it + // follows, which one paragraph of one style cannot express. The design + // names the currency once, here, rather than on every amount. + row.addParagraph(p -> { + p.name("HeadAmount"); + p.inlineText(columns.amount(), style(TABLE_HEAD_SIZE, INK, DocumentTextDecoration.BOLD)); + if (!currencyCode.isBlank()) { + p.inlineText(" (" + currencyCode + ")", + style(TABLE_HEAD_SMALL_SIZE, INK, DocumentTextDecoration.BOLD)); + } + p.align(TextAlign.RIGHT); + p.padding(new DocumentInsets(0, AMOUNT_PAD_R, 0, 0)); + }); + table.headerCells(DocumentTableCell.node(row.build()).withStyle(HEADER_CELL_STYLE)) + .repeatHeader(); + } + + private static void headCell(RowBuilder row, String name, String label, TextAlign align, + double padRight) { + row.addParagraph(p -> p + .name(name) + .text(label) + .textStyle(style(TABLE_HEAD_SIZE, INK, DocumentTextDecoration.BOLD)) + .align(align) + .padding(new DocumentInsets(0, padRight, 0, 0))); + } + + /** + * The five columns as a row inside the row's single cell. Alignment differs + * per column and is part of the design rather than an accident: description + * left, quantity centred, unit price right, tax centred, amount right. + */ + private static DocumentNode renderLine(InvoiceServiceLines.Line line, int index, + String currencyCode) { + int n = index + 1; + RowBuilder row = new RowBuilder(); + row.name("Item" + n) + .spacing(0) + .verticalAlign(RowVerticalAlign.CENTER) + .columns(columnWidths()); + row.addSection("ItemDescription" + n, cell -> { + cell.spacing(0).padding(new DocumentInsets(0, 0, 0, ITEM_CELL_INSET)); + // Mark and text are a horizontal pair inside a row cell, which the + // layout compiler refuses as a bare row. + PaymentsWidgets.layeredRow(cell, "ItemBody" + n, body -> { + body.spacing(ITEM_DISC_GAP) + .verticalAlign(RowVerticalAlign.CENTER) + .columns(DocumentRowColumn.fixed(DISC_D), + DocumentRowColumn.fixed(ITEM_TEXT_W)); + body.addSection("ItemDisc" + n, disc -> { + disc.spacing(0); + if (!line.icon().isBlank()) { + disc.add(PaymentsWidgets.disc(line.icon(), ACCENT_SURFACE)); + } + }); + body.addSection("ItemText" + n, text -> { + text.spacing(0); + text.addParagraph(p -> p + .name("ItemTitle" + n) + .text(line.title()) + .textStyle(style(ITEM_TITLE_SIZE, INK, DocumentTextDecoration.BOLD))); + if (!line.description().isBlank()) { + text.addParagraph(p -> p + .name("ItemSubtitle" + n) + .text(line.description()) + .textStyle(style(ITEM_SUB_SIZE, MUTED, + DocumentTextDecoration.DEFAULT)) + .lineSpacing(gap(ITEM_SUB_PITCH, ITEM_SUB_SIZE)) + .margin(new DocumentInsets( + px(668 - 648) - LINE_BOX * ITEM_TITLE_SIZE + + TEXT_TOP_BEARING + * (ITEM_TITLE_SIZE - ITEM_SUB_SIZE), + 0, 0, 0))); + } + }); + }); + }); + valueCell(row, "ItemQuantity" + n, PaymentsText.quantity(line.quantity()), + CELL_SIZE, BODY, DocumentTextDecoration.DEFAULT, TextAlign.CENTER, 0); + valueCell(row, "ItemUnitPrice" + n, PaymentsText.amount(currencyCode, line.unitPrice()), + CELL_SIZE, BODY, DocumentTextDecoration.DEFAULT, TextAlign.RIGHT, CELL_PAD_R); + valueCell(row, "ItemVat" + n, line.vatRate(), + CELL_SIZE, BODY, DocumentTextDecoration.DEFAULT, TextAlign.CENTER, 0); + valueCell(row, "ItemAmount" + n, PaymentsText.amount(currencyCode, line.amount()), + CELL_AMOUNT_SIZE, INK, DocumentTextDecoration.BOLD, TextAlign.RIGHT, AMOUNT_PAD_R); + return row.build(); + } + + private static void valueCell(RowBuilder row, String name, String value, double size, + DocumentColor color, DocumentTextDecoration decoration, + TextAlign align, double padRight) { + row.addParagraph(p -> p + .name(name) + .text(value) + .textStyle(style(size, color, decoration)) + .align(align) + // padding, not margin: a paragraph's right margin inside a row + // column is taken twice — once off the box's width and again as + // an offset — so a 22 px inset would land the text 44 px in. + .padding(new DocumentInsets(0, padRight, 0, 0))); + } + + /** + * The five column widths as shares of the table's own width. The last takes + * whatever the others leave: five independently rounded shares sum to the + * width plus a half-thousandth of a point, and the engine refuses a row + * narrower than its columns. + */ + private static DocumentRowColumn[] columnWidths() { + DocumentRowColumn[] columns = new DocumentRowColumn[COLUMN_SHARES.length]; + double assigned = 0; + for (int i = 0; i < COLUMN_SHARES.length - 1; i++) { + double width = CONTENT_W * COLUMN_SHARES[i]; + assigned += width; + columns[i] = DocumentRowColumn.fixed(width); + } + columns[COLUMN_SHARES.length - 1] = DocumentRowColumn.fixed(CONTENT_W - assigned); + return columns; + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/PaymentsMasthead.java b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/PaymentsMasthead.java new file mode 100644 index 000000000..c603a47f8 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/PaymentsMasthead.java @@ -0,0 +1,319 @@ +package com.demcha.compose.document.templates.invoice.presets; + +import com.demcha.compose.document.dsl.ImageBuilder; +import com.demcha.compose.document.dsl.PageFlowBuilder; +import com.demcha.compose.document.dsl.PathBuilder; +import com.demcha.compose.document.dsl.RowBuilder; +import com.demcha.compose.document.dsl.SectionBuilder; +import com.demcha.compose.document.node.DocumentNode; +import com.demcha.compose.document.node.LayerAlign; +import com.demcha.compose.document.node.RowVerticalAlign; +import com.demcha.compose.document.style.DocumentBorders; +import com.demcha.compose.document.style.DocumentColor; +import com.demcha.compose.document.style.DocumentInsets; +import com.demcha.compose.document.style.DocumentRowColumn; +import com.demcha.compose.document.style.DocumentStroke; +import com.demcha.compose.document.style.DocumentTextDecoration; +import com.demcha.compose.document.templates.data.invoice.InvoiceBrand; +import com.demcha.compose.document.templates.data.invoice.InvoiceContactBlock; +import com.demcha.compose.document.templates.data.invoice.InvoiceMasthead; + +import java.util.ArrayList; +import java.util.List; + +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.ACCENT; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.ACCENT_BAR_H; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.ACCENT_BAR_OFFSET; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.ACCENT_BAR_W; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.BAND_BOTTOM_PX; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.BAND_H; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.BAND_LAVENDER; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.BAND_LAVENDER_QUAD; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.BAND_LEFT_PX; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.BAND_NAVY_QUAD; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.BAND_RIGHT_PX; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.BAND_W; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.BODY; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.BODY_SIZE; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.BRAND_LOCKUP_H; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.BRAND_LOCKUP_INSET; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.DIVIDER; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.HEADER_RULE_OVERHANG_L; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.HEADER_RULE_OVERHANG_R; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.HEADER_RULE_W; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.HEADER_STACK_BOTTOM_PX; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.INK; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.ISSUER_NAME_SIZE; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.LINE_BOX; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.MARGIN_T; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.META_COL_X; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.META_LABEL_SIZE; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.META_LABEL_W; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.META_PITCH; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.META_VALUE_INSET; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.META_VALUE_SIZE; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.RULE_MED; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.RULE_SOFT; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.TEXT_INSET; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.TEXT_TOP_BEARING; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.TITLE_DROP; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.TITLE_SIZE; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.WORDMARK_SIZE; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.gap; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.px; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.style; + +/** + * The header: the diagonal band, the lockup beside the title, the issuer block + * against the metadata grid, and the rule that closes them. + * + *

    One stack, because two of its three parts are page furniture

    + * + *

    The band bleeds past the top margin to the paper's edge and the accent bar + * stands left of the content box, so neither belongs to the flow. Both are + * positioned by offset from the stack's top-left; only the header content is a + * layer.

    + * + *

    The lockup box is the design's, whatever fills it

    + * + *

    The design's mark measures 136 × 55.3 design px. A document that brings a + * logo has it drawn to that height, so a wider or narrower mark keeps the + * design's optical weight; a document that brings only a name has the name set + * as a wordmark instead. Either way the title beside it does not move, because + * the title is a left-anchored member of the metadata column rather than a + * right-anchored member of the page.

    + */ +final class PaymentsMasthead { + + private PaymentsMasthead() { + } + + /** + * Draws the header and its closing rule. + * + * @param page the page flow + * @param brand the lockup + * @param supplier who is invoicing + * @param masthead the title and the metadata rows + */ + static void render(PageFlowBuilder page, InvoiceBrand brand, InvoiceContactBlock supplier, + InvoiceMasthead masthead) { + page.addLayerStack(stack -> stack + .name("MastheadStack") + .padding(DocumentInsets.zero()) + .position(band(), px(BAND_LEFT_PX - 51), -MARGIN_T, LayerAlign.TOP_LEFT, 0) + .position(accentBar(), ACCENT_BAR_OFFSET, 0, LayerAlign.TOP_LEFT, 0) + .layer(headerContent(brand, supplier, masthead), LayerAlign.TOP_LEFT, 1)); + renderHeaderRule(page); + } + + /** The lavender quadrilateral, then the navy one over it. */ + private static DocumentNode band() { + SectionBuilder band = new SectionBuilder(); + band.name("HeaderBand").spacing(0).padding(DocumentInsets.zero()); + band.addLayerStack(stack -> stack + .name("HeaderBandLayers") + .layer(quad("BandLavender", BAND_LAVENDER, BAND_LAVENDER_QUAD), + LayerAlign.TOP_LEFT, 0) + .layer(quad("BandNavy", INK, BAND_NAVY_QUAD), LayerAlign.TOP_LEFT, 1)); + return band.build(); + } + + /** + * One quadrilateral of the band. The corners arrive in page design pixels + * and are normalised here — {@link #nx} and {@link #ny} are the only place + * the bottom-left origin of a path's coordinate space is dealt with. + */ + private static DocumentNode quad(String name, DocumentColor fill, double[][] corners) { + PathBuilder path = new PathBuilder(); + path.name(name).size(BAND_W, BAND_H).fillColor(fill); + path.moveTo(nx(corners[0][0]), ny(corners[0][1])); + for (int i = 1; i < corners.length; i++) { + path.lineTo(nx(corners[i][0]), ny(corners[i][1])); + } + return path.closePath().build(); + } + + private static double nx(double pagePx) { + return (pagePx - BAND_LEFT_PX) / (BAND_RIGHT_PX - BAND_LEFT_PX); + } + + /** Page y runs down; a path's y runs up from its own bottom edge. */ + private static double ny(double pagePx) { + return 1.0 - pagePx / BAND_BOTTOM_PX; + } + + private static DocumentNode accentBar() { + SectionBuilder holder = new SectionBuilder(); + holder.name("AccentBarHolder").spacing(0).padding(DocumentInsets.zero()); + holder.addLine(line -> line + .name("HeaderAccentBar") + .vertical(ACCENT_BAR_H) + .thickness(ACCENT_BAR_W) + .color(ACCENT)); + return holder.build(); + } + + private static DocumentNode headerContent(InvoiceBrand brand, InvoiceContactBlock supplier, + InvoiceMasthead masthead) { + SectionBuilder content = new SectionBuilder(); + content.name("HeaderContent").spacing(0).padding(DocumentInsets.zero()); + content.addRow("Masthead", row -> renderLockupRow(row, brand, masthead)); + content.addRow("HeaderSplit", row -> { + // The two cells of the split do not share a top edge: the metadata + // block starts 11 px below the issuer name's box. + row.spacing(0) + .verticalAlign(RowVerticalAlign.TOP) + // 12 px by the design's own ink gap, less the 2.5 the title's + // line box gives back. + .margin(new DocumentInsets(px(12 - 2.5), 0, 0, 0)) + .columns(DocumentRowColumn.fixed(META_COL_X), DocumentRowColumn.weight(1)); + row.addSection("IssuerBlock", cell -> renderIssuer(cell, supplier)); + row.addSection("InvoiceMeta", cell -> { + cell.spacing(0).padding(new DocumentInsets(px(11), 0, 0, 0)); + renderMeta(cell, masthead.entries()); + }); + }); + return content.build(); + } + + /** + * TOP, not CENTER: the title's line box is much taller than the lockup, so + * centring would push the lockup down the row it is supposed to set the top + * edge of. + */ + private static void renderLockupRow(RowBuilder row, InvoiceBrand brand, + InvoiceMasthead masthead) { + row.spacing(0) + .verticalAlign(RowVerticalAlign.TOP) + .margin(new DocumentInsets(px(46 - 28), 0, 0, 0)) + .columns(DocumentRowColumn.fixed(META_COL_X), DocumentRowColumn.weight(1)); + row.addSection("BrandLockup", cell -> { + cell.spacing(0).padding(new DocumentInsets(0, 0, 0, BRAND_LOCKUP_INSET)); + if (brand.logo() != null) { + cell.add(new ImageBuilder() + .name("BrandLogo") + .source(brand.logo()) + .height(BRAND_LOCKUP_H) + .build()); + } else if (!brand.name().isBlank()) { + cell.addParagraph(p -> p + .name("BrandWordmark") + .text(brand.name()) + .textStyle(style(WORDMARK_SIZE, ACCENT, DocumentTextDecoration.BOLD))); + } + }); + row.addParagraph(p -> p + .name("InvoiceTitle") + .text(masthead.title()) + .textStyle(style(TITLE_SIZE, INK, DocumentTextDecoration.BOLD)) + .margin(new DocumentInsets(TITLE_DROP, 0, 0, 0))); + } + + /** + * The issuer's name, its address, and its registrations — the last set off + * by exactly two line pitches. That gap is authored, not drift. + */ + private static void renderIssuer(SectionBuilder cell, InvoiceContactBlock supplier) { + cell.spacing(0).padding(new DocumentInsets(0, 0, 0, TEXT_INSET)); + cell.addParagraph(p -> p + .name("IssuerName") + .text(supplier.legalName()) + .textStyle(style(ISSUER_NAME_SIZE, INK, DocumentTextDecoration.BOLD))); + cell.addSection("IssuerAddress", lines -> { + lines.spacing(gap(21.33, BODY_SIZE)) + .margin(new DocumentInsets(px(172 - 140) - LINE_BOX * ISSUER_NAME_SIZE + + TEXT_TOP_BEARING * (ISSUER_NAME_SIZE - BODY_SIZE), 0, 0, 0)); + PaymentsWidgets.textLines(lines, "IssuerAddressLine", supplier.addressLines(), + BODY_SIZE, BODY); + }); + List registrations = registrationLines(supplier); + if (!registrations.isEmpty()) { + cell.addSection("IssuerRegistration", lines -> { + lines.spacing(gap(21.0, BODY_SIZE)) + .margin(new DocumentInsets(gap(2 * 21.33, BODY_SIZE), 0, 0, 0)); + PaymentsWidgets.textLines(lines, "IssuerRegistrationLine", registrations, + BODY_SIZE, BODY); + }); + } + } + + /** The two labelled registrations a supplier may carry, as printed lines. */ + private static List registrationLines(InvoiceContactBlock supplier) { + List lines = new ArrayList<>(); + addRegistration(lines, supplier.registrationLabel(), supplier.registrationNumber()); + addRegistration(lines, supplier.taxRegistrationLabel(), supplier.taxRegistrationNumber()); + return lines; + } + + private static void addRegistration(List lines, String label, String number) { + if (number == null || number.isBlank()) { + return; + } + lines.add(label == null || label.isBlank() ? number : label + " " + number); + } + + /** + * Two stacks side by side rather than a row per entry, so the divider + * between them is one continuous border. It is the value column's left + * border, not a node: that is why it begins with the first row and ends with + * the last rather than running the block's full box. + * + *

    The two stacks carry different spacings because their type sizes differ + * — a bold label and a regular value have different line boxes — and the + * shared pitch is what makes their rows line up.

    + */ + private static void renderMeta(SectionBuilder cell, List entries) { + PaymentsWidgets.layeredRow(cell, "MetaGrid", row -> { + row.spacing(0) + .verticalAlign(RowVerticalAlign.TOP) + .columns(DocumentRowColumn.fixed(META_LABEL_W), DocumentRowColumn.weight(1)); + row.addSection("MetaLabels", labels -> { + labels.spacing(gap(META_PITCH, META_LABEL_SIZE)); + int index = 0; + for (InvoiceMasthead.Entry entry : entries) { + String name = "MetaLabel" + (++index); + labels.addParagraph(p -> p + .name(name) + .text(entry.label()) + .textStyle(style(META_LABEL_SIZE, INK, DocumentTextDecoration.BOLD))); + } + }); + row.addSection("MetaValues", values -> { + values.spacing(gap(META_PITCH, META_VALUE_SIZE)) + .borders(DocumentBorders.left(DocumentStroke.of(DIVIDER, RULE_MED))) + .padding(new DocumentInsets(0, 0, 0, META_VALUE_INSET)); + int index = 0; + for (InvoiceMasthead.Entry entry : entries) { + String name = "MetaValue" + (++index); + values.addParagraph(p -> p + .name(name) + .text(entry.value()) + .textStyle(entry.emphasized() + ? style(META_LABEL_SIZE, ACCENT, DocumentTextDecoration.BOLD) + : style(META_VALUE_SIZE, BODY, + DocumentTextDecoration.DEFAULT))); + } + }); + }); + } + + /** + * Wider than the content box on both sides — it is furniture for the header + * block rather than a divider between two pieces of content, which is why it + * overhangs. Its top margin is negative because the band, not the content, + * is the tallest layer of the stack above it. + */ + private static void renderHeaderRule(PageFlowBuilder page) { + page.addLine(line -> line + .name("HeaderRule") + .horizontal(HEADER_RULE_W) + .thickness(RULE_MED) + .color(RULE_SOFT) + .margin(new DocumentInsets( + px(359 - HEADER_STACK_BOTTOM_PX), + -HEADER_RULE_OVERHANG_R, + px(378 - 361), + -HEADER_RULE_OVERHANG_L))); + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/PaymentsParties.java b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/PaymentsParties.java new file mode 100644 index 000000000..8ed94a8ed --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/PaymentsParties.java @@ -0,0 +1,127 @@ +package com.demcha.compose.document.templates.invoice.presets; + +import com.demcha.compose.document.dsl.PageFlowBuilder; +import com.demcha.compose.document.dsl.SectionBuilder; +import com.demcha.compose.document.node.RowVerticalAlign; +import com.demcha.compose.document.style.DocumentBorders; +import com.demcha.compose.document.style.DocumentInsets; +import com.demcha.compose.document.style.DocumentRowColumn; +import com.demcha.compose.document.style.DocumentStroke; +import com.demcha.compose.document.style.DocumentTextDecoration; +import com.demcha.compose.document.templates.data.invoice.InvoiceRecipient; + +import java.util.ArrayList; +import java.util.List; + +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.ACCENT_SURFACE; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.BODY; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.BODY_SIZE; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.DISC_D; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.DIVIDER; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.INK; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.LINE_BOX; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.PARTY_ADDRESS_PITCH; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.PARTY_CELL_INSET; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.PARTY_DISC_GAP; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.PARTY_DISC_INSET; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.PARTY_LABEL_SIZE; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.PARTY_NAME_SIZE; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.PARTY_TEXT_INDENT; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.RULE_THIN; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.TEXT_TOP_BEARING; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.capOffset; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.gap; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.px; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.style; + +/** + * The addressed parties: who is billed, and where the work went. + * + *

    One method draws both — they are the same component with different data, + * down to the registration line only one of them usually has. The divider + * belongs to the split, so it is the second cell's own left edge rather than a + * rule drawn between the two.

    + */ +final class PaymentsParties { + + private PaymentsParties() { + } + + /** + * Draws the row. + * + * @param page the page flow + * @param billTo who is billed + * @param shipTo where the work went, or a party with no name when absent + */ + static void render(PageFlowBuilder page, InvoiceRecipient billTo, InvoiceRecipient shipTo) { + List parties = new ArrayList<>(); + parties.add(new Party(PaymentsIcons.BILL_TO, billTo)); + if (!shipTo.name().isBlank()) { + parties.add(new Party(PaymentsIcons.SHIP_TO, shipTo)); + } + + page.addRow("PartiesRow", row -> { + row.spacing(0).verticalAlign(RowVerticalAlign.TOP).evenWeights(); + for (int i = 0; i < parties.size(); i++) { + Party party = parties.get(i); + boolean first = i == 0; + row.addSection("Party" + (i + 1), cell -> { + cell.spacing(0); + if (first) { + cell.padding(new DocumentInsets(0, 0, 0, PARTY_DISC_INSET)); + } else { + cell.borders(DocumentBorders.left(DocumentStroke.of(DIVIDER, RULE_THIN))) + .padding(new DocumentInsets(0, 0, 0, PARTY_CELL_INSET)); + } + renderParty(cell, party); + }); + } + }); + } + + private static void renderParty(SectionBuilder cell, Party party) { + InvoiceRecipient who = party.recipient(); + PaymentsWidgets.layeredRow(cell, "PartyHead", row -> { + row.spacing(PARTY_DISC_GAP) + .verticalAlign(RowVerticalAlign.TOP) + .columns(DocumentRowColumn.fixed(DISC_D), DocumentRowColumn.weight(1)); + row.addSection("PartyDisc", disc -> disc + .spacing(0) + .add(PaymentsWidgets.disc(party.token(), ACCENT_SURFACE))); + row.addParagraph(p -> p + .name("PartyLabel") + .text(who.heading()) + .textStyle(style(PARTY_LABEL_SIZE, INK, DocumentTextDecoration.BOLD)) + .margin(new DocumentInsets(capOffset(391 - 378, PARTY_LABEL_SIZE), 0, 0, 0))); + }); + cell.addSection("PartyBody", block -> { + block.spacing(0).padding(new DocumentInsets(0, 0, 0, PARTY_TEXT_INDENT)); + block.addParagraph(p -> p + .name("PartyName") + .text(who.name()) + .textStyle(style(PARTY_NAME_SIZE, INK, DocumentTextDecoration.BOLD))); + block.addSection("PartyAddress", lines -> { + lines.spacing(gap(PARTY_ADDRESS_PITCH, BODY_SIZE)) + .margin(new DocumentInsets(px(451 - 423) - LINE_BOX * PARTY_NAME_SIZE + + TEXT_TOP_BEARING * (PARTY_NAME_SIZE - BODY_SIZE), 0, 0, 0)); + PaymentsWidgets.textLines(lines, "PartyAddressLine", who.addressLines(), + BODY_SIZE, BODY); + }); + // The subline is drawn UNDER the address, set off by the design's own + // gap: this sheet uses it for the registration a billed party prints + // below where it is, not for a second line of its name. + if (!who.subline().isBlank()) { + block.addParagraph(p -> p + .name("PartySubline") + .text(who.subline()) + .textStyle(style(BODY_SIZE, BODY, DocumentTextDecoration.DEFAULT)) + .margin(new DocumentInsets(gap(552 - 513, BODY_SIZE), 0, 0, 0))); + } + }); + } + + /** A party and the mark the preset gives it. */ + private record Party(String token, InvoiceRecipient recipient) { + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/PaymentsSettlement.java b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/PaymentsSettlement.java new file mode 100644 index 000000000..b3ea7847c --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/PaymentsSettlement.java @@ -0,0 +1,273 @@ +package com.demcha.compose.document.templates.invoice.presets; + +import com.demcha.compose.document.dsl.PageFlowBuilder; +import com.demcha.compose.document.dsl.SectionBuilder; +import com.demcha.compose.document.node.RowVerticalAlign; +import com.demcha.compose.document.node.TextAlign; +import com.demcha.compose.document.style.DocumentBorders; +import com.demcha.compose.document.style.DocumentInsets; +import com.demcha.compose.document.style.DocumentRowColumn; +import com.demcha.compose.document.style.DocumentStroke; +import com.demcha.compose.document.style.DocumentTextDecoration; +import com.demcha.compose.document.templates.data.invoice.InvoicePaymentBlock; +import com.demcha.compose.document.templates.data.invoice.InvoiceTotalsBlock; + +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.ACCENT; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.BODY; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.CARD_DISC_D; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.CARD_DISC_INSET; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.CARD_DISC_SURFACE; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.CARD_FIELD_PITCH; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.CARD_GLYPH; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.CARD_HEAD_BOTTOM_PX; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.CARD_HEAD_GAP; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.CARD_HEAD_SIZE; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.CARD_LABEL_SIZE; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.CARD_LABEL_W; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.CARD_PAD; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.CARD_RADIUS; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.CARD_SURFACE; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.CARD_TEXT_INDENT; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.CARD_VALUE_SIZE; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.CARD_W; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.DIVIDER; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.DUE_PAD; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.DUE_RADIUS; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.DUE_SUB_PITCH; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.INK; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.LINE_BOX; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.RULE_MED; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.RULE_THIN; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.SETTLEMENT_GUTTER; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.SETTLEMENT_RIGHT_INSET; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.SURFACE; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.TEXT_TOP_BEARING; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.TOTALS_LABEL_SIZE; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.TOTALS_PAD_R; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.TOTALS_PITCH; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.TOTALS_RULE_W; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.TOTALS_TEXT_INSET; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.TOTALS_VALUE_SIZE; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.TOTAL_DUE_LABEL_SIZE; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.TOTAL_DUE_SUB_SIZE; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.TOTAL_DUE_VALUE_SIZE; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.capOffset; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.gap; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.px; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.style; + +/** + * The settlement row: the payment card against the totals and the panel that + * closes them. + * + *

    The split is not the even one the two rows above it use — the card is + * narrower than the totals cell, and the divider sits inside the + * gutter rather than on either cell's edge. That asymmetry is in the design.

    + */ +final class PaymentsSettlement { + + private PaymentsSettlement() { + } + + /** + * Draws the row. + * + * @param page the page flow + * @param payment the bank details and the due notice + * @param totals the summed rows and the amount due + * @param currencyCode the ISO code the figures are stated in + */ + static void render(PageFlowBuilder page, InvoicePaymentBlock payment, + InvoiceTotalsBlock totals, String currencyCode) { + page.addRow("SettlementRow", row -> { + row.spacing(SETTLEMENT_GUTTER) + .verticalAlign(RowVerticalAlign.TOP) + .columns(DocumentRowColumn.fixed(CARD_W), DocumentRowColumn.weight(1)); + row.addSection("PaymentDetails", cell -> renderCard(cell, payment)); + row.addSection("Settlement", cell -> { + // The divider sits inside the gutter rather than on either cell's + // edge, so it is this cell's left border and the cell then pads + // itself back out to where the panel's edge actually is. + cell.spacing(0) + .borders(DocumentBorders.left(DocumentStroke.of(DIVIDER, RULE_THIN))) + .padding(new DocumentInsets(0, 0, 0, SETTLEMENT_RIGHT_INSET)); + renderTotals(cell, totals, currencyCode); + renderDuePanel(cell, payment, totals, currencyCode); + }); + }); + } + + /** + * A tinted rounded card with no border — the fill alone separates it from + * the page. Built from a fill, a radius and a padding rather than a panel + * helper, which takes one padding value where this card's insets are + * asymmetric. + */ + private static void renderCard(SectionBuilder cell, InvoicePaymentBlock payment) { + cell.spacing(0) + .fillColor(CARD_SURFACE) + .cornerRadius(CARD_RADIUS) + .padding(CARD_PAD) + .keepTogether(); + + PaymentsWidgets.layeredRow(cell, "CardHead", row -> { + row.spacing(CARD_HEAD_GAP) + .verticalAlign(RowVerticalAlign.TOP) + // The disc's own inset lives inside this column, so the column + // has to be wide enough for both — otherwise the heading + // starts on the disc's left edge plus the gap rather than on + // its right edge plus the gap. + .columns(DocumentRowColumn.fixed(CARD_DISC_INSET + CARD_DISC_D), + DocumentRowColumn.weight(1)); + row.addSection("CardDisc", disc -> disc + .spacing(0) + .padding(new DocumentInsets(0, 0, 0, CARD_DISC_INSET)) + .add(PaymentsWidgets.disc(PaymentsIcons.BANK, CARD_DISC_SURFACE, + CARD_DISC_D, CARD_GLYPH))); + row.addParagraph(p -> p + .name("CardLabel") + .text(payment.heading()) + .textStyle(style(CARD_HEAD_SIZE, ACCENT, DocumentTextDecoration.BOLD)) + .margin(new DocumentInsets( + capOffset(1078 - 1065, CARD_HEAD_SIZE), 0, 0, 0))); + }); + + // Two stacks, as in the metadata grid: the rows line up on a shared + // pitch rather than on a two-column row each. + PaymentsWidgets.layeredRow(cell, "CardFields", row -> { + row.spacing(0) + .verticalAlign(RowVerticalAlign.TOP) + // Measured from the head ROW's bottom edge, which the disc + // sets — not from the heading's own line box, which is + // shorter than the disc and therefore not what the fields + // follow. + .margin(new DocumentInsets(px(1110 - CARD_HEAD_BOTTOM_PX) + - TEXT_TOP_BEARING * CARD_LABEL_SIZE, 0, 0, CARD_TEXT_INDENT)) + .columns(DocumentRowColumn.fixed(CARD_LABEL_W), DocumentRowColumn.weight(1)); + row.addSection("CardFieldLabels", labels -> { + labels.spacing(gap(CARD_FIELD_PITCH, CARD_LABEL_SIZE)); + int index = 0; + for (InvoicePaymentBlock.Field field : payment.fields()) { + String name = "CardFieldLabel" + (++index); + labels.addParagraph(p -> p + .name(name) + .text(field.label()) + .textStyle(style(CARD_LABEL_SIZE, INK, DocumentTextDecoration.BOLD))); + } + }); + row.addSection("CardFieldValues", values -> { + values.spacing(gap(CARD_FIELD_PITCH, CARD_VALUE_SIZE)); + int index = 0; + for (InvoicePaymentBlock.Field field : payment.fields()) { + String name = "CardFieldValue" + (++index); + values.addParagraph(p -> p + .name(name) + .text(field.value()) + .textStyle(style(CARD_VALUE_SIZE, BODY, + DocumentTextDecoration.DEFAULT))); + } + }); + }); + } + + private static void renderTotals(SectionBuilder cell, InvoiceTotalsBlock totals, + String currencyCode) { + cell.addSection("Totals", block -> { + block.spacing(0) + .padding(new DocumentInsets(0, TOTALS_PAD_R, 0, TOTALS_TEXT_INSET)) + .margin(new DocumentInsets( + capOffset(1082 - 1060, TOTALS_LABEL_SIZE), 0, 0, 0)) + .keepTogether(); + PaymentsWidgets.layeredRow(block, "TotalsGrid", row -> { + row.spacing(0).verticalAlign(RowVerticalAlign.TOP).evenWeights(); + row.addSection("TotalsLabels", labels -> { + labels.spacing(gap(TOTALS_PITCH, TOTALS_LABEL_SIZE)); + int index = 0; + for (InvoiceTotalsBlock.Row entry : totals.rows()) { + String name = "TotalsLabel" + (++index); + labels.addParagraph(p -> p + .name(name) + .text(entry.label()) + .textStyle(style(TOTALS_LABEL_SIZE, INK, + DocumentTextDecoration.BOLD))); + } + }); + row.addSection("TotalsValues", values -> { + values.spacing(gap(TOTALS_PITCH, TOTALS_VALUE_SIZE)); + int index = 0; + for (InvoiceTotalsBlock.Row entry : totals.rows()) { + String name = "TotalsValue" + (++index); + values.addParagraph(p -> p + .name(name) + .text(PaymentsText.amount(currencyCode, entry.amount())) + .textStyle(style(TOTALS_VALUE_SIZE, BODY, + DocumentTextDecoration.DEFAULT)) + .align(TextAlign.RIGHT)); + } + }); + }); + }); + + cell.addLine(line -> line + .name("TotalsRule") + .horizontal(TOTALS_RULE_W) + .thickness(RULE_MED) + .color(DIVIDER) + // Measured cap-to-rule, less what the last label's box hangs + // below its own cap: the line box minus the top bearing, not the + // whole line box. + .margin(new DocumentInsets( + px(1154 - 1120) - (LINE_BOX - TEXT_TOP_BEARING) * TOTALS_LABEL_SIZE, + 0, + px(1173 - 1156), + 0))); + } + + /** + * Navy fill owning white type. CENTER, not TOP: the amount is centred + * against the label block rather than aligned to its first line. + * + *

    The line under the label is the payment block's due notice — the sheet + * says when the money is wanted twice, once here beside the amount and once + * in the footer, and both come from the same field.

    + */ + private static void renderDuePanel(SectionBuilder cell, InvoicePaymentBlock payment, + InvoiceTotalsBlock totals, String currencyCode) { + cell.addSection("TotalDuePanel", panel -> { + panel.spacing(0) + .fillColor(INK) + .cornerRadius(DUE_RADIUS) + .padding(DUE_PAD) + .keepTogether(); + PaymentsWidgets.layeredRow(panel, "TotalDueRow", row -> { + row.spacing(0).verticalAlign(RowVerticalAlign.CENTER).evenWeights(); + row.addSection("TotalDueText", text -> { + text.spacing(0); + text.addParagraph(p -> p + .name("TotalDueLabel") + .text(totals.totalLabel()) + .textStyle(style(TOTAL_DUE_LABEL_SIZE, SURFACE, + DocumentTextDecoration.BOLD))); + if (!payment.dueNotice().isBlank()) { + text.addParagraph(p -> p + .name("TotalDueSubLabel") + .text(payment.dueNotice()) + .textStyle(style(TOTAL_DUE_SUB_SIZE, SURFACE, + DocumentTextDecoration.DEFAULT)) + .margin(new DocumentInsets( + px(DUE_SUB_PITCH) - LINE_BOX * TOTAL_DUE_LABEL_SIZE + + TEXT_TOP_BEARING + * (TOTAL_DUE_LABEL_SIZE - TOTAL_DUE_SUB_SIZE), + 0, 0, 0))); + } + }); + row.addParagraph(p -> p + .name("TotalDueValue") + .text(PaymentsText.amount(currencyCode, totals.totalAmount())) + .textStyle(style(TOTAL_DUE_VALUE_SIZE, SURFACE, + DocumentTextDecoration.BOLD)) + .align(TextAlign.RIGHT)); + }); + }); + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/PaymentsStyles.java b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/PaymentsStyles.java new file mode 100644 index 000000000..8c76cc924 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/PaymentsStyles.java @@ -0,0 +1,456 @@ +package com.demcha.compose.document.templates.invoice.presets; + +import com.demcha.compose.document.api.DocumentPageSize; +import com.demcha.compose.document.style.DocumentColor; +import com.demcha.compose.document.style.DocumentInsets; +import com.demcha.compose.document.style.DocumentStroke; +import com.demcha.compose.document.style.DocumentTextDecoration; +import com.demcha.compose.document.style.DocumentTextStyle; +import com.demcha.compose.document.table.DocumentTableStyle; +import com.demcha.compose.document.table.DocumentTableTextAnchor; +import com.demcha.compose.font.FontName; + +/** + * Design tokens for {@link PaymentsInvoice} — the palette, the type scale and + * the geometry every part of the sheet measures against. + * + *

    One scale, and one content box

    + * + *

    The design was drawn at 1055 px across an A4 page, so {@link #PX} converts + * every length below from that raster to points. The content box is the + * table's box, not the text's: body copy sits a further + * {@link #TEXT_INSET} inside it, while the table, the payment card and the + * total-due panel sit on it exactly, which is what makes it the page's real + * edge.

    + * + *

    Ink measurements against box measurements

    + * + *

    Every vertical distance read off the design is between two pieces of ink; + * every one the engine accepts is between two boxes. Text sits about + * {@link #TEXT_TOP_BEARING} of its size below its own box top — the half-leading + * of a 1.2× line box plus the run from the ascender down to the cap — so a gap + * read ink-to-ink loses that before it can be a margin. {@link #gap} and + * {@link #capOffset} are the two places that conversion happens.

    + * + *

    Sizes come from widths, not heights

    + * + *

    Each size below is a measured ink width divided by the face's own + * advance for the same string at unit cap. The two disagree for all-caps runs, + * where the body face sets about 6% wider than the design's at the same cap + * height, and width is the one to match: it keeps every glyph after the first in + * place, where matching the height would leave the run's right edge short by 6% + * of its length.

    + */ +final class PaymentsStyles { + + private PaymentsStyles() { + } + + static final DocumentPageSize PAGE = DocumentPageSize.A4; + static final double PAGE_WIDTH = PAGE.width(); + + /** The design's raster is 1055 px wide; every length below came off it. */ + static final double PX = PAGE_WIDTH / 1055.0; + + static final double MARGIN_L = px(51); + static final double MARGIN_R = px(52); + static final double MARGIN_T = px(28); + + /** + * The band the page number sits in, measured up from the paper's edge, and + * page one's bottom margin, which is the same thing. + * + *

    The design is a one-page sheet and has no page number, but this preset + * flows, and a financial record that paginates has to make a missing page + * detectable. The band is reserved rather than merely drawn into: on a + * paginated invoice page one is all table, and without the margin the last + * row runs down to where the number is.

    + */ + static final double PAGE_NUMBER_BAND = px(32); + static final double MARGIN_B_FIRST = PAGE_NUMBER_BAND; + + /** + * A continuation page reserves more. Its last table row would otherwise end + * against the paper rather than against a margin, which page one never shows + * because its content stops well above the fold. + */ + static final double MARGIN_B_LATER = px(58); + + static final DocumentInsets PAGE_MARGIN = + new DocumentInsets(MARGIN_T, MARGIN_R, MARGIN_B_FIRST, MARGIN_L); + static final DocumentInsets CONTINUATION_MARGIN = + new DocumentInsets(MARGIN_T, MARGIN_R, MARGIN_B_LATER, MARGIN_L); + + /** Every width in this preset is a fraction of this: 952 design px. */ + static final double CONTENT_W = PAGE_WIDTH - MARGIN_L - MARGIN_R; + + /** How far body copy sits inside the content box the table defines. */ + static final double TEXT_INSET = px(11); + + static final DocumentColor ACCENT = DocumentColor.rgb(99, 91, 255); + static final DocumentColor INK = DocumentColor.rgb(14, 24, 72); + static final DocumentColor BODY = DocumentColor.rgb(46, 54, 92); + static final DocumentColor MUTED = DocumentColor.rgb(70, 76, 110); + static final DocumentColor SURFACE = DocumentColor.WHITE; + + /** + * Three lavender surfaces, not one. Sampled as rings inside each disc, clear + * of the glyph: the party and line item discs agree, the notes disc is two + * steps paler and the payment card's is two steps deeper. They look like one + * colour and measure as three. + */ + static final DocumentColor ACCENT_SURFACE = DocumentColor.rgb(224, 218, 253); + static final DocumentColor NOTES_SURFACE = DocumentColor.rgb(226, 222, 253); + static final DocumentColor CARD_DISC_SURFACE = DocumentColor.rgb(216, 208, 252); + static final DocumentColor CARD_SURFACE = DocumentColor.rgb(248, 247, 254); + static final DocumentColor TABLE_HEAD_FILL = DocumentColor.rgb(240, 238, 254); + static final DocumentColor BAND_LAVENDER = DocumentColor.rgb(205, 194, 248); + static final DocumentColor HAIRLINE = DocumentColor.rgb(227, 227, 238); + static final DocumentColor RULE_SOFT = DocumentColor.rgb(213, 208, 239); + static final DocumentColor RULE_STRONG = DocumentColor.rgb(159, 145, 222); + static final DocumentColor DIVIDER = DocumentColor.rgb(199, 191, 236); + + static final FontName FACE = FontName.LATO; + + /** + * The face's cap heights in ems. Regular and bold differ, and the difference + * matters: every size below is a measured cap divided by the ratio for the + * weight it is set in. + */ + private static final double CAP_RATIO_REGULAR = 0.7165; + private static final double CAP_RATIO_BOLD = 0.723; + + /** How far below its own box top a line of text sits. */ + static final double TEXT_TOP_BEARING = 0.235; + + /** + * {@code lineSpacing} is points added between lines over a line box + * of 1.2× the type size, so a measured pitch converts as + * {@code pitch - 1.2 * size}, never as a ratio. Section spacing works the + * same way: it is the gap between sibling boxes, so a measured top-to-top + * pitch has the first box's height taken off it. + */ + static final double LINE_BOX = 1.2; + + static final double TITLE_SIZE = sizeB(46.53); + static final double ISSUER_NAME_SIZE = sizeB(14.65); + static final double PARTY_NAME_SIZE = sizeB(13.11); + static final double PARTY_LABEL_SIZE = sizeB(10.91); + static final double META_LABEL_SIZE = sizeB(11.59); + static final double META_VALUE_SIZE = sizeR(10.35); + static final double BODY_SIZE = sizeR(11.08); + static final double TABLE_HEAD_SIZE = sizeB(10.22); + + /** The currency qualifier after the amount label is a smaller run of it. */ + static final double TABLE_HEAD_SMALL_SIZE = sizeB(8.0); + static final double ITEM_TITLE_SIZE = sizeB(12.18); + static final double ITEM_SUB_SIZE = sizeR(10.17); + static final double CELL_SIZE = sizeR(11.19); + static final double CELL_AMOUNT_SIZE = sizeB(12.25); + static final double CARD_HEAD_SIZE = sizeB(11.30); + static final double CARD_LABEL_SIZE = sizeB(9.44); + static final double CARD_VALUE_SIZE = sizeR(9.33); + static final double TOTALS_LABEL_SIZE = sizeB(12.07); + static final double TOTALS_VALUE_SIZE = sizeR(11.72); + static final double TOTAL_DUE_LABEL_SIZE = sizeB(13.04); + static final double TOTAL_DUE_SUB_SIZE = sizeR(10.79); + static final double TOTAL_DUE_VALUE_SIZE = sizeB(22.18); + static final double NOTES_LABEL_SIZE = sizeB(10.29); + static final double NOTES_BODY_SIZE = sizeR(10.07); + static final double FOOTER_DUE_TITLE_SIZE = sizeB(10.16); + static final double FOOTER_SUPPORT_TITLE_SIZE = sizeB(11.12); + static final double FOOTER_SUB_SIZE = sizeR(10.05); + static final double PAGE_NUMBER_SIZE = sizeR(7.5); + + /* + * The diagonal band: two slanted quadrilaterals in the gutter between the + * lockup and the title, the lavender running from the paper's top edge and + * the navy drawn over it from a third of the way down. Corners are given in + * PAGE design pixels — the coordinates that were actually measured — and + * normalised in one place by nx/ny, because a path's own coordinates run + * 0..1 inside its box with the origin at the BOTTOM-LEFT, which is the + * opposite of the direction every measurement here was taken in. + */ + static final double BAND_LEFT_PX = 385.4; + static final double BAND_RIGHT_PX = 556.0; + static final double BAND_BOTTOM_PX = 360.0; + static final double BAND_W = px(BAND_RIGHT_PX - BAND_LEFT_PX); + static final double BAND_H = px(BAND_BOTTOM_PX); + + /** Top-left, top-right, bottom-right, bottom-left, in page design pixels. */ + static final double[][] BAND_LAVENDER_QUAD = { + {495.0, 0.0}, {539.6, 0.0}, {422.4, 360.0}, {385.4, 360.0} + }; + static final double[][] BAND_NAVY_QUAD = { + {507.7, 98.0}, {554.7, 98.0}, {469.3, 360.0}, {422.3, 360.0} + }; + + /** + * How tall the masthead's layer stack comes out. The band is the tallest + * layer in it, so the stack's own height is the band's — and the header rule + * that follows the stack has to be pulled back up by the difference between + * the band's bottom and where the stack ends. The band is offset up by + * {@link #MARGIN_T} to reach the paper, and the stack is sized to its layers + * without accounting for that offset, so the stack ends that much below the + * band's visible bottom. + */ + static final double HEADER_STACK_BOTTOM_PX = BAND_BOTTOM_PX + 28.0; + + /** + * Where the metadata column starts, as an offset into the content box. The + * title shares it: the title is a left-anchored member of the metadata + * column, not a right-anchored member of the page, and its measured left + * edge is the metadata labels' to the pixel. + */ + static final double META_COL_X = px(639 - 51); + + /** + * The lockup's box, measured off the design's own ink: 136 design px wide + * and 55.3 tall, starting at {@link #BRAND_LOCKUP_INSET} into the content + * box. A caller's logo is sized by the height, so a wider or narrower mark + * keeps the design's optical weight and stays on the same top edge. + */ + static final double BRAND_LOCKUP_W = px(136); + static final double BRAND_LOCKUP_H = px(55.3); + static final double BRAND_LOCKUP_INSET = px(63 - 51); + + /** + * The size a wordmark is set at when a document brings a name rather than a + * logo. Not the lockup's own height: a name is usually longer than a + * drawn mark and set at that height would run out of the column, so it is + * sized to read as a lockup beside the title rather than to fill the box. + */ + static final double WORDMARK_SIZE = sizeB(28); + + /** + * The lockup sets the row's top edge, so the title's own line box needs + * pushing down to its measured cap. The 1.4 px correction is measured, not + * derived: at 46 design px of cap the top bearing runs slightly deeper than + * the fraction that holds at body sizes. + */ + static final double TITLE_DROP = px(65 - 46 - 1.4) - TEXT_TOP_BEARING * TITLE_SIZE; + + static final double ACCENT_BAR_W = px(2); + static final double ACCENT_BAR_H = px(310); + + /** The bar is anchored to the PAGE, 22 design px left of the content box. */ + static final double ACCENT_BAR_OFFSET = px(-22); + + static final double META_LABEL_W = px(781.5 - 639); + static final double META_VALUE_INSET = px(805 - 781.5); + static final double META_PITCH = 30.17; + + static final double HEADER_RULE_W = px(1018 - 30); + + /** How far the rule reaches past the content box: 21 design px left, 15 right. */ + static final double HEADER_RULE_OVERHANG_L = px(51 - 30); + static final double HEADER_RULE_OVERHANG_R = px(1018 - 1003); + static final double RULE_THIN = px(1.4); + static final double RULE_MED = px(2.0); + + static final double DISC_D = px(40); + + /** + * Box, not ink. An icon's size is the box the artwork is fitted into, + * and every glyph here carries about two units of padding on each side of + * its 24-unit viewBox — so the ink comes back at roughly this fraction of + * what is asked for. Each glyph size below is therefore the design's + * measured ink divided by that ratio, which is why they read larger than the + * marks they produce. + */ + static final double GLYPH_INK_RATIO = 0.75; + static final double DISC_GLYPH = glyph(21); + static final double PARTY_DISC_INSET = px(60 - 51); + static final double PARTY_DISC_GAP = px(118 - 100); + static final double PARTY_TEXT_INDENT = DISC_D + PARTY_DISC_GAP; + + /** The second cell's own left padding, past the divider that is its left border. */ + static final double PARTY_CELL_INSET = px(567 - 527); + static final double PARTY_ADDRESS_PITCH = 20.7; + + /** + * Column shares of the content width. Derived from each column's measured + * anchor rather than from boundaries the design never draws: the + * description text's left edge, the quantity digits' centre, the unit + * price's right edge, the tax centre and the amount's right edge. + */ + static final double[] COLUMN_SHARES = { + 379.0 / 952.0, 127.0 / 952.0, 143.0 / 952.0, 139.0 / 952.0, 164.0 / 952.0 + }; + + static final double HEAD_PAD_T = px(11.7); + static final double HEAD_PAD_B = px(12.3); + static final double CELL_PAD_V = px(8.5); + + /** The description cell's own inset; the disc sits on it and the text past it. */ + static final double ITEM_CELL_INSET = px(62 - 51); + static final double ITEM_DISC_GAP = px(118 - 102); + + /** + * What a line's description wraps at. Not a free choice: the design breaks + * one description before its parenthetical and keeps the next whole, which + * puts the width between 248 and 256 design px. Everything about the table's + * row heights follows from it. + */ + static final double ITEM_TEXT_W = px(250); + static final double ITEM_SUB_PITCH = 18.0; + static final double CELL_PAD_R = px(22); + static final double AMOUNT_PAD_R = px(23); + static final double DESC_PAD_L = px(118 - 51); + + static final DocumentTableStyle HEADER_CELL_STYLE = DocumentTableStyle.builder() + .fillColor(TABLE_HEAD_FILL) + .stroke(DocumentStroke.of(HAIRLINE, RULE_THIN)) + .padding(new DocumentInsets(HEAD_PAD_T, 0, HEAD_PAD_B, 0)) + .textAnchor(DocumentTableTextAnchor.CENTER_LEFT) + .build(); + + /** + * Every rule in the table comes from this one stroke. A cell strokes all + * four of its own edges and a one-column table has no interior vertical edge + * — so the same stroke that draws the separator between two rows draws the + * table's left and right sides, and nothing else. That is exactly the + * topology the design shows. + */ + static final DocumentTableStyle ITEM_CELL_STYLE = DocumentTableStyle.builder() + .stroke(DocumentStroke.of(HAIRLINE, RULE_THIN)) + .padding(new DocumentInsets(CELL_PAD_V, 0, CELL_PAD_V, 0)) + .textAnchor(DocumentTableTextAnchor.CENTER_LEFT) + .build(); + + /** + * The settlement split is NOT the even split the two rows above it use: the + * card is 440 design px, the totals cell 455, and the divider sits inside + * the gutter rather than on either cell's edge. That asymmetry is in the + * design, not in the measurement. + */ + static final double CARD_W = px(440); + static final double SETTLEMENT_GUTTER = px(527.5 - 491); + static final double SETTLEMENT_RIGHT_INSET = px(547 - 527.5); + + static final double CARD_RADIUS = px(10); + + /** + * The card's own disc is smaller than the party and line item discs — 38 + * design px against 40 — and it sits 5 px below the card's top edge, not 12. + * Both were measured off the card rather than assumed from the other three, + * and both matter: the disc sets the head row's height, and that height is + * what the field block's margin is measured from. + */ + static final double CARD_DISC_D = px(38); + static final double CARD_GLYPH = glyph(21); + static final DocumentInsets CARD_PAD = + new DocumentInsets(px(1065 - 1060), px(20), px(1252 - 1238.5), 0); + static final double CARD_DISC_INSET = px(63.5 - 51); + static final double CARD_HEAD_GAP = px(118 - 101); + static final double CARD_LABEL_W = px(256 - 116); + static final double CARD_TEXT_INDENT = px(116 - 51); + static final double CARD_FIELD_PITCH = 19.3; + + /** The disc's bottom edge, measured down from the card's content top. */ + static final double CARD_HEAD_BOTTOM_PX = 1065 + 38; + + static final double TOTALS_TEXT_INSET = px(566 - 547); + static final double TOTALS_PAD_R = px(1003 - 987); + static final double TOTALS_PITCH = 38.0; + static final double TOTALS_RULE_W = px(1002 - 547); + + static final double DUE_RADIUS = px(10); + + /** + * The top inset is the distance to the label's box, not to its cap: + * the measured cap gap less what the text hangs below its own box top. + * Taking the cap gap as a padding makes the panel 4 px too tall and pushes + * its bottom past the card's, which the design has ending on the same line. + */ + static final DocumentInsets DUE_PAD = new DocumentInsets( + px(1194 - 1173) - TEXT_TOP_BEARING * TOTAL_DUE_LABEL_SIZE, + px(1002 - 984), + px(1251 - 1235), + px(566 - 547)); + static final double DUE_SUB_PITCH = 28.0; + + static final double NOTES_DISC_D = px(39); + static final double NOTES_DISC_INSET = px(53 - 51); + static final double NOTES_TEXT_INDENT = px(106 - 53); + static final double NOTES_GLYPH = glyph(22); + static final double NOTES_PITCH = 22.0; + + static final double FOOTER_RULE_W = px(1005 - 50); + + /** + * The rule's own gaps. The 1.2 px correction is measured and is taken off + * the gap below as well as added to the gap above: the footer's text already + * sits on the design's line, so the rule has to move without taking the + * footer with it. + */ + static final double FOOTER_RULE_NUDGE = px(1.2); + static final double FOOTER_RULE_GAP_ABOVE = px(1373 - 1343) + FOOTER_RULE_NUDGE; + static final double FOOTER_RULE_GAP_BELOW = px(1397 - 1374) - FOOTER_RULE_NUDGE; + + /** One design px past the content box on the left, two on the right. Measured. */ + static final double FOOTER_RULE_OVERHANG_L = px(51 - 50); + static final double FOOTER_RULE_OVERHANG_R = px(1005 - 1003); + static final double FOOTER_SPLIT_X = px(541 - 51); + static final double FOOTER_DUE_ICON_COL = px(112 - 55); + static final double FOOTER_DUE_ICON_INSET = px(55 - 51); + static final double FOOTER_DUE_ICON = glyph(37); + static final double FOOTER_SUPPORT_CELL_INSET = px(594 - 541); + static final double FOOTER_SUPPORT_ICON_COL = px(659 - 594); + static final double FOOTER_SUPPORT_ICON = glyph(39); + + static double px(double designPixels) { + return designPixels * PX; + } + + /** + * An icon box sized so its ink comes out at {@code inkPx} design pixels — + * see {@link #GLYPH_INK_RATIO}. Every glyph goes through here, so the one + * measured ratio lives in one place. + */ + static double glyph(double inkPx) { + return px(inkPx) / GLYPH_INK_RATIO; + } + + /** Half the padding {@link #glyph} adds — what a left-anchored glyph gives back. */ + static double glyphPad(double inkPx) { + return (glyph(inkPx) - px(inkPx)) / 2.0; + } + + /** A regular-weight size named by the cap height it reproduces, in design px. */ + static double sizeR(double capPx) { + return px(capPx) / CAP_RATIO_REGULAR; + } + + /** The same for bold, whose cap ratio differs. */ + static double sizeB(double capPx) { + return px(capPx) / CAP_RATIO_BOLD; + } + + /** + * Where to start a text box so its caps land {@code capPx} below the top of + * the block it shares a row with — a label beside a glyph, whose design + * position is its own cap top rather than the glyph's centre. + */ + static double capOffset(double capPx, double size) { + return Math.max(0, px(capPx) - TEXT_TOP_BEARING * size); + } + + /** + * A measured top-to-top pitch turned into the gap the engine wants: the + * first box's own height comes off it, because spacing is between boxes. + */ + static double gap(double pitchPx, double sizeOfFirst) { + return Math.max(0, px(pitchPx) - LINE_BOX * sizeOfFirst); + } + + static DocumentTextStyle style(double size, DocumentColor color, + DocumentTextDecoration decoration) { + return DocumentTextStyle.builder() + .fontName(FACE) + .size(size) + .color(color) + .decoration(decoration) + .build(); + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/PaymentsText.java b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/PaymentsText.java new file mode 100644 index 000000000..827ce83cf --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/PaymentsText.java @@ -0,0 +1,62 @@ +package com.demcha.compose.document.templates.invoice.presets; + +import java.math.BigDecimal; +import java.text.NumberFormat; +import java.util.Currency; +import java.util.Locale; + +/** + * How the Payments sheet writes its figures. + * + *

    The locale is stated, never inherited

    + * + *

    Both the grouping of a number and the symbol for a currency code are + * locale-dependent — {@code USD} is {@code $} in English and {@code US$} in the + * root locale, {@code JPY} is {@code ¥} or {@code JP¥} — so a preset that let + * the JVM's default locale decide would render a different sheet on a different + * machine, and its baselines would move with the machine rather than with the + * design. Both helpers below pin {@link Locale#ENGLISH}.

    + */ +final class PaymentsText { + + private PaymentsText() { + } + + /** An amount, grouped and always to two places. */ + static String money(BigDecimal value) { + NumberFormat format = NumberFormat.getNumberInstance(Locale.ENGLISH); + format.setGroupingUsed(true); + format.setMinimumFractionDigits(2); + format.setMaximumFractionDigits(2); + return format.format(value); + } + + /** A quantity, written without trailing zeros a whole number does not need. */ + static String quantity(BigDecimal value) { + BigDecimal stripped = value.stripTrailingZeros(); + return stripped.scale() <= 0 ? stripped.toBigInteger().toString() : stripped.toPlainString(); + } + + /** + * The symbol for an ISO currency code, or the code itself when there is no + * symbol for it and the empty string when a document states no currency. + * + * @param currencyCode the ISO code the figures are stated in + * @return what to print before an amount + */ + static String symbol(String currencyCode) { + if (currencyCode == null || currencyCode.isBlank()) { + return ""; + } + try { + return Currency.getInstance(currencyCode.trim()).getSymbol(Locale.ENGLISH); + } catch (IllegalArgumentException notACurrency) { + return currencyCode.trim(); + } + } + + /** An amount behind its currency's symbol. */ + static String amount(String currencyCode, BigDecimal value) { + return symbol(currencyCode) + money(value); + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/PaymentsWidgets.java b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/PaymentsWidgets.java new file mode 100644 index 000000000..0fffc324d --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/PaymentsWidgets.java @@ -0,0 +1,129 @@ +package com.demcha.compose.document.templates.invoice.presets; + +import com.demcha.compose.document.dsl.ParagraphBuilder; +import com.demcha.compose.document.dsl.RowBuilder; +import com.demcha.compose.document.dsl.SectionBuilder; +import com.demcha.compose.document.dsl.ShapeContainerBuilder; +import com.demcha.compose.document.node.DocumentLinkOptions; +import com.demcha.compose.document.node.DocumentNode; +import com.demcha.compose.document.node.HorizontalAlign; +import com.demcha.compose.document.node.LayerAlign; +import com.demcha.compose.document.style.DocumentColor; +import com.demcha.compose.document.style.DocumentTextDecoration; + +import java.util.List; +import java.util.function.Consumer; + +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.DISC_D; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.DISC_GLYPH; +import static com.demcha.compose.document.templates.invoice.presets.PaymentsStyles.style; + +/** + * The pieces more than one part of the Payments sheet draws: the wrapper every + * horizontal pair inside a cell goes through, the disc that owns its glyph, and + * the two text helpers. + */ +final class PaymentsWidgets { + + private PaymentsWidgets() { + } + + /** + * A row that survives inside a row cell. + * + *

    A row nested directly in a row cell is refused by the layout compiler. + * Every horizontal pair that lives inside a cell in this preset goes through + * here, wrapped in a single layer of a stack.

    + * + * @param parent the section to add the row to + * @param name the node name, for review and rollback + * @param spec describes the row + */ + static void layeredRow(SectionBuilder parent, String name, Consumer spec) { + SectionBuilder holder = new SectionBuilder(); + holder.name(name + "Holder"); + holder.addRow(name, spec); + DocumentNode node = holder.build(); + parent.addLayerStack(stack -> stack + .name(name + "Layer") + .layer(node, LayerAlign.TOP_LEFT, 0)); + } + + /** + * A filled disc that owns its glyph: the glyph sits inside the fill, so it + * is a child of the shape rather than a coloured circle with an icon + * dropped near it. + * + * @param token the packaged mark + * @param fill the disc colour + * @return the disc node + */ + static DocumentNode disc(String token, DocumentColor fill) { + return disc(token, fill, DISC_D, DISC_GLYPH); + } + + /** + * The same, at a stated diameter — the payment card's and the notes' discs + * are each their own size, measured off the block they belong to. + * + * @param token the packaged mark + * @param fill the disc colour + * @param diameter the disc's diameter + * @param glyph the glyph's box inside it + * @return the disc node + */ + static DocumentNode disc(String token, DocumentColor fill, double diameter, double glyph) { + return new ShapeContainerBuilder() + .name("Disc" + token) + .circle(diameter) + .fillColor(fill) + .center(glyphNode(token, glyph, HorizontalAlign.CENTER)) + .build(); + } + + /** + * A glyph in a box of its own, so a shape can own it. A glyph that leads a + * text row is aligned left: its column's width is the distance to the text, + * not a box for the glyph to sit in the middle of. + * + * @param token the packaged mark + * @param size the glyph's box + * @param align where the glyph sits in it + * @return the glyph node + */ + static DocumentNode glyphNode(String token, double size, HorizontalAlign align) { + return new SectionBuilder() + .name("Glyph" + token) + .spacing(0) + .addSvgIcon(PaymentsIcons.icon(token), size, align) + .build(); + } + + /** + * A block of plain lines, one paragraph each. + * + * @param block the section to fill + * @param prefix the node-name prefix, numbered from one + * @param lines the lines + * @param size their type size + * @param color their colour + */ + static void textLines(SectionBuilder block, String prefix, List lines, + double size, DocumentColor color) { + int index = 0; + for (String line : lines) { + String name = prefix + (++index); + block.addParagraph(p -> p + .name(name) + .text(line) + .textStyle(style(size, color, DocumentTextDecoration.DEFAULT))); + } + } + + /** The paragraph, made reachable when there is somewhere for it to point. */ + static ParagraphBuilder linked(ParagraphBuilder paragraph, String href) { + return href == null || href.isBlank() + ? paragraph + : paragraph.link(new DocumentLinkOptions(href)); + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/PlatformClosing.java b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/PlatformClosing.java new file mode 100644 index 000000000..3cf1d3674 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/PlatformClosing.java @@ -0,0 +1,200 @@ +package com.demcha.compose.document.templates.invoice.presets; + +import com.demcha.compose.document.dsl.ImageBuilder; +import com.demcha.compose.document.dsl.PageFlowBuilder; +import com.demcha.compose.document.dsl.ParagraphBuilder; +import com.demcha.compose.document.node.DocumentLinkOptions; +import com.demcha.compose.document.node.TextAlign; +import com.demcha.compose.document.style.DocumentInsets; +import com.demcha.compose.document.style.DocumentRowColumn; +import com.demcha.compose.document.templates.core.identity.ContactUri; +import com.demcha.compose.document.templates.data.invoice.InvoiceBrand; +import com.demcha.compose.document.templates.data.invoice.InvoiceContactBlock; +import com.demcha.compose.document.templates.data.invoice.InvoiceNotesBlock; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; + +import static com.demcha.compose.document.templates.invoice.presets.PlatformStyles.ACCENT; +import static com.demcha.compose.document.templates.invoice.presets.PlatformStyles.BODY; +import static com.demcha.compose.document.templates.invoice.presets.PlatformStyles.CAP_INSET; +import static com.demcha.compose.document.templates.invoice.presets.PlatformStyles.CONTENT_W; +import static com.demcha.compose.document.templates.invoice.presets.PlatformStyles.FOOTER_LOCKUP_W; +import static com.demcha.compose.document.templates.invoice.presets.PlatformStyles.HAIRLINE; +import static com.demcha.compose.document.templates.invoice.presets.PlatformStyles.INK; +import static com.demcha.compose.document.templates.invoice.presets.PlatformStyles.LINE_BOX; +import static com.demcha.compose.document.templates.invoice.presets.PlatformStyles.MUTED; +import static com.demcha.compose.document.templates.invoice.presets.PlatformStyles.NOTE_GUTTER; +import static com.demcha.compose.document.templates.invoice.presets.PlatformStyles.NOTE_ICON; +import static com.demcha.compose.document.templates.invoice.presets.PlatformStyles.NOTE_SIZE; +import static com.demcha.compose.document.templates.invoice.presets.PlatformStyles.RULE_THICK; +import static com.demcha.compose.document.templates.invoice.presets.PlatformStyles.TINY_SIZE; +import static com.demcha.compose.document.templates.invoice.presets.PlatformStyles.bold; +import static com.demcha.compose.document.templates.invoice.presets.PlatformStyles.capPitch; +import static com.demcha.compose.document.templates.invoice.presets.PlatformStyles.px; +import static com.demcha.compose.document.templates.invoice.presets.PlatformStyles.py; +import static com.demcha.compose.document.templates.invoice.presets.PlatformStyles.style; + +/** + * The foot of the sheet: a rule, the closing note beside its mark, a second + * rule, and the identity band pairing the lockup against the issuer's address. + */ +final class PlatformClosing { + + private PlatformClosing() { + } + + /** The rule above the closing note. */ + static void renderNoteRule(PageFlowBuilder page) { + page.addLine(line -> line + .name("ClosingNoteRule") + .horizontal(CONTENT_W) + .thickness(RULE_THICK) + .color(HAIRLINE) + .margin(new DocumentInsets(py(1364 - 1350), 0, 0, 0))); + } + + /** + * The closing note. + * + *

    The note tells a reader where to go, and the places it can send them are + * printed inside the sentence. Written as one string the whole sentence would + * have to carry the target or none of it would, so each address it actually + * contains becomes its own run, in the accent, and the text between them stays + * plain.

    + */ + static void renderNote(PageFlowBuilder page, InvoiceNotesBlock notes, + InvoiceContactBlock supplier) { + if (notes.heading().isBlank() && notes.paragraphs().isEmpty()) { + return; + } + page.addRow("ClosingNote", row -> { + row.spacing(0) + .columns(DocumentRowColumn.fixed(NOTE_GUTTER), DocumentRowColumn.weight(1)) + .margin(new DocumentInsets( + py(1392 - 1365) - CAP_INSET * NOTE_SIZE, 0, 0, 0)); + row.addSection("ClosingNoteIcon", gutter -> { + gutter.spacing(0); + gutter.addSvgIcon(PlatformIcons.icon(PlatformIcons.INFO_OUTLINE), NOTE_ICON); + }); + row.addSection("ClosingNoteText", text -> { + text.spacing(capPitch(22, NOTE_SIZE)) + .keepWithNext(); + if (!notes.heading().isBlank()) { + text.addParagraph(p -> p + .name("ClosingNoteLead") + .text(notes.heading()) + .textStyle(style(NOTE_SIZE, BODY))); + } + List paragraphs = notes.paragraphs(); + for (int i = 0; i < paragraphs.size(); i++) { + String prose = paragraphs.get(i); + int index = i; + text.addParagraph(p -> { + p.name("ClosingNoteLine_" + index); + writeReachable(p, prose, notes, supplier); + }); + } + }); + }); + } + + /** The rule above the identity band. */ + static void renderIdentityRule(PageFlowBuilder page) { + page.addLine(line -> line + .name("FooterIdentityRule") + .horizontal(CONTENT_W) + .thickness(RULE_THICK) + .color(HAIRLINE) + .margin(new DocumentInsets( + py(1447 - 1414) - (LINE_BOX - CAP_INSET) * NOTE_SIZE, 0, 0, 0))); + } + + /** + * The identity band: the lockup again, and the issuer's own name and address + * set against the right margin. + */ + static void renderIdentity(PageFlowBuilder page, InvoiceBrand brand, + InvoiceContactBlock supplier) { + page.addRow("FooterIdentity", row -> { + row.spacing(0) + .columns(DocumentRowColumn.fixed(FOOTER_LOCKUP_W), + DocumentRowColumn.weight(1)) + .margin(new DocumentInsets(py(1465 - 1448), 0, 0, px(1))); + row.addSection("FooterLockup", cell -> { + cell.spacing(0); + if (brand.logo() != null) { + cell.add(new ImageBuilder() + .name("FooterLogo") + .source(brand.logo()) + .width(FOOTER_LOCKUP_W) + .build()); + } else if (!brand.name().isBlank()) { + cell.addParagraph(p -> p + .name("FooterWordmark") + .text(brand.name()) + .textStyle(bold(NOTE_SIZE, INK))); + } + }); + row.addSection("FooterAddress", text -> { + text.spacing(capPitch(20, TINY_SIZE)) + .margin(new DocumentInsets( + py(1471 - 1465) - CAP_INSET * TINY_SIZE, 0, 0, 0)); + text.addParagraph(p -> p + .name("FooterIdentityName") + .text(supplier.legalName()) + .textStyle(style(TINY_SIZE, MUTED)) + .align(TextAlign.RIGHT)); + text.addParagraph(p -> p + .name("FooterIdentityAddress") + .text(String.join(", ", supplier.addressLines())) + .textStyle(style(TINY_SIZE, MUTED)) + .align(TextAlign.RIGHT)); + }); + }); + } + + private static void writeReachable(ParagraphBuilder paragraph, String prose, + InvoiceNotesBlock notes, InvoiceContactBlock supplier) { + List found = new ArrayList<>(); + addIfPresent(found, prose, notes.contactEmail(), ContactUri.mailLink(notes.contactEmail())); + addIfPresent(found, prose, supplier.email(), ContactUri.mailLink(supplier.email())); + addIfPresent(found, prose, supplier.website(), ContactUri.webLink(supplier.website())); + addIfPresent(found, prose, notes.contactPhone(), ContactUri.telLink(notes.contactPhone())); + found.sort(Comparator.comparingInt(Reachable::at)); + + int cursor = 0; + for (Reachable reachable : found) { + if (reachable.at() < cursor) { + // An address overlapping one already written — the site inside an + // address, say. The first one wins; writing both would duplicate + // the glyphs. + continue; + } + if (reachable.at() > cursor) { + paragraph.inlineText(prose.substring(cursor, reachable.at()), + style(NOTE_SIZE, BODY)); + } + paragraph.inlineText(reachable.text(), style(NOTE_SIZE, ACCENT), reachable.link()); + cursor = reachable.at() + reachable.text().length(); + } + if (cursor < prose.length()) { + paragraph.inlineText(prose.substring(cursor), style(NOTE_SIZE, BODY)); + } + } + + private static void addIfPresent(List found, String prose, String text, + DocumentLinkOptions link) { + if (text == null || text.isBlank() || link == null) { + return; + } + int at = prose.indexOf(text); + if (at >= 0) { + found.add(new Reachable(at, text, link)); + } + } + + private record Reachable(int at, String text, DocumentLinkOptions link) { + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/PlatformIcons.java b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/PlatformIcons.java new file mode 100644 index 000000000..c33114d03 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/PlatformIcons.java @@ -0,0 +1,83 @@ +package com.demcha.compose.document.templates.invoice.presets; + +import com.demcha.compose.document.svg.SvgIcon; + +import java.io.IOException; +import java.io.InputStream; +import java.io.UncheckedIOException; +import java.nio.charset.StandardCharsets; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +/** + * The marks the Platform invoice packages. + * + *

    Nine the preset chooses itself — which glyph opens the bill-to block, the + * ship-to block, the bank panel, the panel's note, the due-date card and each of + * the three contact channels. The other five are a vocabulary a document may + * name on a service line through {@code InvoiceServiceLines.Line.icon()}.

    + * + *

    Each mark carries the accent colour in the asset itself rather than taking + * it at draw time, which is how the design draws them: they are filled shapes in + * one colour, not line work tinted per use.

    + */ +final class PlatformIcons { + + static final String BILL_TO = "bill-to"; + static final String SHIP_TO = "ship-to"; + static final String BANK = "bank"; + static final String CALENDAR = "calendar"; + static final String INFO = "info"; + static final String INFO_OUTLINE = "info-outline"; + static final String WEBSITE = "website"; + static final String EMAIL = "email"; + static final String PHONE = "phone"; + + private static final String ICON_ROOT = "/templates/invoice/platform/icons/"; + + /** The marks a document may name on a service line. */ + static final Set LINE_TOKENS = + Set.of("compute", "storage", "database", "network", "support"); + + private static final Set TOKENS = Set.of( + BILL_TO, SHIP_TO, BANK, CALENDAR, INFO, INFO_OUTLINE, WEBSITE, EMAIL, PHONE, + "compute", "storage", "database", "network", "support"); + + private static final Map CACHE = new ConcurrentHashMap<>(); + + private PlatformIcons() { + } + + /** + * The packaged mark a token names. + * + * @param token one of this preset's tokens + * @return the parsed icon + * @throws IllegalArgumentException when the token names no packaged mark, + * listing the ones a document may choose — an icon token is data, so + * a wrong one is a data error and not a missing resource + */ + static SvgIcon icon(String token) { + if (!TOKENS.contains(token)) { + throw new IllegalArgumentException( + "Unknown platform invoice icon token '" + token + + "'. This preset packages " + LINE_TOKENS.stream().sorted().toList() + + " for a service line's mark."); + } + return CACHE.computeIfAbsent(token, PlatformIcons::read); + } + + private static SvgIcon read(String token) { + String resourcePath = ICON_ROOT + token + ".svg"; + try (InputStream input = PlatformIcons.class.getResourceAsStream(resourcePath)) { + if (input == null) { + throw new IllegalStateException("Missing platform invoice icon: " + resourcePath); + } + return SvgIcon.parse(new String(input.readAllBytes(), StandardCharsets.UTF_8)); + } catch (IOException e) { + throw new UncheckedIOException( + "Failed to read platform invoice icon: " + resourcePath, e); + } + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/PlatformInvoice.java b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/PlatformInvoice.java new file mode 100644 index 000000000..a14745072 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/PlatformInvoice.java @@ -0,0 +1,184 @@ +package com.demcha.compose.document.templates.invoice.presets; + +import com.demcha.compose.document.api.DocumentSession; +import com.demcha.compose.document.api.PageMarginRule; +import com.demcha.compose.document.output.DocumentHeaderFooter; +import com.demcha.compose.document.output.DocumentHeaderFooterZone; +import com.demcha.compose.document.style.DocumentInsets; +import com.demcha.compose.document.templates.api.DocumentTemplate; +import com.demcha.compose.document.templates.data.invoice.StructuredInvoiceData; + +import java.util.List; +import java.util.Objects; + +import static com.demcha.compose.document.templates.invoice.presets.PlatformStyles.FACE; +import static com.demcha.compose.document.templates.invoice.presets.PlatformStyles.FOOTER_BAND_H; +import static com.demcha.compose.document.templates.invoice.presets.PlatformStyles.FOOTER_SIZE; +import static com.demcha.compose.document.templates.invoice.presets.PlatformStyles.MUTED; +import static com.demcha.compose.document.templates.invoice.presets.PlatformStyles.PAGE; +import static com.demcha.compose.document.templates.invoice.presets.PlatformStyles.PAGE_MARGIN; + +/** + * Platform — an invoice for platform usage billed by region: a lockup against + * the title, the supplier split from the invoice's metadata by a full-height + * hairline, two addressed parties, a six-column usage table, an outlined bank + * panel beside the totals and the due-date card, and an identity band closing + * the sheet. + * + *

    Where, beside what

    + * + *

    The table is six columns wide because this design bills the same service in + * more than one place and prints where beside what: + * {@code DESCRIPTION | SERVICE | REGION | USAGE | UNIT PRICE | AMOUNT}. A line + * names its region through {@code InvoiceServiceLines.Line.region()}, and a + * document with one location per invoice leaves it blank.

    + * + *

    Two scales, on purpose

    + * + *

    The design is 1.50 aspect where A4 is 1.414, so one conversion constant + * cannot serve both axes. Widths, x-offsets and type sizes go through the + * horizontal scale because that is the axis where advance widths have to fit; + * heights, pitches and gaps go through the vertical one, which is 6.3% tighter; + * marks and discs go through their mean, and are therefore about 3% out in each + * axis rather than 6% out in one.

    + * + *

    Rules are rows

    + * + *

    A table's rules come from each cell's own style and cover all four of that + * cell's edges, so any stroke that buys a horizontal separator also buys five + * verticals — which this design has nowhere inside its table. Every cell is + * stroked at zero width and the separators are rows of their own: one cell + * spanning every column, zero padding, and a full-width line as its content.

    + * + *

    It flows

    + * + *

    The design shows five service lines and real usage brings dozens, so the + * table is the region that grows: its header repeats on every page it reaches, + * a continuation page keeps the first page's geometry, and every page carries + * its number. The bottom margin is the enumeration's band exactly — reserving + * less lets a continuation page's last row run into it, a defect page one is + * structurally unable to show.

    + * + *

    A rate is not an amount, and usage is not a tally

    + * + *

    A unit price is written at the precision it is quoted at, between two + * places and four: a rate of 0.0670 rounded to an amount's two places is a + * different price. Usage that names a unit carries two places even on a whole + * number, because the meter reads to that precision; usage that names none is a + * count of whole things and is written bare. The currency is stated once per + * money column and the figures under it are bare. Figures state their locale + * rather than inheriting it.

    + * + *

    The lockup is the caller's

    + * + *

    The design opens and closes with the same mark. Both come from + * {@code InvoiceBrand}: the masthead draws the logo at the design's measured + * width and the identity band at its smaller one, or the brand's name when there + * is no logo. The templates artifact carries no mark of its own.

    + * + *

    Fonts

    + * + *

    The sheet is set in Barlow throughout, and the preset's pitches are solved + * from Barlow's own cap height and ascender. The templates artifact carries no + * fonts — register the family on the session, or the engine substitutes and that + * arithmetic no longer describes the type sitting in it.

    + * + *

    Usage:

    + *
    {@code
    + * DocumentTemplate template = PlatformInvoice.create();
    + * template.compose(session, invoice);
    + * }
    + * + * @since 2.4.0 + */ +public final class PlatformInvoice { + + /** Stable identifier of this preset. */ + public static final String ID = "platform-invoice"; + + /** Human-readable name of this preset. */ + public static final String DISPLAY_NAME = "Platform Invoice"; + + private PlatformInvoice() { + } + + /** + * Creates the template. + * + * @return a template composing a {@link StructuredInvoiceData} + */ + public static DocumentTemplate create() { + return new Template(); + } + + private record Template() implements DocumentTemplate { + + @Override + public String id() { + return ID; + } + + @Override + public String displayName() { + return DISPLAY_NAME; + } + + @Override + public void compose(DocumentSession document, StructuredInvoiceData data) { + Objects.requireNonNull(document, "document"); + Objects.requireNonNull(data, "data"); + + // Every length on the sheet is a share of the design's own grid, so + // the preset sets the page and its margins rather than following + // what the caller configured. + document.pageSize(PAGE); + document.margin(PAGE_MARGIN); + // A continuation page has the same geometry as the first: it loses + // the masthead only because that is body content already drawn. + // Stating the rule keeps the page model decided rather than + // inherited by accident. + document.pageMargins(List.of(PageMarginRule.from(2, PAGE_MARGIN))); + renderPageNumber(document); + + document.pageFlow(page -> { + page.name("PlatformInvoice").padding(DocumentInsets.zero()).spacing(0); + PlatformMasthead.renderBrandHeader(page, data.brand(), data.masthead()); + PlatformMasthead.renderIdentityRow(page, data.supplier(), data.masthead()); + PlatformMasthead.renderIdentityRule(page); + PlatformParties.render(page, data.billTo(), data.shipTo()); + PlatformLines.render(page, data.serviceLines(), data.currencyCode()); + PlatformSettlement.render(page, data.payment(), data.totals(), + data.currencyCode()); + PlatformClosing.renderNoteRule(page); + PlatformClosing.renderNote(page, data.notes(), data.supplier()); + PlatformClosing.renderIdentityRule(page); + PlatformClosing.renderIdentity(page, data.brand(), data.supplier()); + }); + } + + /** + * The only chrome the sheet has. The design's own identity band cannot be + * chrome — a header/footer zone carries left, centre and right strings + * and nothing else, and the band holds a lockup — so page identity is + * carried by the enumeration alone. + * + *

    The height is stated because it is also what positions the number + * inside the band: the engine seats the line box at the band's top and + * the band's foot is the paper's, so the number rises point for point + * with it. The face is stated because the zone's own default is the + * standard-14 one, which would leave the number the single line on the + * sheet in a family the design never uses.

    + */ + private static void renderPageNumber(DocumentSession document) { + document.footer(DocumentHeaderFooter.builder() + .zone(DocumentHeaderFooterZone.FOOTER) + .height((float) FOOTER_BAND_H) + .fontName(FACE) + .fontSize((float) FOOTER_SIZE) + .textColor(MUTED) + .centerText("Page {page} of {pages}") + .showSeparator(false) + .build()); + } + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/PlatformLines.java b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/PlatformLines.java new file mode 100644 index 000000000..8687956fc --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/PlatformLines.java @@ -0,0 +1,215 @@ +package com.demcha.compose.document.templates.invoice.presets; + +import com.demcha.compose.document.dsl.LineBuilder; +import com.demcha.compose.document.dsl.PageFlowBuilder; +import com.demcha.compose.document.dsl.TableBuilder; +import com.demcha.compose.document.style.DocumentInsets; +import com.demcha.compose.document.style.DocumentStroke; +import com.demcha.compose.document.table.DocumentTableCell; +import com.demcha.compose.document.table.DocumentTableColumn; +import com.demcha.compose.document.table.DocumentTableStyle; +import com.demcha.compose.document.table.DocumentTableTextAnchor; +import com.demcha.compose.document.templates.data.invoice.InvoiceServiceLines; + +import java.util.ArrayList; +import java.util.List; + +import static com.demcha.compose.document.templates.invoice.presets.PlatformStyles.BAND; +import static com.demcha.compose.document.templates.invoice.presets.PlatformStyles.BODY_SIZE; +import static com.demcha.compose.document.templates.invoice.presets.PlatformStyles.CONTENT_W; +import static com.demcha.compose.document.templates.invoice.presets.PlatformStyles.HAIRLINE; +import static com.demcha.compose.document.templates.invoice.presets.PlatformStyles.LINE_BOX; +import static com.demcha.compose.document.templates.invoice.presets.PlatformStyles.MUTED; +import static com.demcha.compose.document.templates.invoice.presets.PlatformStyles.RULE_THICK; +import static com.demcha.compose.document.templates.invoice.presets.PlatformStyles.SMALL_SIZE; +import static com.demcha.compose.document.templates.invoice.presets.PlatformStyles.TABLE_HEADER_H; +import static com.demcha.compose.document.templates.invoice.presets.PlatformStyles.TABLE_HEAD_SIZE; +import static com.demcha.compose.document.templates.invoice.presets.PlatformStyles.TABLE_ICON; +import static com.demcha.compose.document.templates.invoice.presets.PlatformStyles.TABLE_PAD_CENTRED; +import static com.demcha.compose.document.templates.invoice.presets.PlatformStyles.TABLE_PAD_FIRST; +import static com.demcha.compose.document.templates.invoice.presets.PlatformStyles.TABLE_PAD_ICON; +import static com.demcha.compose.document.templates.invoice.presets.PlatformStyles.TABLE_PAD_RIGHT; +import static com.demcha.compose.document.templates.invoice.presets.PlatformStyles.TABLE_PAD_X; +import static com.demcha.compose.document.templates.invoice.presets.PlatformStyles.TABLE_ROW_H; +import static com.demcha.compose.document.templates.invoice.presets.PlatformStyles.WHITE; +import static com.demcha.compose.document.templates.invoice.presets.PlatformStyles.ACCENT; +import static com.demcha.compose.document.templates.invoice.presets.PlatformStyles.bold; +import static com.demcha.compose.document.templates.invoice.presets.PlatformStyles.capPitch; +import static com.demcha.compose.document.templates.invoice.presets.PlatformStyles.py; +import static com.demcha.compose.document.templates.invoice.presets.PlatformStyles.style; + +/** + * The usage table: six columns, a filled caps header that repeats on every page + * it reaches, and a horizontal separator under every row. + * + *

    Why the separators are rows

    + * + *

    A table's rules come from each cell's own style and there is no per-edge + * control, so any stroke that buys a horizontal separator also buys five + * verticals — and the design has no vertical rule anywhere inside the table, nor + * a side border below the header. Every cell is therefore stroked at zero width + * and the separators are rows of their own: one cell spanning every column, zero + * padding, and a full-width line as its content.

    + */ +final class PlatformLines { + + /** The six columns, as shares of the content width, solved from the design. */ + private static final double[] WEIGHTS = {0.2834, 0.1658, 0.1348, 0.123, 0.1487, 0.1443}; + + /** The two numeric columns are centred; the four leading ones are not. */ + private static final boolean[] CENTRED = {false, false, false, false, true, true}; + + private PlatformLines() { + } + + /** + * The table. + * + * @param page the page flow + * @param serviceLines the lines and their column captions + * @param currencyCode the code the two money captions carry + */ + static void render(PageFlowBuilder page, InvoiceServiceLines serviceLines, + String currencyCode) { + List captions = captions(serviceLines.columns(), currencyCode); + page.addTable(table -> { + table.name("LineItems").width(CONTENT_W); + table.margin(new DocumentInsets(py(654 - 622), 0, 0, 0)); + List columns = new ArrayList<>(); + for (double weight : WEIGHTS) { + columns.add(DocumentTableColumn.fixed(CONTENT_W * weight)); + } + table.columns(columns.toArray(new DocumentTableColumn[0])); + + renderHeader(table, captions); + // The header's bottom edge, then one separator under every row — + // including the last, which the design draws. + table.rowCells(separatorCells()); + List lines = serviceLines.lines(); + for (int i = 0; i < lines.size(); i++) { + table.rowCells(bodyCells(lines.get(i), i)); + table.rowCells(separatorCells()); + } + }); + } + + /** + * The captions, with the currency code appended to the two money columns. + * + *

    The design states the currency once per money column and writes the + * figures under it bare, which is why the code belongs in the caption rather + * than in front of every amount.

    + */ + private static List captions(InvoiceServiceLines.Columns columns, + String currencyCode) { + String suffix = currencyCode == null || currencyCode.isBlank() + ? "" + : " (" + currencyCode.trim() + ")"; + return List.of( + columns.description(), + columns.servicePeriod(), + columns.region(), + columns.quantity(), + columns.unitPrice() + suffix, + columns.amount() + suffix); + } + + /** + * The header band: one cell per column, each carrying the fill so the band + * reads as continuous, and a repeat instruction so it comes back at the top + * of every page the table reaches. Without it a continuation page carries + * unlabelled columns — a defect no single-page render can show. + * + *

    The first heading is inset further than the other five. Measured; the + * design really is asymmetric there.

    + */ + private static void renderHeader(TableBuilder table, List captions) { + double pad = (TABLE_HEADER_H - LINE_BOX * TABLE_HEAD_SIZE) / 2.0; + List cells = new ArrayList<>(); + for (int i = 0; i < captions.size(); i++) { + DocumentInsets insets = CENTRED[i] + ? new DocumentInsets(pad, TABLE_PAD_CENTRED, pad, TABLE_PAD_CENTRED) + : new DocumentInsets(pad, TABLE_PAD_RIGHT, pad, + i == 0 ? TABLE_PAD_FIRST : TABLE_PAD_X); + DocumentTableStyle cellStyle = DocumentTableStyle.builder() + .padding(insets) + .fillColor(BAND) + .stroke(DocumentStroke.of(BAND, 0)) + .textStyle(bold(TABLE_HEAD_SIZE, ACCENT)) + .textAnchor(CENTRED[i] + ? DocumentTableTextAnchor.CENTER + : DocumentTableTextAnchor.CENTER_LEFT) + .build(); + cells.add(new DocumentTableCell(List.of(captions.get(i)), cellStyle)); + } + table.headerCells(cells); + table.repeatHeader(); + } + + /** One usage row: a composed description cell and five plain ones. */ + private static List bodyCells(InvoiceServiceLines.Line line, int index) { + double pad = (TABLE_ROW_H - descriptionHeight()) / 2.0; + List values = List.of( + "", + line.servicePeriod(), + line.region(), + "", + PlatformText.rate(line.unitPrice()), + PlatformText.money(line.amount())); + List cells = new ArrayList<>(); + for (int i = 0; i < WEIGHTS.length; i++) { + DocumentInsets insets = CENTRED[i] + ? new DocumentInsets(pad, TABLE_PAD_CENTRED, pad, TABLE_PAD_CENTRED) + : new DocumentInsets(pad, TABLE_PAD_RIGHT, pad, + i == 0 ? TABLE_PAD_ICON : TABLE_PAD_X); + DocumentTableStyle cellStyle = DocumentTableStyle.builder() + .padding(insets) + .fillColor(WHITE) + .stroke(DocumentStroke.of(WHITE, 0)) + .textStyle(style(SMALL_SIZE, MUTED)) + .lineSpacing(capPitch(23, SMALL_SIZE)) + .textAnchor(CENTRED[i] + ? DocumentTableTextAnchor.CENTER + : DocumentTableTextAnchor.CENTER_LEFT) + .build(); + if (i == 0) { + cells.add(new DocumentTableCell(List.of(), cellStyle, 1, 1, + PlatformWidgets.descriptionCell(line, index))); + } else if (i == 3) { + // The count and what it counts are two lines of one cell, so a + // long unit falls under the figure rather than widening the + // column the design measured. + String usage = PlatformText.usage(line.quantity(), line.unit()); + cells.add(line.unit().isBlank() + ? new DocumentTableCell(List.of(usage), cellStyle) + : new DocumentTableCell(List.of(usage, line.unit()), cellStyle)); + } else { + cells.add(new DocumentTableCell(List.of(values.get(i)), cellStyle)); + } + } + return cells; + } + + /** The tallest cell in a row, and therefore what its padding is solved against. */ + private static double descriptionHeight() { + return Math.max(TABLE_ICON, + LINE_BOX * BODY_SIZE + capPitch(23, BODY_SIZE, SMALL_SIZE) + + LINE_BOX * SMALL_SIZE); + } + + /** A separator row: one cell across every column, carrying a full-width line. */ + private static List separatorCells() { + DocumentTableStyle cellStyle = DocumentTableStyle.builder() + .padding(DocumentInsets.zero()) + .fillColor(WHITE) + .stroke(DocumentStroke.of(WHITE, 0)) + .build(); + return List.of(new DocumentTableCell(List.of(), cellStyle, WEIGHTS.length, 1, + new LineBuilder() + .name("LineItemsSeparator") + .horizontal(CONTENT_W) + .thickness(RULE_THICK) + .color(HAIRLINE) + .build())); + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/PlatformMasthead.java b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/PlatformMasthead.java new file mode 100644 index 000000000..84f4b894d --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/PlatformMasthead.java @@ -0,0 +1,222 @@ +package com.demcha.compose.document.templates.invoice.presets; + +import com.demcha.compose.document.dsl.ImageBuilder; +import com.demcha.compose.document.dsl.PageFlowBuilder; +import com.demcha.compose.document.dsl.SectionBuilder; +import com.demcha.compose.document.node.DocumentLinkOptions; +import com.demcha.compose.document.node.InlineImageAlignment; +import com.demcha.compose.document.node.TextAlign; +import com.demcha.compose.document.style.DocumentInsets; +import com.demcha.compose.document.style.DocumentRowColumn; +import com.demcha.compose.document.templates.core.identity.ContactUri; +import com.demcha.compose.document.templates.data.invoice.InvoiceBrand; +import com.demcha.compose.document.templates.data.invoice.InvoiceContactBlock; +import com.demcha.compose.document.templates.data.invoice.InvoiceMasthead; + +import java.util.List; + +import static com.demcha.compose.document.templates.invoice.presets.PlatformStyles.ACCENT; +import static com.demcha.compose.document.templates.invoice.presets.PlatformStyles.BODY; +import static com.demcha.compose.document.templates.invoice.presets.PlatformStyles.BODY_SIZE; +import static com.demcha.compose.document.templates.invoice.presets.PlatformStyles.CAP_INSET; +import static com.demcha.compose.document.templates.invoice.presets.PlatformStyles.CONTACT_GAP_PX; +import static com.demcha.compose.document.templates.invoice.presets.PlatformStyles.CONTACT_ICON; +import static com.demcha.compose.document.templates.invoice.presets.PlatformStyles.DIVIDER_W; +import static com.demcha.compose.document.templates.invoice.presets.PlatformStyles.INK; +import static com.demcha.compose.document.templates.invoice.presets.PlatformStyles.LEFT_COLUMN_W; +import static com.demcha.compose.document.templates.invoice.presets.PlatformStyles.LINE_BOX; +import static com.demcha.compose.document.templates.invoice.presets.PlatformStyles.LOCKUP_W; +import static com.demcha.compose.document.templates.invoice.presets.PlatformStyles.MARGIN_TOP; +import static com.demcha.compose.document.templates.invoice.presets.PlatformStyles.META_LABEL_W; +import static com.demcha.compose.document.templates.invoice.presets.PlatformStyles.META_PAD_L; +import static com.demcha.compose.document.templates.invoice.presets.PlatformStyles.RULE; +import static com.demcha.compose.document.templates.invoice.presets.PlatformStyles.RULE_THICK; +import static com.demcha.compose.document.templates.invoice.presets.PlatformStyles.SUPPLIER_NAME_SIZE; +import static com.demcha.compose.document.templates.invoice.presets.PlatformStyles.TITLE_SIZE; +import static com.demcha.compose.document.templates.invoice.presets.PlatformStyles.bold; +import static com.demcha.compose.document.templates.invoice.presets.PlatformStyles.capPitch; +import static com.demcha.compose.document.templates.invoice.presets.PlatformStyles.flowY; +import static com.demcha.compose.document.templates.invoice.presets.PlatformStyles.px; +import static com.demcha.compose.document.templates.invoice.presets.PlatformStyles.py; +import static com.demcha.compose.document.templates.invoice.presets.PlatformStyles.spaces; +import static com.demcha.compose.document.templates.invoice.presets.PlatformStyles.style; +import static com.demcha.compose.document.templates.invoice.presets.PlatformWidgets.layeredRow; + +/** + * The head of the sheet: the lockup against the title, then the supplier's own + * details against the invoice's metadata, split by a full-height hairline. + */ +final class PlatformMasthead { + + private PlatformMasthead() { + } + + /** + * The lockup on the left and the document's title on the right. + * + *

    The mark is the caller's: the lockup draws the brand's logo at the + * design's measured width, or the brand's name when there is none. The + * templates artifact carries no mark of its own.

    + */ + static void renderBrandHeader(PageFlowBuilder page, InvoiceBrand brand, + InvoiceMasthead masthead) { + page.addRow("Masthead", row -> { + row.spacing(0) + .columns(DocumentRowColumn.fixed(LOCKUP_W), DocumentRowColumn.weight(1)); + row.addSection("BrandLockup", cell -> { + cell.spacing(0); + if (brand.logo() != null) { + cell.add(new ImageBuilder() + .name("BrandLogo") + .source(brand.logo()) + .width(LOCKUP_W) + .margin(new DocumentInsets(flowY(42), 0, 0, 0)) + .build()); + } else if (!brand.name().isBlank()) { + cell.addParagraph(p -> p + .name("BrandWordmark") + .text(brand.name()) + .textStyle(bold(SUPPLIER_NAME_SIZE, INK)) + .margin(new DocumentInsets(flowY(42), 0, 0, 0))); + } + }); + row.addParagraph(p -> p + .name("InvoiceTitle") + .text(masthead.title()) + .textStyle(bold(TITLE_SIZE, INK)) + .align(TextAlign.RIGHT)); + }); + } + + /** + * The supplier's details against the invoice's metadata. + * + *

    The gap above is measured from the title's line-box foot to the + * supplier name's cap, because the masthead row's box ends at the former and + * the design measures the latter.

    + */ + static void renderIdentityRow(PageFlowBuilder page, InvoiceContactBlock supplier, + InvoiceMasthead masthead) { + page.addRow("IdentityRow", row -> { + row.spacing(0) + .columns(DocumentRowColumn.fixed(LEFT_COLUMN_W), + DocumentRowColumn.fixed(DIVIDER_W), + DocumentRowColumn.weight(1)) + .margin(new DocumentInsets( + py(135) - CAP_INSET * SUPPLIER_NAME_SIZE + - (flowY(45) - CAP_INSET * TITLE_SIZE + LINE_BOX * TITLE_SIZE) + - MARGIN_TOP, + 0, 0, 0)); + row.addSection("SupplierColumn", column -> renderSupplier(column, supplier)); + row.addSection("IdentityColumnDivider", column -> { + column.spacing(0); + column.addLine(line -> line + .name("IdentityColumnDivider") + .vertical(py(383 - 128)) + .thickness(DIVIDER_W) + .color(RULE)); + }); + row.addSection("MetaColumn", column -> renderMeta(column, masthead)); + }); + } + + /** The rule under both columns, at the design's own width and inset. */ + static void renderIdentityRule(PageFlowBuilder page) { + page.addLine(line -> line + .name("IdentityRule") + .horizontal(px(924)) + .thickness(RULE_THICK) + .color(RULE) + .margin(new DocumentInsets(py(408 - 383), 0, 0, px(5)))); + } + + private static void renderSupplier(SectionBuilder column, InvoiceContactBlock supplier) { + column.spacing(0).padding(new DocumentInsets(0, 0, 0, px(6))); + column.addParagraph(p -> p + .name("SupplierName") + .text(supplier.legalName()) + .textStyle(bold(SUPPLIER_NAME_SIZE, INK))); + + column.addSection("SupplierAddress", block -> { + block.spacing(capPitch(24, BODY_SIZE)) + .margin(new DocumentInsets( + capPitch(38, SUPPLIER_NAME_SIZE, BODY_SIZE), 0, 0, 0)); + List lines = supplier.addressLines(); + for (int i = 0; i < lines.size(); i++) { + String line = lines.get(i); + int index = i; + block.addParagraph(p -> p + .name("SupplierAddress_" + index) + .text(line) + .textStyle(style(BODY_SIZE, BODY))); + } + }); + + column.addSection("SupplierContact", block -> { + block.spacing(capPitch(28, BODY_SIZE)) + .margin(new DocumentInsets(capPitch(46, BODY_SIZE), 0, 0, 0)); + channel(block, "Website", PlatformIcons.WEBSITE, supplier.website(), + ContactUri.webLink(supplier.website())); + channel(block, "Email", PlatformIcons.EMAIL, supplier.email(), + ContactUri.mailLink(supplier.email())); + channel(block, "Phone", PlatformIcons.PHONE, supplier.phone(), + ContactUri.telLink(supplier.phone())); + }); + + if (!supplier.taxRegistrationNumber().isBlank()) { + column.addParagraph(p -> { + p.name("SupplierTaxId"); + p.inlineText(supplier.taxRegistrationLabel(), style(BODY_SIZE, BODY)); + p.inlineText(spaces(20, BODY_SIZE), style(BODY_SIZE, BODY)); + p.inlineText(supplier.taxRegistrationNumber(), style(BODY_SIZE, BODY)); + p.margin(new DocumentInsets(capPitch(38, BODY_SIZE), 0, 0, 0)); + }); + } + } + + /** + * One contact channel: its mark, a measured gap, and the value. + * + *

    The gap is a run of spaces rather than a column, because the design sets + * mark and value on one baseline inside a single line — a two-column row + * would put the mark on the line box's own top edge instead.

    + */ + private static void channel(SectionBuilder block, String name, String token, + String value, DocumentLinkOptions link) { + if (value.isBlank()) { + return; + } + block.addParagraph(p -> { + p.name("SupplierContact" + name); + p.inlineSvgIcon(PlatformIcons.icon(token), CONTACT_ICON, InlineImageAlignment.CENTER); + p.inlineText(spaces(CONTACT_GAP_PX, BODY_SIZE), style(BODY_SIZE, BODY)); + if (link == null) { + p.inlineText(value, style(BODY_SIZE, BODY)); + } else { + p.inlineText(value, style(BODY_SIZE, BODY), link); + } + }); + } + + private static void renderMeta(SectionBuilder column, InvoiceMasthead masthead) { + column.spacing(capPitch(36.17, BODY_SIZE)) + .padding(new DocumentInsets(0, 0, 0, META_PAD_L)) + .margin(new DocumentInsets( + py(138) - CAP_INSET * BODY_SIZE + - (py(135) - CAP_INSET * SUPPLIER_NAME_SIZE), + 0, 0, 0)); + List entries = masthead.entries(); + for (int i = 0; i < entries.size(); i++) { + InvoiceMasthead.Entry entry = entries.get(i); + layeredRow(column, "MetaRow_" + i, row -> { + row.spacing(0) + .columns(DocumentRowColumn.fixed(META_LABEL_W), + DocumentRowColumn.weight(1)); + row.addParagraph(p -> p.text(entry.label()).textStyle(style(BODY_SIZE, BODY))); + row.addParagraph(p -> p + .text(entry.value()) + .textStyle(style(BODY_SIZE, entry.emphasized() ? ACCENT : INK))); + }); + } + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/PlatformParties.java b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/PlatformParties.java new file mode 100644 index 000000000..b22a2fbf9 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/PlatformParties.java @@ -0,0 +1,140 @@ +package com.demcha.compose.document.templates.invoice.presets; + +import com.demcha.compose.document.dsl.PageFlowBuilder; +import com.demcha.compose.document.dsl.SectionBuilder; +import com.demcha.compose.document.node.RowVerticalAlign; +import com.demcha.compose.document.style.DocumentInsets; +import com.demcha.compose.document.style.DocumentRowColumn; +import com.demcha.compose.document.templates.data.invoice.InvoiceRecipient; + +import java.util.List; + +import static com.demcha.compose.document.templates.invoice.presets.PlatformStyles.ACCENT; +import static com.demcha.compose.document.templates.invoice.presets.PlatformStyles.BILL_TO_GUTTER; +import static com.demcha.compose.document.templates.invoice.presets.PlatformStyles.BILL_TO_PAD_L; +import static com.demcha.compose.document.templates.invoice.presets.PlatformStyles.BODY; +import static com.demcha.compose.document.templates.invoice.presets.PlatformStyles.BODY_SIZE; +import static com.demcha.compose.document.templates.invoice.presets.PlatformStyles.CAP_INSET; +import static com.demcha.compose.document.templates.invoice.presets.PlatformStyles.DIVIDER_W; +import static com.demcha.compose.document.templates.invoice.presets.PlatformStyles.INK; +import static com.demcha.compose.document.templates.invoice.presets.PlatformStyles.LABEL_SIZE; +import static com.demcha.compose.document.templates.invoice.presets.PlatformStyles.LEFT_COLUMN_W; +import static com.demcha.compose.document.templates.invoice.presets.PlatformStyles.PARTY_NAME_SIZE; +import static com.demcha.compose.document.templates.invoice.presets.PlatformStyles.PIN_W; +import static com.demcha.compose.document.templates.invoice.presets.PlatformStyles.RULE; +import static com.demcha.compose.document.templates.invoice.presets.PlatformStyles.SHIP_TO_GUTTER; +import static com.demcha.compose.document.templates.invoice.presets.PlatformStyles.SHIP_TO_PAD_L; +import static com.demcha.compose.document.templates.invoice.presets.PlatformStyles.bold; +import static com.demcha.compose.document.templates.invoice.presets.PlatformStyles.capPitch; +import static com.demcha.compose.document.templates.invoice.presets.PlatformStyles.py; +import static com.demcha.compose.document.templates.invoice.presets.PlatformStyles.spaces; +import static com.demcha.compose.document.templates.invoice.presets.PlatformStyles.style; +import static com.demcha.compose.document.templates.invoice.presets.PlatformWidgets.layeredRow; +import static com.demcha.compose.document.templates.invoice.presets.PlatformWidgets.partyDisc; + +/** + * The two addressed parties, split by the same hairline the identity row uses. + * + *

    The two headings are not drawn alike, and that is the design's own choice + * rather than an inconsistency: the billed party's mark sits knocked out of a + * filled disc, the shipped-to party's is a bare pin. They also sit on different + * gutters and different left insets, both measured.

    + */ +final class PlatformParties { + + private PlatformParties() { + } + + /** + * The parties row. + * + * @param page the page flow + * @param billTo the billed party + * @param shipTo the shipped-to party, or {@code null} when the invoice ships + * nowhere and the column stays empty + */ + static void render(PageFlowBuilder page, InvoiceRecipient billTo, InvoiceRecipient shipTo) { + page.addRow("PartiesRow", row -> { + row.spacing(0) + .columns(DocumentRowColumn.fixed(LEFT_COLUMN_W), + DocumentRowColumn.fixed(DIVIDER_W), + DocumentRowColumn.weight(1)) + .margin(new DocumentInsets(py(432 - 409), 0, 0, 0)); + row.addSection("BillToColumn", column -> + renderParty(column, "BillTo", billTo, PlatformIcons.BILL_TO, + BILL_TO_PAD_L, BILL_TO_GUTTER, true)); + row.addSection("PartyColumnDivider", column -> { + column.spacing(0); + column.addLine(line -> line + .name("PartyColumnDivider") + .vertical(py(622 - 437)) + .thickness(DIVIDER_W) + .color(RULE) + .margin(new DocumentInsets(py(437 - 432), 0, 0, 0))); + }); + row.addSection("ShipToColumn", column -> { + if (shipTo != null) { + renderParty(column, "ShipTo", shipTo, PlatformIcons.SHIP_TO, + SHIP_TO_PAD_L, SHIP_TO_GUTTER, false); + } else { + column.spacing(0); + } + }); + }); + } + + private static void renderParty(SectionBuilder column, String id, InvoiceRecipient party, + String token, double padLeft, double gutter, + boolean drawsDisc) { + column.spacing(0).padding(new DocumentInsets(0, 0, 0, padLeft)); + + layeredRow(column, id + "Label", row -> { + row.spacing(0) + .verticalAlign(RowVerticalAlign.CENTER) + .columns(DocumentRowColumn.fixed(gutter), DocumentRowColumn.weight(1)); + row.add(drawsDisc + ? partyDisc(id + "Disc", token) + : PlatformIcons.icon(token).node(PIN_W)); + row.addParagraph(p -> p + .name(id + "LabelText") + .text(party.heading()) + .textStyle(bold(LABEL_SIZE, ACCENT))); + }); + + column.addSection(id + "Body", block -> { + block.spacing(0).padding(new DocumentInsets(0, 0, 0, gutter)); + + block.addParagraph(p -> p + .name(id + "Name") + .text(party.name()) + .textStyle(bold(PARTY_NAME_SIZE, INK)) + .margin(new DocumentInsets( + py(474 - 466) - CAP_INSET * PARTY_NAME_SIZE, 0, 0, 0))); + + block.addSection(id + "Address", lines -> { + lines.spacing(capPitch(25.3, BODY_SIZE)) + .margin(new DocumentInsets( + capPitch(30, PARTY_NAME_SIZE, BODY_SIZE), 0, 0, 0)); + List address = party.addressLines(); + for (int i = 0; i < address.size(); i++) { + String line = address.get(i); + int index = i; + lines.addParagraph(p -> p + .name(id + "Address_" + index) + .text(line) + .textStyle(style(BODY_SIZE, BODY))); + } + }); + + if (!party.registrationNumber().isBlank()) { + block.addParagraph(p -> { + p.name(id + "Registration"); + p.inlineText(party.registrationLabel(), style(BODY_SIZE, BODY)); + p.inlineText(spaces(26, BODY_SIZE), style(BODY_SIZE, BODY)); + p.inlineText(party.registrationNumber(), style(BODY_SIZE, BODY)); + p.margin(new DocumentInsets(capPitch(41, BODY_SIZE), 0, 0, 0)); + }); + } + }); + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/PlatformSettlement.java b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/PlatformSettlement.java new file mode 100644 index 000000000..d5ed49de9 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/PlatformSettlement.java @@ -0,0 +1,280 @@ +package com.demcha.compose.document.templates.invoice.presets; + +import com.demcha.compose.document.dsl.PageFlowBuilder; +import com.demcha.compose.document.dsl.SectionBuilder; +import com.demcha.compose.document.node.RowVerticalAlign; +import com.demcha.compose.document.node.TextAlign; +import com.demcha.compose.document.style.DocumentInsets; +import com.demcha.compose.document.style.DocumentRowColumn; +import com.demcha.compose.document.style.DocumentStroke; +import com.demcha.compose.document.templates.core.identity.ContactUri; +import com.demcha.compose.document.templates.data.invoice.InvoicePaymentBlock; +import com.demcha.compose.document.templates.data.invoice.InvoiceTotalsBlock; + +import java.util.List; + +import static com.demcha.compose.document.templates.invoice.presets.PlatformStyles.ACCENT; +import static com.demcha.compose.document.templates.invoice.presets.PlatformStyles.BODY; +import static com.demcha.compose.document.templates.invoice.presets.PlatformStyles.BODY_SIZE; +import static com.demcha.compose.document.templates.invoice.presets.PlatformStyles.CAP_INSET; +import static com.demcha.compose.document.templates.invoice.presets.PlatformStyles.CARD; +import static com.demcha.compose.document.templates.invoice.presets.PlatformStyles.CARD_DISC_D; +import static com.demcha.compose.document.templates.invoice.presets.PlatformStyles.CARD_DISC_ICON; +import static com.demcha.compose.document.templates.invoice.presets.PlatformStyles.CARD_GUTTER; +import static com.demcha.compose.document.templates.invoice.presets.PlatformStyles.CARD_PAD_L; +import static com.demcha.compose.document.templates.invoice.presets.PlatformStyles.CARD_RADIUS; +import static com.demcha.compose.document.templates.invoice.presets.PlatformStyles.DUE_VALUE_SIZE; +import static com.demcha.compose.document.templates.invoice.presets.PlatformStyles.HAIRLINE; +import static com.demcha.compose.document.templates.invoice.presets.PlatformStyles.INK; +import static com.demcha.compose.document.templates.invoice.presets.PlatformStyles.LABEL_SIZE; +import static com.demcha.compose.document.templates.invoice.presets.PlatformStyles.LINE_BOX; +import static com.demcha.compose.document.templates.invoice.presets.PlatformStyles.PANEL_BORDER; +import static com.demcha.compose.document.templates.invoice.presets.PlatformStyles.PANEL_BOTTOM_Y; +import static com.demcha.compose.document.templates.invoice.presets.PlatformStyles.PANEL_HEAD_GUTTER; +import static com.demcha.compose.document.templates.invoice.presets.PlatformStyles.PANEL_ICON; +import static com.demcha.compose.document.templates.invoice.presets.PlatformStyles.PANEL_ICON_Y; +import static com.demcha.compose.document.templates.invoice.presets.PlatformStyles.PANEL_LABEL_W; +import static com.demcha.compose.document.templates.invoice.presets.PlatformStyles.PANEL_LAST_ROW_CAP_Y; +import static com.demcha.compose.document.templates.invoice.presets.PlatformStyles.PANEL_NOTE_CAP_Y; +import static com.demcha.compose.document.templates.invoice.presets.PlatformStyles.PANEL_NOTE_GUTTER; +import static com.demcha.compose.document.templates.invoice.presets.PlatformStyles.PANEL_NOTE_ICON; +import static com.demcha.compose.document.templates.invoice.presets.PlatformStyles.PANEL_NOTE_LAST_CAP_Y; +import static com.demcha.compose.document.templates.invoice.presets.PlatformStyles.PANEL_PAD_ROWS_L; +import static com.demcha.compose.document.templates.invoice.presets.PlatformStyles.PANEL_PAD_X; +import static com.demcha.compose.document.templates.invoice.presets.PlatformStyles.PANEL_RADIUS; +import static com.demcha.compose.document.templates.invoice.presets.PlatformStyles.PANEL_ROW1_CAP_Y; +import static com.demcha.compose.document.templates.invoice.presets.PlatformStyles.PANEL_ROW_PITCH_Y; +import static com.demcha.compose.document.templates.invoice.presets.PlatformStyles.PANEL_RULE_Y; +import static com.demcha.compose.document.templates.invoice.presets.PlatformStyles.PANEL_TOP_Y; +import static com.demcha.compose.document.templates.invoice.presets.PlatformStyles.PANEL_W; +import static com.demcha.compose.document.templates.invoice.presets.PlatformStyles.RIGHT_COLUMN_W; +import static com.demcha.compose.document.templates.invoice.presets.PlatformStyles.RULE_THICK; +import static com.demcha.compose.document.templates.invoice.presets.PlatformStyles.SMALL_SIZE; +import static com.demcha.compose.document.templates.invoice.presets.PlatformStyles.SUMMARY_GAP_W; +import static com.demcha.compose.document.templates.invoice.presets.PlatformStyles.TOTALS_PAD_L; +import static com.demcha.compose.document.templates.invoice.presets.PlatformStyles.TOTALS_PAD_R; +import static com.demcha.compose.document.templates.invoice.presets.PlatformStyles.TOTALS_RULE_W; +import static com.demcha.compose.document.templates.invoice.presets.PlatformStyles.TOTAL_LABEL_SIZE; +import static com.demcha.compose.document.templates.invoice.presets.PlatformStyles.TOTAL_VALUE_SIZE; +import static com.demcha.compose.document.templates.invoice.presets.PlatformStyles.WHITE; +import static com.demcha.compose.document.templates.invoice.presets.PlatformStyles.bold; +import static com.demcha.compose.document.templates.invoice.presets.PlatformStyles.capPitch; +import static com.demcha.compose.document.templates.invoice.presets.PlatformStyles.py; +import static com.demcha.compose.document.templates.invoice.presets.PlatformStyles.style; +import static com.demcha.compose.document.templates.invoice.presets.PlatformWidgets.disc; +import static com.demcha.compose.document.templates.invoice.presets.PlatformWidgets.layeredRow; + +/** + * The band under the table: the outlined bank panel on the left, and on the + * right the totals over the filled card carrying the due date. + */ +final class PlatformSettlement { + + private PlatformSettlement() { + } + + /** + * The settlement row. + * + *

    The card stops short of the right margin, so the row carries the + * remainder as a cell of its own rather than letting the right column stretch + * into it.

    + */ + static void render(PageFlowBuilder page, InvoicePaymentBlock payment, + InvoiceTotalsBlock totals, String currencyCode) { + page.addRow("Settlement", row -> { + row.spacing(0) + .columns(DocumentRowColumn.fixed(PANEL_W), + DocumentRowColumn.fixed(SUMMARY_GAP_W), + DocumentRowColumn.fixed(RIGHT_COLUMN_W), + DocumentRowColumn.weight(1)) + .margin(new DocumentInsets(py(1046 - 1034), 0, 0, 0)); + row.addSection("PaymentPanel", column -> renderPaymentPanel(column, payment)); + row.addSection("SettlementGap", column -> column.spacing(0)); + row.addSection("SettlementRight", column -> { + column.spacing(0); + renderTotals(column, totals, currencyCode); + renderDueCard(column, payment); + }); + row.addSection("SettlementTail", column -> column.spacing(0)); + }); + } + + private static void renderPaymentPanel(SectionBuilder column, InvoicePaymentBlock payment) { + column.spacing(0) + .keepTogether() + .fillColor(WHITE) + .stroke(DocumentStroke.of(PANEL_BORDER, RULE_THICK)) + .cornerRadius(PANEL_RADIUS); + + layeredRow(column, "PaymentHeading", row -> { + row.spacing(0) + .verticalAlign(RowVerticalAlign.CENTER) + .columns(DocumentRowColumn.fixed(PANEL_HEAD_GUTTER), + DocumentRowColumn.weight(1)) + .padding(new DocumentInsets(py(PANEL_ICON_Y - PANEL_TOP_Y), 0, 0, PANEL_PAD_X)); + row.add(PlatformIcons.icon(PlatformIcons.BANK).node(PANEL_ICON)); + row.addParagraph(p -> p + .name("PaymentHeadingText") + .text(payment.heading()) + .textStyle(bold(LABEL_SIZE, ACCENT))); + }); + + column.addSection("PaymentRows", rows -> { + rows.spacing(capPitch(PANEL_ROW_PITCH_Y, SMALL_SIZE)) + .padding(new DocumentInsets(0, PANEL_PAD_X, 0, PANEL_PAD_ROWS_L)) + // Measured from the mark, because the heading layer's height + // is the mark's and not the label's line box. + .margin(new DocumentInsets( + py(PANEL_ROW1_CAP_Y - PANEL_ICON_Y) + - CAP_INSET * SMALL_SIZE - PANEL_ICON, + 0, 0, 0)); + List fields = payment.fields(); + for (int i = 0; i < fields.size(); i++) { + InvoicePaymentBlock.Field field = fields.get(i); + layeredRow(rows, "PaymentRow_" + i, row -> { + row.spacing(0) + .columns(DocumentRowColumn.fixed(PANEL_LABEL_W), + DocumentRowColumn.weight(1)); + row.addParagraph(p -> p.text(field.label()).textStyle(style(SMALL_SIZE, BODY))); + row.addParagraph(p -> { + p.text(field.value()).textStyle(style(SMALL_SIZE, INK)); + // A bank detail is a reference, not a destination — + // except the remittance address, the one field on the + // panel a reader would act on. + if (field.value().contains("@")) { + p.link(ContactUri.mailLink(field.value())); + } + }); + }); + } + }); + + if (payment.instruction().isBlank()) { + return; + } + column.addLine(line -> line + .name("PaymentPanelRule") + .horizontal(PANEL_W) + .thickness(RULE_THICK) + .color(HAIRLINE) + .margin(new DocumentInsets( + py(PANEL_RULE_Y - PANEL_LAST_ROW_CAP_Y) + - (LINE_BOX - CAP_INSET) * SMALL_SIZE, + 0, 0, 0))); + layeredRow(column, "PaymentNote", row -> { + row.spacing(0) + .columns(DocumentRowColumn.fixed(PANEL_NOTE_GUTTER), + DocumentRowColumn.weight(1)) + .padding(new DocumentInsets( + py(PANEL_NOTE_CAP_Y - PANEL_RULE_Y) - CAP_INSET * SMALL_SIZE, + 0, + py(PANEL_BOTTOM_Y - PANEL_NOTE_LAST_CAP_Y) + - (LINE_BOX - CAP_INSET) * SMALL_SIZE, + PANEL_PAD_X)); + row.add(PlatformIcons.icon(PlatformIcons.INFO).node(PANEL_NOTE_ICON)); + row.addSection("PaymentNoteText", text -> { + text.spacing(capPitch(22, SMALL_SIZE)); + String[] lines = payment.instruction().split("\n"); + for (int i = 0; i < lines.length; i++) { + String prose = lines[i]; + int index = i; + text.addParagraph(p -> p + .name("PaymentNoteLine_" + index) + .text(prose) + .textStyle(style(SMALL_SIZE, BODY))); + } + }); + }); + } + + /** + * The summed rows and the grand total. + * + *

    The summed rows are written bare, under a column that already states + * the currency; the grand total carries the code, because it stands alone + * under no caption and a bare figure there names no currency at all.

    + */ + private static void renderTotals(SectionBuilder column, InvoiceTotalsBlock totals, + String currencyCode) { + column.addSection("Totals", block -> { + block.spacing(0) + .keepTogether() + .padding(new DocumentInsets(0, 0, 0, TOTALS_PAD_L)) + .margin(new DocumentInsets(py(1065 - 1046) - CAP_INSET * BODY_SIZE, 0, 0, 0)); + List rows = totals.rows(); + for (int i = 0; i < rows.size(); i++) { + InvoiceTotalsBlock.Row entry = rows.get(i); + boolean first = i == 0; + layeredRow(block, "TotalsRow_" + i, row -> { + row.spacing(0) + .padding(new DocumentInsets(0, TOTALS_PAD_R, 0, 0)) + .margin(new DocumentInsets( + first ? 0 : capPitch(31, BODY_SIZE), 0, 0, 0)) + .evenWeights(); + row.addParagraph(p -> p.text(entry.label()).textStyle(style(BODY_SIZE, BODY))); + row.addParagraph(p -> p + .text(PlatformText.money(entry.amount())) + .textStyle(style(BODY_SIZE, INK)) + .align(TextAlign.RIGHT)); + }); + } + block.addLine(line -> line + .name("TotalsRule") + .horizontal(TOTALS_RULE_W) + .thickness(RULE_THICK) + .color(HAIRLINE) + .margin(new DocumentInsets( + py(1127) - (py(1096) + (LINE_BOX - CAP_INSET) * BODY_SIZE), 0, 0, 0))); + layeredRow(block, "TotalDue", row -> { + row.spacing(0) + .verticalAlign(RowVerticalAlign.CENTER) + .padding(new DocumentInsets( + py(1155 - 1129) - CAP_INSET * TOTAL_VALUE_SIZE, + TOTALS_PAD_R, 0, 0)) + .evenWeights(); + row.addParagraph(p -> p + .name("TotalDueLabel") + .text(totals.totalLabel()) + .textStyle(bold(TOTAL_LABEL_SIZE, INK))); + row.addParagraph(p -> p + .name("TotalDueValue") + .text(PlatformText.coded(currencyCode, totals.totalAmount())) + .textStyle(bold(TOTAL_VALUE_SIZE, ACCENT)) + .align(TextAlign.RIGHT)); + }); + }); + } + + private static void renderDueCard(SectionBuilder column, InvoicePaymentBlock payment) { + if (payment.dueNotice().isBlank() && payment.dueNoticeEmphasis().isBlank()) { + return; + } + column.addSection("DueDateCard", panel -> { + panel.spacing(0) + .keepTogether() + .fillColor(CARD) + .cornerRadius(CARD_RADIUS) + .margin(new DocumentInsets(py(1215 - 1182), 0, 0, 0)); + layeredRow(panel, "DueDateRow", row -> { + row.spacing(0) + .verticalAlign(RowVerticalAlign.CENTER) + .columns(DocumentRowColumn.fixed(CARD_GUTTER), DocumentRowColumn.weight(1)) + .padding(new DocumentInsets( + py(1235 - 1215), 0, py(1306 - 1285), CARD_PAD_L)); + row.add(disc("DueDateDisc", PlatformIcons.CALENDAR, CARD_DISC_D, CARD_DISC_ICON)); + row.addSection("DueDateText", text -> { + text.spacing(capPitch(28, LABEL_SIZE, DUE_VALUE_SIZE)); + text.addParagraph(p -> p + .name("DueDateLabel") + .text(payment.dueNotice()) + .textStyle(bold(LABEL_SIZE, ACCENT))); + text.addParagraph(p -> p + .name("DueDateValue") + .text(payment.dueNoticeEmphasis()) + .textStyle(bold(DUE_VALUE_SIZE, INK))); + }); + }); + }); + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/PlatformStyles.java b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/PlatformStyles.java new file mode 100644 index 000000000..30f3f3fed --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/PlatformStyles.java @@ -0,0 +1,335 @@ +package com.demcha.compose.document.templates.invoice.presets; + +import com.demcha.compose.document.api.DocumentPageSize; +import com.demcha.compose.document.style.DocumentColor; +import com.demcha.compose.document.style.DocumentInsets; +import com.demcha.compose.document.style.DocumentTextDecoration; +import com.demcha.compose.document.style.DocumentTextStyle; +import com.demcha.compose.font.FontName; + +/** + * Every measurement, colour and type size of the Platform invoice. + * + *

    Two scales, on purpose

    + * + *

    The design is 1.50 aspect and A4 is 1.414, so one conversion constant + * cannot serve both axes. {@link #px(double)} is horizontal and every column + * width, x-offset and font size goes through it, because that is the axis where + * advance widths have to fit. {@link #py(double)} is vertical, 6.3% tighter, and + * every block height, line pitch and gap goes through that. {@link #sq(double)} + * is their mean, for the two square things on the sheet — marks and discs — + * which cannot match both axes and are therefore about 3% out in each rather + * than 6% out in one.

    + * + *

    Writing a vertical measurement as {@code px(n)} would stretch the sheet by + * 6.3% without anything failing. The split exists to make that visible.

    + */ +final class PlatformStyles { + + private PlatformStyles() { + } + + // ------------------------------------------------------------------ + // The page and its two scales + // ------------------------------------------------------------------ + + static final DocumentPageSize PAGE = DocumentPageSize.A4; + + private static final double PAGE_W = PAGE.width(); + private static final double PAGE_H = PAGE.height(); + + /** The design image's own pixel size — the units every measurement below is in. */ + private static final double REF_W = 1024.0; + private static final double REF_H = 1536.0; + + /** Points per design pixel, horizontal. */ + private static final double PX = PAGE_W / REF_W; + + /** + * Points per design pixel, vertical. + * + *

    The design's full height maps onto the page's full height, which is what + * a comparison against it does: the image is resampled to the render's width + * and height, so a design pixel at y is compared against the page at + * y / 1536 of its height and nothing else. Anchoring the scale on anything + * narrower — the last ink, say — tilts every measurement below the masthead + * by the difference, growing to a visible offset by the foot of the page.

    + */ + private static final double PY = PAGE_H / REF_H; + + /** Points per design pixel for anything square. */ + private static final double SQ = (PX + PY) / 2.0; + + static double px(double designPixels) { + return designPixels * PX; + } + + static double py(double designPixels) { + return designPixels * PY; + } + + static double sq(double designPixels) { + return designPixels * SQ; + } + + // ------------------------------------------------------------------ + // Type, and the arithmetic the design's pitches need + // ------------------------------------------------------------------ + + /** + * The sheet is set in Barlow throughout. The templates artifact carries no + * fonts — register the family on the session, or the engine substitutes and + * the cap arithmetic below no longer describes the type sitting in it. + */ + static final FontName FACE = FontName.BARLOW; + + /** Barlow's capHeight / unitsPerEm, identical in Regular and Bold. */ + private static final double FACE_CAP_RATIO = 0.700; + + /** Barlow's ascender, as a share of the em. */ + private static final double FACE_ASCENT = 1.000; + + /** The line box the engine lays a paragraph out in, as a multiple of the size. */ + static final double LINE_BOX = 1.200; + + /** What a line box carries above its own cap. */ + static final double CAP_INSET = FACE_ASCENT - FACE_CAP_RATIO; + + /** Barlow's space advance, as a share of the em. */ + private static final double FACE_SPACE_ADVANCE = 0.200; + + /** A size whose caps measure {@code capPx} on the design. */ + static double sizeForCap(double capPx) { + return px(capPx) / FACE_CAP_RATIO; + } + + /** + * The gap to put between two stacked text boxes so their cap tops land + * {@code pitchPx} design pixels apart. + * + *

    Line spacing and section spacing are additive terms in points + * between boxes rather than multiples of the type size, and a box reaches + * the full ascent above its baseline where the cap only reaches + * {@code FACE_CAP_RATIO}. So the gap is the measured pitch minus what the + * previous box hangs below its own cap and what the next box carries above + * its own.

    + * + *

    The result is clamped at zero, and on this sheet that clamp fires. Type + * is sized on the horizontal scale because advance widths have to fit the + * columns, while pitches are measured on the vertical one, which is 6.3% + * tighter — so a line box can be marginally taller than the pitch the design + * sets it at. The closing note is the case: 22 design pixels of pitch against + * a line box that occupies 22.1. Zero spacing lands it a tenth of a pixel + * out, which is the closest the compressed page can come, and a negative gap + * is not something the engine accepts.

    + * + * @param pitchPx the cap-to-cap distance the design sets + * @param prevSize the size of the box above + * @param nextSize the size of the box below + * @return the gap between the two boxes + */ + static double capPitch(double pitchPx, double prevSize, double nextSize) { + return Math.max(0, py(pitchPx) - (LINE_BOX - CAP_INSET) * prevSize - CAP_INSET * nextSize); + } + + /** The same, for two lines of one size. */ + static double capPitch(double pitchPx, double size) { + return capPitch(pitchPx, size, size); + } + + static final double TITLE_SIZE = sizeForCap(39); + static final double SUPPLIER_NAME_SIZE = sizeForCap(16); + static final double PARTY_NAME_SIZE = sizeForCap(13); + static final double BODY_SIZE = sizeForCap(12); + static final double SMALL_SIZE = sizeForCap(11); + static final double TINY_SIZE = sizeForCap(10); + static final double LABEL_SIZE = sizeForCap(12); + static final double TABLE_HEAD_SIZE = sizeForCap(10); + static final double TOTAL_LABEL_SIZE = sizeForCap(15); + static final double TOTAL_VALUE_SIZE = sizeForCap(20); + static final double DUE_VALUE_SIZE = sizeForCap(15); + static final double NOTE_SIZE = sizeForCap(11); + static final double FOOTER_SIZE = sizeForCap(8); + + static DocumentTextStyle style(double size, DocumentColor color) { + return type(size, color, DocumentTextDecoration.DEFAULT); + } + + static DocumentTextStyle bold(double size, DocumentColor color) { + return type(size, color, DocumentTextDecoration.BOLD); + } + + private static DocumentTextStyle type(double size, DocumentColor color, + DocumentTextDecoration decoration) { + return DocumentTextStyle.builder() + .fontName(FACE) + .size(size) + .color(color) + .decoration(decoration) + .build(); + } + + // ------------------------------------------------------------------ + // The page box + // ------------------------------------------------------------------ + + /** + * Points reserved at the foot of every page for the enumeration, and the + * bottom margin that keeps the body out of it. + * + *

    The band's height is also what positions the number inside it: the + * engine seats the text's line box at the band's top and the band's foot is + * the paper's, so the number rises point for point with this constant. At + * nine points the descender came within a point of the paper edge, which + * reads as a printing accident rather than a margin.

    + * + *

    The margin is the band, and not the band plus a chosen number. + * Reserving less is what lets a continuation page's last row run into the + * enumeration — a defect page one is structurally unable to show, because its + * content ends well above the fold. Reserving more is a second gap doing the + * job the band's own top leading already does.

    + */ + static final double FOOTER_BAND_H = 12.0; + + static final double MARGIN_TOP = py(45) - CAP_INSET * TITLE_SIZE; + static final double MARGIN_X = px(44); + static final double MARGIN_BOTTOM = FOOTER_BAND_H; + + static final DocumentInsets PAGE_MARGIN = + new DocumentInsets(MARGIN_TOP, MARGIN_X, MARGIN_BOTTOM, MARGIN_X); + + static final double CONTENT_W = PAGE_W - 2 * MARGIN_X; + + /** Flow y of a design y, for the few places a gap is measured from the page. */ + static double flowY(double designY) { + return py(designY) - MARGIN_TOP; + } + + // ------------------------------------------------------------------ + // Columns and blocks + // ------------------------------------------------------------------ + + /** Every rule on the sheet. */ + static final double RULE_THICK = py(1.2); + + static final double PANEL_RADIUS = px(8); + static final double CARD_RADIUS = px(8); + + static final double LOCKUP_W = px(292); + static final double FOOTER_LOCKUP_W = px(180); + + static final double LEFT_COLUMN_W = px(463); + static final double DIVIDER_W = px(1); + static final double META_PAD_L = px(65); + static final double META_LABEL_W = px(175); + + static final double BILL_TO_GUTTER = px(56); + static final double BILL_TO_PAD_L = px(5); + static final double SHIP_TO_GUTTER = px(53); + static final double SHIP_TO_PAD_L = px(32); + + static final double DISC_D = sq(33); + static final double DISC_ICON = sq(18); + static final double PIN_W = sq(29); + static final double CONTACT_ICON = sq(18); + static final double CONTACT_GAP_PX = 20.0; + + // ------------------------------------------------------------------ + // The usage table + // ------------------------------------------------------------------ + + /** The pitch of the design's five body rows, measured across all of them. */ + private static final double TABLE_ROW_PITCH_Y = (1034 - 696) / 5.0; + + /** A separator asked for less than a point is laid out as one. */ + static final double TABLE_RULE_LAID_OUT = 1.0; + + static final double TABLE_HEADER_H = py(41); + static final double TABLE_ROW_H = py(TABLE_ROW_PITCH_Y) - TABLE_RULE_LAID_OUT; + + static final double TABLE_PAD_FIRST = px(29); + static final double TABLE_PAD_X = px(19); + static final double TABLE_PAD_RIGHT = px(8); + + /** + * A centred column carries far less side padding than a left-aligned one: + * nineteen design pixels on both sides does not leave room for the widest + * heading inside its measured column. + */ + static final double TABLE_PAD_CENTRED = px(6); + + static final double TABLE_PAD_ICON = px(18); + static final double TABLE_ICON = sq(36); + static final double TABLE_ICON_GUTTER = px(36); + static final double TABLE_ICON_GAP = px(22); + + // ------------------------------------------------------------------ + // Settlement + // ------------------------------------------------------------------ + + static final double PANEL_W = px(415); + static final double PANEL_PAD_X = px(24); + static final double PANEL_HEAD_GUTTER = px(50); + static final double PANEL_NOTE_GUTTER = px(39); + static final double PANEL_LABEL_W = px(180); + static final double PANEL_ICON = sq(24); + static final double PANEL_NOTE_ICON = sq(23); + static final double PANEL_PAD_ROWS_L = px(25); + + static final double SUMMARY_GAP_W = px(81); + static final double RIGHT_COLUMN_W = px(405); + static final double TOTALS_PAD_L = px(10); + static final double TOTALS_PAD_R = px(13); + static final double TOTALS_RULE_W = px(395); + + static final double CARD_DISC_D = sq(50); + static final double CARD_DISC_ICON = sq(26); + static final double CARD_PAD_L = px(23); + static final double CARD_GUTTER = px(69); + + static final double NOTE_ICON = sq(26); + static final double NOTE_GUTTER = px(45); + + // The design's measured y positions inside the settlement band. + static final double PANEL_TOP_Y = 1046; + static final double PANEL_ICON_Y = 1064; + static final double PANEL_ROW1_CAP_Y = 1103; + static final double PANEL_ROW_PITCH_Y = 21.7; + static final double PANEL_LAST_ROW_CAP_Y = 1254; + static final double PANEL_RULE_Y = 1286; + static final double PANEL_NOTE_CAP_Y = 1302; + static final double PANEL_NOTE_LAST_CAP_Y = 1324; + static final double PANEL_BOTTOM_Y = 1350; + + /** Design y of the last ink on the sheet: the foot of the identity band. */ + static final double REF_CONTENT_BOTTOM = 1505.0; + + // ------------------------------------------------------------------ + // Colour + // ------------------------------------------------------------------ + + static final DocumentColor ACCENT = DocumentColor.rgb(26, 115, 232); + static final DocumentColor INK = DocumentColor.rgb(32, 33, 36); + static final DocumentColor BODY = DocumentColor.rgb(60, 64, 67); + static final DocumentColor MUTED = DocumentColor.rgb(95, 99, 104); + static final DocumentColor BAND = DocumentColor.rgb(239, 244, 254); + static final DocumentColor CARD = DocumentColor.rgb(244, 248, 254); + static final DocumentColor PANEL_BORDER = DocumentColor.rgb(226, 236, 253); + static final DocumentColor RULE = DocumentColor.rgb(224, 224, 225); + static final DocumentColor HAIRLINE = DocumentColor.rgb(229, 229, 230); + static final DocumentColor WHITE = DocumentColor.WHITE; + + /** + * A run of spaces that advances {@code widthPx} design pixels at + * {@code size}, for the two places the design sets a measured gap inside one + * paragraph rather than between two. + * + * @param widthPx the gap the design measures + * @param size the type the gap is set in + * @return the spaces to write + */ + static String spaces(double widthPx, double size) { + int count = (int) Math.round(px(widthPx) / (FACE_SPACE_ADVANCE * size)); + return " ".repeat(Math.max(1, count)); + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/PlatformText.java b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/PlatformText.java new file mode 100644 index 000000000..431fb873e --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/PlatformText.java @@ -0,0 +1,79 @@ +package com.demcha.compose.document.templates.invoice.presets; + +import java.math.BigDecimal; +import java.text.NumberFormat; +import java.util.Locale; + +/** + * How the Platform invoice writes its figures. + * + *

    This design states its currency once per money column and writes the + * figures under it bare, so nothing here carries a code. The locale is stated + * rather than inherited, so the same data renders the same sheet on any + * machine.

    + */ +final class PlatformText { + + /** A metered rate is quoted to four places; an amount and a count to two. */ + private static final int RATE_MAX_DIGITS = 4; + private static final int MONEY_DIGITS = 2; + + private PlatformText() { + } + + /** An amount, grouped and to two places. */ + static String money(BigDecimal value) { + return decimal(value, MONEY_DIGITS, MONEY_DIGITS); + } + + /** An amount behind its currency code, for the one figure that stands alone. */ + static String coded(String currencyCode, BigDecimal value) { + return currencyCode == null || currencyCode.isBlank() + ? money(value) + : currencyCode.trim() + " " + money(value); + } + + /** + * A unit price at the precision it is quoted at. + * + *

    Metered rates are fractions of a currency unit — an hour of compute at + * 0.0670 rounds to 0.07 at an amount's two places, which is a different price + * and multiplies out to a different bill. A rate is written at its own scale, + * never at fewer than two places and never at more than four.

    + */ + static String rate(BigDecimal value) { + int digits = Math.min(Math.max(value.scale(), MONEY_DIGITS), RATE_MAX_DIGITS); + return decimal(value, digits, digits); + } + + /** + * A usage figure, written the way the design writes it. + * + *

    Usage that names a unit is a measurement and carries two places even + * when it lands on a whole number — 730.00 hours, 250.00 gigabytes — because + * the meter reads to that precision and a bare "730" claims it did not. + * Usage that names no unit is a tally of whole things, and the design writes + * it bare.

    + * + * @param value the metered figure + * @param unit what it counts, blank when the line counts whole things + * @return the figure as the sheet prints it + */ + static String usage(BigDecimal value, String unit) { + if (unit == null || unit.isBlank()) { + BigDecimal stripped = value.stripTrailingZeros(); + return stripped.scale() <= 0 + ? stripped.toBigInteger().toString() + : stripped.toPlainString(); + } + return decimal(value, MONEY_DIGITS, MONEY_DIGITS); + } + + private static String decimal(BigDecimal value, int min, int max) { + NumberFormat format = NumberFormat.getNumberInstance(Locale.ENGLISH); + format.setGroupingUsed(true); + format.setMinimumFractionDigits(min); + format.setMaximumFractionDigits(max); + return format.format(value); + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/PlatformWidgets.java b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/PlatformWidgets.java new file mode 100644 index 000000000..1304be467 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/PlatformWidgets.java @@ -0,0 +1,115 @@ +package com.demcha.compose.document.templates.invoice.presets; + +import com.demcha.compose.document.dsl.RowBuilder; +import com.demcha.compose.document.dsl.SectionBuilder; +import com.demcha.compose.document.dsl.ShapeContainerBuilder; +import com.demcha.compose.document.node.DocumentNode; +import com.demcha.compose.document.node.LayerAlign; +import com.demcha.compose.document.node.RowVerticalAlign; +import com.demcha.compose.document.style.DocumentRowColumn; +import com.demcha.compose.document.templates.data.invoice.InvoiceServiceLines; + +import java.util.function.Consumer; + +import static com.demcha.compose.document.templates.invoice.presets.PlatformStyles.ACCENT; +import static com.demcha.compose.document.templates.invoice.presets.PlatformStyles.BODY_SIZE; +import static com.demcha.compose.document.templates.invoice.presets.PlatformStyles.DISC_D; +import static com.demcha.compose.document.templates.invoice.presets.PlatformStyles.DISC_ICON; +import static com.demcha.compose.document.templates.invoice.presets.PlatformStyles.INK; +import static com.demcha.compose.document.templates.invoice.presets.PlatformStyles.MUTED; +import static com.demcha.compose.document.templates.invoice.presets.PlatformStyles.SMALL_SIZE; +import static com.demcha.compose.document.templates.invoice.presets.PlatformStyles.TABLE_ICON; +import static com.demcha.compose.document.templates.invoice.presets.PlatformStyles.TABLE_ICON_GAP; +import static com.demcha.compose.document.templates.invoice.presets.PlatformStyles.TABLE_ICON_GUTTER; +import static com.demcha.compose.document.templates.invoice.presets.PlatformStyles.bold; +import static com.demcha.compose.document.templates.invoice.presets.PlatformStyles.capPitch; +import static com.demcha.compose.document.templates.invoice.presets.PlatformStyles.style; + +/** + * The shapes the Platform invoice repeats: a horizontal pair inside a column, + * the filled disc a party heading opens with, and the mark-and-two-lines block + * that fills the table's first column. + */ +final class PlatformWidgets { + + private PlatformWidgets() { + } + + /** + * A row wrapped in a layer, so it can sit inside a row cell. + * + *

    A row nested in a row cell is refused by the layout compiler, and + * through a section too — it is the ancestry that matters, not the immediate + * parent. A row wrapped in a LayerStack layer lays out horizontally there. + * Every horizontal pair below the top level goes through here.

    + * + * @param parent the section the pair belongs to + * @param name the pair's name, which its holder and layer extend + * @param spec the row itself + */ + static void layeredRow(SectionBuilder parent, String name, Consumer spec) { + SectionBuilder holder = new SectionBuilder(); + holder.name(name + "Holder"); + holder.addRow(name, spec); + DocumentNode node = holder.build(); + parent.addLayerStack(stack -> stack + .name(name + "Layer") + .layer(node, LayerAlign.TOP_LEFT, 0)); + } + + /** A mark knocked out of a filled accent disc. */ + static DocumentNode disc(String name, String token, double diameter, double markSize) { + return new ShapeContainerBuilder() + .name(name) + .circle(diameter) + .fillColor(ACCENT) + .center(PlatformIcons.icon(token).node(markSize)) + .build(); + } + + /** The party disc, at the size the two headings use. */ + static DocumentNode partyDisc(String name, String token) { + return disc(name, token, DISC_D, DISC_ICON); + } + + /** + * The service mark and the two-line description beside it, as one table cell's + * content. + * + *

    The mark hangs in a fixed gutter and the text takes the rest, so a longer + * product name wraps under itself rather than under the mark.

    + * + * @param line the service line + * @param index the line's index, which names the block + * @return the block + */ + static DocumentNode descriptionCell(InvoiceServiceLines.Line line, int index) { + String token = line.icon(); + SectionBuilder holder = new SectionBuilder(); + holder.name("Description_" + index + "Holder"); + holder.addRow("Description_" + index, cell -> { + cell.spacing(0) + .verticalAlign(RowVerticalAlign.CENTER) + .columns(DocumentRowColumn.fixed(TABLE_ICON_GUTTER + TABLE_ICON_GAP), + DocumentRowColumn.weight(1)); + cell.addSection("DescriptionIcon_" + index, gutter -> { + gutter.spacing(0); + if (!token.isBlank()) { + gutter.addSvgIcon(PlatformIcons.icon(token), TABLE_ICON); + } + }); + cell.addSection("DescriptionText_" + index, text -> { + text.spacing(capPitch(23, BODY_SIZE, SMALL_SIZE)); + text.addParagraph(p -> p + .name("LineTitle_" + index) + .text(line.title()) + .textStyle(bold(BODY_SIZE, INK))); + text.addParagraph(p -> p + .name("LineSubtitle_" + index) + .text(line.description()) + .textStyle(style(SMALL_SIZE, MUTED))); + }); + }); + return holder.build(); + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/SubscriptionClosing.java b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/SubscriptionClosing.java new file mode 100644 index 000000000..7d0405bc4 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/SubscriptionClosing.java @@ -0,0 +1,304 @@ +package com.demcha.compose.document.templates.invoice.presets; + +import com.demcha.compose.document.dsl.PageFlowBuilder; +import com.demcha.compose.document.dsl.ParagraphBuilder; +import com.demcha.compose.document.node.DocumentLinkOptions; +import com.demcha.compose.document.node.HorizontalAlign; +import com.demcha.compose.document.node.RowVerticalAlign; +import com.demcha.compose.document.node.TextAlign; +import com.demcha.compose.document.style.DocumentColor; +import com.demcha.compose.document.style.DocumentEdge; +import com.demcha.compose.document.style.DocumentInsets; +import com.demcha.compose.document.style.DocumentRowColumn; +import com.demcha.compose.document.svg.SvgIcon; +import com.demcha.compose.document.templates.core.identity.ContactUri; +import com.demcha.compose.document.templates.data.invoice.InvoiceContactBlock; +import com.demcha.compose.document.templates.data.invoice.InvoicePaymentBlock; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; + +import static com.demcha.compose.document.templates.invoice.presets.SubscriptionStyles.ACCENT; +import static com.demcha.compose.document.templates.invoice.presets.SubscriptionStyles.BAND_FILL; +import static com.demcha.compose.document.templates.invoice.presets.SubscriptionStyles.CLOSE_BODY_SIZE; +import static com.demcha.compose.document.templates.invoice.presets.SubscriptionStyles.CLOSE_HEAD_SIZE; +import static com.demcha.compose.document.templates.invoice.presets.SubscriptionStyles.CLOSE_ICON_COLUMN; +import static com.demcha.compose.document.templates.invoice.presets.SubscriptionStyles.CLOSE_ICON_LIFT; +import static com.demcha.compose.document.templates.invoice.presets.SubscriptionStyles.CLOSE_ICON_W; +import static com.demcha.compose.document.templates.invoice.presets.SubscriptionStyles.CLOSE_LINE_PITCH; +import static com.demcha.compose.document.templates.invoice.presets.SubscriptionStyles.CLOSE_PAD_B; +import static com.demcha.compose.document.templates.invoice.presets.SubscriptionStyles.CLOSE_PAD_L; +import static com.demcha.compose.document.templates.invoice.presets.SubscriptionStyles.CLOSE_PAD_T; +import static com.demcha.compose.document.templates.invoice.presets.SubscriptionStyles.CLOSE_TEXT_INDENT; +import static com.demcha.compose.document.templates.invoice.presets.SubscriptionStyles.CONTENT_PAD; +import static com.demcha.compose.document.templates.invoice.presets.SubscriptionStyles.GAP_NOTE_TO_STRIP; +import static com.demcha.compose.document.templates.invoice.presets.SubscriptionStyles.GAP_PAYMENT_TO_NOTE; +import static com.demcha.compose.document.templates.invoice.presets.SubscriptionStyles.GAP_STRIP_TO_BAND; +import static com.demcha.compose.document.templates.invoice.presets.SubscriptionStyles.GAP_TABLE_TO_PAYMENT; +import static com.demcha.compose.document.templates.invoice.presets.SubscriptionStyles.HAIRLINE; +import static com.demcha.compose.document.templates.invoice.presets.SubscriptionStyles.HEADING_SIZE; +import static com.demcha.compose.document.templates.invoice.presets.SubscriptionStyles.INK; +import static com.demcha.compose.document.templates.invoice.presets.SubscriptionStyles.PAGE_W; +import static com.demcha.compose.document.templates.invoice.presets.SubscriptionStyles.PAYMENT_PX; +import static com.demcha.compose.document.templates.invoice.presets.SubscriptionStyles.PAY_COLUMNS_GAP; +import static com.demcha.compose.document.templates.invoice.presets.SubscriptionStyles.PAY_DIVIDER; +import static com.demcha.compose.document.templates.invoice.presets.SubscriptionStyles.PAY_ICON_W; +import static com.demcha.compose.document.templates.invoice.presets.SubscriptionStyles.PAY_LABEL_GAP; +import static com.demcha.compose.document.templates.invoice.presets.SubscriptionStyles.PAY_LABEL_SIZE; +import static com.demcha.compose.document.templates.invoice.presets.SubscriptionStyles.PAY_NOTE_SIZE; +import static com.demcha.compose.document.templates.invoice.presets.SubscriptionStyles.PAY_OUTER_NUDGE; +import static com.demcha.compose.document.templates.invoice.presets.SubscriptionStyles.PAY_VALUE_GAP; +import static com.demcha.compose.document.templates.invoice.presets.SubscriptionStyles.PAY_VALUE_SIZE; +import static com.demcha.compose.document.templates.invoice.presets.SubscriptionStyles.STRIP_H; +import static com.demcha.compose.document.templates.invoice.presets.SubscriptionStyles.STRIP_PX; +import static com.demcha.compose.document.templates.invoice.presets.SubscriptionStyles.STRIP_TOTAL_PX; +import static com.demcha.compose.document.templates.invoice.presets.SubscriptionStyles.MARGIN_X; +import static com.demcha.compose.document.templates.invoice.presets.SubscriptionStyles.baselineGap; +import static com.demcha.compose.document.templates.invoice.presets.SubscriptionStyles.bold; +import static com.demcha.compose.document.templates.invoice.presets.SubscriptionStyles.cycle; +import static com.demcha.compose.document.templates.invoice.presets.SubscriptionStyles.plain; +import static com.demcha.compose.document.templates.invoice.presets.SubscriptionWidgets.headingPlaque; + +/** + * The foot of the sheet: the payment band, the reference note under it, the + * four-segment strip, and the closing band that bleeds to three paper edges. + */ +final class SubscriptionClosing { + + private SubscriptionClosing() { + } + + /** + * The payment band: one marked cell per field, divided by hairlines. + * + *

    The design's outer two cells are not centred on the space the dividers + * give them — their content sits inboard — while the middle three are centred + * to within three pixels, so the nudge is on the outer pair only.

    + * + *

    The cells take their widths from the design's five, so a payment block + * with a different number of fields divides the band evenly instead.

    + */ + static void renderPaymentBand(PageFlowBuilder page, InvoicePaymentBlock payment) { + List fields = payment.fields(); + if (fields.isEmpty()) { + return; + } + page.addSection("PaymentDetails", section -> { + section.padding(CONTENT_PAD); + section.margin(new DocumentInsets(GAP_TABLE_TO_PAYMENT, 0, 0, 0)); + section.spacing(0); + section.keepTogether(); + headingPlaque(section, "PaymentHeading", payment.heading(), HEADING_SIZE, ACCENT); + section.addRow("PaymentColumns", row -> { + row.spacing(0); + row.margin(new DocumentInsets(PAY_COLUMNS_GAP, 0, 0, 0)); + row.weights(weights(fields.size())); + for (int i = 0; i < fields.size(); i++) { + InvoicePaymentBlock.Field field = fields.get(i); + SvgIcon mark = SubscriptionIcons.band(i); + boolean divided = i > 0; + boolean outer = i == 0 || i == fields.size() - 1; + int index = i; + row.addSection("PaymentColumn_" + index, cell -> { + cell.spacing(0); + if (outer) { + cell.padding(new DocumentInsets(0, PAY_OUTER_NUDGE, 0, 0)); + } + if (divided) { + cell.accentLeft(HAIRLINE, PAY_DIVIDER); + } + if (mark != null) { + cell.addSvgIcon(mark, PAY_ICON_W, HorizontalAlign.CENTER); + } + cell.addParagraph(p -> p + .name("PaymentLabel_" + index) + .text(field.label()) + .textStyle(bold(PAY_LABEL_SIZE, INK)) + .align(TextAlign.CENTER) + .margin(new DocumentInsets(PAY_LABEL_GAP, 0, 0, 0))); + cell.addParagraph(p -> p + .name("PaymentValue_" + index) + .text(field.value()) + .textStyle(plain(PAY_VALUE_SIZE, INK)) + .align(TextAlign.CENTER) + .margin(new DocumentInsets(PAY_VALUE_GAP, 0, 0, 0))); + }); + } + }); + }); + } + + /** + * The design's five column widths when the band has five cells, and an even + * division when it has any other number — measured shares only describe the + * band they were measured on. + */ + private static double[] weights(int cells) { + double[] weights = new double[cells]; + if (cells == PAYMENT_PX.length) { + double total = 0; + for (double share : PAYMENT_PX) { + total += share; + } + for (int i = 0; i < cells; i++) { + weights[i] = PAYMENT_PX[i] / total; + } + return weights; + } + for (int i = 0; i < cells; i++) { + weights[i] = 1.0 / cells; + } + return weights; + } + + /** The instruction under the band. */ + static void renderPaymentNote(PageFlowBuilder page, InvoicePaymentBlock payment) { + if (payment.instruction().isBlank()) { + return; + } + page.addSection("PaymentNote", section -> { + section.padding(CONTENT_PAD); + section.margin(new DocumentInsets(GAP_PAYMENT_TO_NOTE, 0, 0, 0)); + section.spacing(0); + section.addParagraph(p -> p + .name("PaymentNote") + .text(payment.instruction()) + .textStyle(plain(PAY_NOTE_SIZE, INK))); + }); + } + + /** The four-segment strip, at the design's measured widths across the paper. */ + static void renderStrip(PageFlowBuilder page) { + page.addRow("BrandStrip", row -> { + row.margin(new DocumentInsets(GAP_NOTE_TO_STRIP, 0, 0, 0)); + row.spacing(0); + DocumentRowColumn[] columns = new DocumentRowColumn[STRIP_PX.length]; + for (int i = 0; i < STRIP_PX.length; i++) { + columns[i] = DocumentRowColumn.fixed(PAGE_W * STRIP_PX[i] / STRIP_TOTAL_PX); + } + row.columns(columns); + for (int i = 0; i < STRIP_PX.length; i++) { + DocumentColor colour = cycle(i); + double width = PAGE_W * STRIP_PX[i] / STRIP_TOTAL_PX; + int index = i; + row.addShape(shape -> shape + .name("BrandStripSegment_" + index) + .size(width, STRIP_H) + .fillColor(colour)); + } + }); + } + + /** + * The closing band. + * + *

    It reaches three paper edges by bleeding rather than by being the last + * thing on the page, which is what lets the enumeration sit over its fill + * without the band giving up any of its own width.

    + * + *

    Its lines come from the payment block's sign-off and due notice: the + * sign-off is the headline, and the notice's own lines are the prose under + * it, with any address the prose names made reachable.

    + */ + static void renderBand(PageFlowBuilder page, InvoicePaymentBlock payment, + InvoiceContactBlock supplier) { + if (payment.signOff().isBlank() && payment.dueNotice().isBlank()) { + return; + } + page.addSection("ClosingBand", section -> { + section.name("ClosingBand"); + section.spacing(0); + section.keepTogether(); + section.margin(new DocumentInsets(GAP_STRIP_TO_BAND, 0, 0, 0)); + section.padding(new DocumentInsets(CLOSE_PAD_T, MARGIN_X, CLOSE_PAD_B, CLOSE_PAD_L)); + section.fillColor(BAND_FILL); + section.bleedToEdge(DocumentEdge.LEFT, DocumentEdge.RIGHT, DocumentEdge.BOTTOM); + section.addRow("ClosingLockup", row -> { + row.spacing(0); + row.verticalAlign(RowVerticalAlign.CENTER); + row.columns(DocumentRowColumn.fixed(CLOSE_ICON_COLUMN), + DocumentRowColumn.weight(1)); + row.addSection("ClosingMark", cell -> { + cell.spacing(0); + cell.margin(new DocumentInsets(0, 0, CLOSE_ICON_LIFT, 0)); + cell.addSvgIcon(SubscriptionIcons.icon(SubscriptionIcons.SHIELD), + CLOSE_ICON_W, HorizontalAlign.LEFT); + }); + row.addSection("ClosingMessage", cell -> { + cell.spacing(0); + cell.padding(new DocumentInsets(0, 0, 0, CLOSE_TEXT_INDENT)); + if (!payment.signOff().isBlank()) { + cell.addParagraph(p -> p + .name("ClosingHeadline") + .text(payment.signOff()) + .textStyle(bold(CLOSE_HEAD_SIZE, INK))); + } + String[] lines = payment.dueNotice().isBlank() + ? new String[0] + : payment.dueNotice().split("\n"); + for (int i = 0; i < lines.length; i++) { + String prose = lines[i]; + int index = i; + cell.addParagraph(p -> { + p.name("ClosingLine_" + index); + p.margin(new DocumentInsets( + baselineGap(CLOSE_LINE_PITCH, + index == 0 ? CLOSE_HEAD_SIZE : CLOSE_BODY_SIZE, + CLOSE_BODY_SIZE), + 0, 0, 0)); + writeReachable(p, prose, supplier); + }); + } + }); + }); + }); + } + + /** + * The prose, with any address it names made reachable. + * + *

    The band tells a reader where to write, and the address is printed + * inside the sentence. Written as one string the whole sentence would have to + * carry the target or none of it would, so the address becomes its own run + * and the text around it stays plain.

    + */ + private static void writeReachable(ParagraphBuilder paragraph, String prose, + InvoiceContactBlock supplier) { + List found = new ArrayList<>(); + addIfPresent(found, prose, supplier.email(), ContactUri.mailLink(supplier.email())); + addIfPresent(found, prose, supplier.website(), ContactUri.webLink(supplier.website())); + addIfPresent(found, prose, supplier.phone(), ContactUri.telLink(supplier.phone())); + found.sort(Comparator.comparingInt(Reachable::at)); + + int cursor = 0; + for (Reachable reachable : found) { + if (reachable.at() < cursor) { + continue; + } + if (reachable.at() > cursor) { + paragraph.inlineText(prose.substring(cursor, reachable.at()), + plain(CLOSE_BODY_SIZE, INK)); + } + paragraph.inlineText(reachable.text(), plain(CLOSE_BODY_SIZE, INK), reachable.link()); + cursor = reachable.at() + reachable.text().length(); + } + if (cursor < prose.length()) { + paragraph.inlineText(prose.substring(cursor), plain(CLOSE_BODY_SIZE, INK)); + } + } + + private static void addIfPresent(List found, String prose, String text, + DocumentLinkOptions link) { + if (text == null || text.isBlank() || link == null) { + return; + } + int at = prose.indexOf(text); + if (at >= 0) { + found.add(new Reachable(at, text, link)); + } + } + + private record Reachable(int at, String text, DocumentLinkOptions link) { + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/SubscriptionIcons.java b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/SubscriptionIcons.java new file mode 100644 index 000000000..eedecfd3a --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/SubscriptionIcons.java @@ -0,0 +1,74 @@ +package com.demcha.compose.document.templates.invoice.presets; + +import com.demcha.compose.document.svg.SvgIcon; + +import java.io.IOException; +import java.io.InputStream; +import java.io.UncheckedIOException; +import java.nio.charset.StandardCharsets; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * The marks the Subscription invoice packages. + * + *

    All of them are the preset's, not the document's. Five open the payment + * band's cells and one opens the closing band, and which glyph opens which is a + * property of the design rather than of the invoice — a bank payment block's + * cells are the same five roles wherever it is printed.

    + * + *

    The band's marks are drawn in order: the design is five marked + * cells, so a payment block with more fields than that draws the extra ones + * unmarked rather than repeating a glyph that would then mean nothing.

    + */ +final class SubscriptionIcons { + + /** The payment band's marks, in the order the design's cells take them. */ + static final List BAND = + List.of("beneficiary", "bank", "transfer", "card", "reference"); + + static final String SHIELD = "shield"; + + private static final String ICON_ROOT = "/templates/invoice/subscription/icons/"; + + private static final Map CACHE = new ConcurrentHashMap<>(); + + private SubscriptionIcons() { + } + + /** + * The mark the payment band's cell at {@code index} opens with, or + * {@code null} when the design has no cell there. + * + * @param index the cell's position in the band + * @return the icon, or {@code null} past the band's own five cells + */ + static SvgIcon band(int index) { + return index < 0 || index >= BAND.size() ? null : icon(BAND.get(index)); + } + + /** + * A packaged mark by name. + * + * @param token the mark's name + * @return the parsed icon + */ + static SvgIcon icon(String token) { + return CACHE.computeIfAbsent(token, SubscriptionIcons::read); + } + + private static SvgIcon read(String token) { + String resourcePath = ICON_ROOT + token + ".svg"; + try (InputStream input = SubscriptionIcons.class.getResourceAsStream(resourcePath)) { + if (input == null) { + throw new IllegalStateException( + "Missing subscription invoice icon: " + resourcePath); + } + return SvgIcon.parse(new String(input.readAllBytes(), StandardCharsets.UTF_8)); + } catch (IOException e) { + throw new UncheckedIOException( + "Failed to read subscription invoice icon: " + resourcePath, e); + } + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/SubscriptionInvoice.java b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/SubscriptionInvoice.java new file mode 100644 index 000000000..79842b21f --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/SubscriptionInvoice.java @@ -0,0 +1,181 @@ +package com.demcha.compose.document.templates.invoice.presets; + +import com.demcha.compose.document.api.DocumentSession; +import com.demcha.compose.document.output.DocumentHeaderFooter; +import com.demcha.compose.document.output.DocumentHeaderFooterZone; +import com.demcha.compose.document.output.DocumentPageNumbering; +import com.demcha.compose.document.style.DocumentInsets; +import com.demcha.compose.document.templates.api.DocumentTemplate; +import com.demcha.compose.document.templates.data.invoice.StructuredInvoiceData; + +import java.util.Objects; + +import static com.demcha.compose.document.templates.invoice.presets.SubscriptionStyles.CHROME_GREY; +import static com.demcha.compose.document.templates.invoice.presets.SubscriptionStyles.CHROME_SIZE; +import static com.demcha.compose.document.templates.invoice.presets.SubscriptionStyles.FACE; +import static com.demcha.compose.document.templates.invoice.presets.SubscriptionStyles.FOOTER_H; +import static com.demcha.compose.document.templates.invoice.presets.SubscriptionStyles.FOOTER_RESERVE; +import static com.demcha.compose.document.templates.invoice.presets.SubscriptionStyles.MARGIN_T; +import static com.demcha.compose.document.templates.invoice.presets.SubscriptionStyles.PAGE; + +/** + * Subscription — an invoice for seats and licences billed with tax per line: a + * lockup against the title, the supplier's details beside a metadata panel whose + * rows each open with a coloured bar, two addressed parties under coloured + * underlines, a six-column table carrying a tax rate on every line, the notes + * beside the totals, a marked payment band, a four-segment strip, and a closing + * band that bleeds to three paper edges. + * + *

    Tax on the line, not only in the sum

    + * + *

    This is the first preset in the family to draw + * {@code InvoiceServiceLines.Line.vatRate()} and its caption, because it bills + * subscriptions in a jurisdiction that prints the rate per line rather than only + * as a total. It is also the one that numbers its lines: a line states its own + * number through {@code lineNumber()}, and one that states none is numbered by + * its position.

    + * + *

    Colour by position, never by meaning

    + * + *

    The metadata bars, the party underlines and the closing strip all draw from + * one four-colour cycle indexed by position — the fourth metadata row is amber + * because it is fourth. Nothing in the document ever names a colour.

    + * + *

    The page has no side margins

    + * + *

    The strip and the closing band have to reach the paper edges, and they do + * it by being the only blocks that carry no horizontal padding — every other + * block carries the content inset itself. The bottom margin reserves the + * enumeration's strip, and the band reaches the bottom edge by bleeding rather + * than by being the last thing on the page, so reserving it costs the design + * nothing but a few points of the band's lower padding.

    + * + *

    Every cell is unstroked

    + * + *

    A cell's stroke draws all four of its edges and there is no per-edge + * control, so any stroke that buys the table's horizontal rule also buys five + * verticals it does not have. The rule under the header is an accent on the + * header cell, and the rule under each data row is a row of its own.

    + * + *

    Money carries its symbol

    + * + *

    This design writes money with the currency's symbol against the digits + * rather than with a code in front or a caption above, so every figure carries + * its own currency and none of the columns state one. Figures state their locale + * rather than inheriting it.

    + * + *

    That is also this preset's one boundary. The columns are measured for a + * one-character mark, so a currency the runtime knows only by its three-letter + * code — several ordinary European ones among them — does not fit the unit-price + * column, and the render is refused there rather than letting the figure run + * under its neighbour. A document billed in such a currency wants a preset that + * states the currency in the column caption instead.

    + * + *

    The lockup is the caller's

    + * + *

    The masthead draws the logo from {@code InvoiceBrand} at the design's + * measured width, and the brand's name beside it as the wordmark. The templates + * artifact carries no mark of its own.

    + * + *

    Fonts

    + * + *

    The sheet is set in Fira Sans Condensed, chosen by measurement: the usual + * humanist substitute matches this design's mixed-case widths but sets uppercase + * about 20% wider per unit of cap height, which shows immediately as headings + * and their coloured rules overshooting. The templates artifact carries no fonts + * — register the family on the session, or the engine substitutes and the + * measured geometry no longer matches the type sitting in it.

    + * + *

    Usage:

    + *
    {@code
    + * DocumentTemplate template = SubscriptionInvoice.create();
    + * template.compose(session, invoice);
    + * }
    + * + * @since 2.4.0 + */ +public final class SubscriptionInvoice { + + /** Stable identifier of this preset. */ + public static final String ID = "subscription-invoice"; + + /** Human-readable name of this preset. */ + public static final String DISPLAY_NAME = "Subscription Invoice"; + + private SubscriptionInvoice() { + } + + /** + * Creates the template. + * + * @return a template composing a {@link StructuredInvoiceData} + */ + public static DocumentTemplate create() { + return new Template(); + } + + private record Template() implements DocumentTemplate { + + @Override + public String id() { + return ID; + } + + @Override + public String displayName() { + return DISPLAY_NAME; + } + + @Override + public void compose(DocumentSession document, StructuredInvoiceData data) { + Objects.requireNonNull(document, "document"); + Objects.requireNonNull(data, "data"); + + document.pageSize(PAGE); + // No side margins and no real bottom margin: the strip and the + // closing band have to reach the paper edges, and they do it by + // being the only blocks that carry no horizontal padding. + document.margin(new DocumentInsets(MARGIN_T, 0, FOOTER_RESERVE, 0)); + renderPageNumber(document); + + String currency = data.currencyCode(); + document.pageFlow(page -> { + page.name("SubscriptionInvoice"); + page.padding(DocumentInsets.zero()); + page.spacing(0); + SubscriptionMasthead.renderBrandHeader(page, data.brand(), data.masthead()); + SubscriptionMasthead.renderIdentityBand(page, data.supplier(), data.masthead()); + SubscriptionParties.render(page, data.billTo(), data.shipTo()); + SubscriptionLines.render(page, data.serviceLines(), currency); + SubscriptionSettlement.render(page, data.notes(), data.totals(), currency); + SubscriptionClosing.renderPaymentBand(page, data.payment()); + SubscriptionClosing.renderPaymentNote(page, data.payment()); + SubscriptionClosing.renderStrip(page); + SubscriptionClosing.renderBand(page, data.payment(), data.supplier()); + }); + } + + /** + * The enumeration. The design has none — it is one sheet — but the + * document flows, and a reader holding page 1 of 2 has to be able to tell + * that page 2 is missing. + * + *

    It sits in the foot, over the closing band's fill. The face is + * stated because the zone's own default is the standard-14 one, which + * would leave the number the single line on the sheet in a family the + * design never uses.

    + */ + private static void renderPageNumber(DocumentSession document) { + document.footer(DocumentHeaderFooter.builder() + .zone(DocumentHeaderFooterZone.FOOTER) + .height((float) FOOTER_H) + .fontName(FACE) + .fontSize((float) CHROME_SIZE) + .textColor(CHROME_GREY) + .centerText("Page {page} of {pages}") + .showSeparator(false) + .numbering(DocumentPageNumbering.DEFAULT) + .build()); + } + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/SubscriptionLines.java b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/SubscriptionLines.java new file mode 100644 index 000000000..4a8470d37 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/SubscriptionLines.java @@ -0,0 +1,175 @@ +package com.demcha.compose.document.templates.invoice.presets; + +import com.demcha.compose.document.dsl.PageFlowBuilder; +import com.demcha.compose.document.dsl.SectionBuilder; +import com.demcha.compose.document.dsl.SpacerBuilder; +import com.demcha.compose.document.node.TextAlign; +import com.demcha.compose.document.style.DocumentInsets; +import com.demcha.compose.document.table.DocumentTableCell; +import com.demcha.compose.document.table.DocumentTableColumn; +import com.demcha.compose.document.table.DocumentTableStyle; +import com.demcha.compose.document.table.DocumentTableTextAnchor; +import com.demcha.compose.document.templates.data.invoice.InvoiceServiceLines; + +import java.util.List; + +import static com.demcha.compose.document.templates.invoice.presets.SubscriptionStyles.ACCENT; +import static com.demcha.compose.document.templates.invoice.presets.SubscriptionStyles.CELL_PAD_L; +import static com.demcha.compose.document.templates.invoice.presets.SubscriptionStyles.CELL_PAD_R; +import static com.demcha.compose.document.templates.invoice.presets.SubscriptionStyles.CELL_PAD_V; +import static com.demcha.compose.document.templates.invoice.presets.SubscriptionStyles.COLUMN_PX; +import static com.demcha.compose.document.templates.invoice.presets.SubscriptionStyles.COLUMN_TOTAL_PX; +import static com.demcha.compose.document.templates.invoice.presets.SubscriptionStyles.CONTENT_PAD; +import static com.demcha.compose.document.templates.invoice.presets.SubscriptionStyles.CONTENT_W; +import static com.demcha.compose.document.templates.invoice.presets.SubscriptionStyles.GAP_PARTIES_TO_TABLE; +import static com.demcha.compose.document.templates.invoice.presets.SubscriptionStyles.HAIRLINE; +import static com.demcha.compose.document.templates.invoice.presets.SubscriptionStyles.HEAD_PAD_H; +import static com.demcha.compose.document.templates.invoice.presets.SubscriptionStyles.HEAD_PAD_V; +import static com.demcha.compose.document.templates.invoice.presets.SubscriptionStyles.INK; +import static com.demcha.compose.document.templates.invoice.presets.SubscriptionStyles.NO_RULE; +import static com.demcha.compose.document.templates.invoice.presets.SubscriptionStyles.TABLE_BODY_SIZE; +import static com.demcha.compose.document.templates.invoice.presets.SubscriptionStyles.TABLE_HEAD_SIZE; +import static com.demcha.compose.document.templates.invoice.presets.SubscriptionStyles.TABLE_RULE; +import static com.demcha.compose.document.templates.invoice.presets.SubscriptionStyles.TABLE_TOP_RULE; +import static com.demcha.compose.document.templates.invoice.presets.SubscriptionStyles.bold; +import static com.demcha.compose.document.templates.invoice.presets.SubscriptionStyles.plain; +import static com.demcha.compose.document.templates.invoice.presets.SubscriptionStyles.px; + +/** + * The line-item table: six columns under an accent rule, a caps header that + * repeats on every page it reaches, and a hairline under every row. + * + *

    Why every cell is unstroked

    + * + *

    A cell's stroke draws all four of its edges and there is no per-edge + * control, so any stroke that buys the design's horizontal rule also buys five + * verticals it does not have. Every cell here therefore carries none: the rule + * under the header is an accent on the header cell, and the rule under each data + * row is a row of its own — a filled cell spanning every column, one rule + * thick.

    + */ +final class SubscriptionLines { + + private SubscriptionLines() { + } + + /** + * The table. + * + * @param page the page flow + * @param serviceLines the lines and their column captions + * @param currencyCode the code every money figure carries as a symbol + */ + static void render(PageFlowBuilder page, InvoiceServiceLines serviceLines, + String currencyCode) { + page.addSection("LineItems", section -> { + section.padding(CONTENT_PAD); + section.margin(new DocumentInsets(GAP_PARTIES_TO_TABLE, 0, 0, 0)); + section.spacing(0); + // The accent rule belongs to a node whose box is the content box. + // The section above carries the page's horizontal padding, so its own + // box is the full paper width and an accent on it would run edge to + // edge. + section.addSection("LineItemsRuled", ruled -> { + ruled.spacing(0); + ruled.accentTop(ACCENT, TABLE_TOP_RULE); + renderTable(ruled, serviceLines, currencyCode); + }); + }); + } + + private static void renderTable(SectionBuilder ruled, InvoiceServiceLines serviceLines, + String currencyCode) { + InvoiceServiceLines.Columns columns = serviceLines.columns(); + ruled.addTable(table -> { + table.name("LineItemTable"); + table.defaultCellStyle(DocumentTableStyle.builder().stroke(NO_RULE).build()); + table.columns(column(0), column(1), column(2), column(3), column(4), column(5)); + table.headerCells( + headerCell(columns.index(), TextAlign.LEFT), + headerCell(columns.description(), TextAlign.LEFT), + headerCell(columns.quantity(), TextAlign.CENTER), + headerCell(columns.unitPrice(), TextAlign.CENTER), + headerCell(columns.vat(), TextAlign.CENTER), + headerCell(columns.amount(), TextAlign.CENTER)); + table.repeatHeader(); + + List lines = serviceLines.lines(); + for (int i = 0; i < lines.size(); i++) { + InvoiceServiceLines.Line line = lines.get(i); + table.rowCells( + dataCell(number(line, i), DocumentTableTextAnchor.CENTER_LEFT), + dataCell(line.title(), DocumentTableTextAnchor.CENTER_LEFT), + dataCell(SubscriptionText.quantity(line.quantity()), + DocumentTableTextAnchor.CENTER), + dataCell(SubscriptionText.money(currencyCode, line.unitPrice()), + DocumentTableTextAnchor.CENTER_RIGHT), + dataCell(line.vatRate(), DocumentTableTextAnchor.CENTER), + dataCell(SubscriptionText.money(currencyCode, line.amount()), + DocumentTableTextAnchor.CENTER_RIGHT)); + table.rowCells(ruleRow()); + } + }); + } + + /** + * The number the sheet prints for a line: the one the document states, or its + * position when it states none. A document continuing a numbering from + * somewhere else can say so; one that does not gets 1, 2, 3. + */ + private static String number(InvoiceServiceLines.Line line, int index) { + return String.valueOf(line.lineNumber() > 0 ? line.lineNumber() : index + 1); + } + + private static DocumentTableColumn column(int index) { + return DocumentTableColumn.fixed(CONTENT_W * COLUMN_PX[index] / COLUMN_TOTAL_PX); + } + + /** + * A caption cell. A centred caption needs room, not alignment, so it takes + * the narrower symmetric padding; a left-aligned one is positioned by its + * padding and keeps the data cells' measured inset. + */ + private static DocumentTableCell headerCell(String caption, TextAlign align) { + SectionBuilder cell = new SectionBuilder(); + cell.name("LineItemHeaderCell"); + cell.spacing(0); + double padL = align == TextAlign.LEFT ? CELL_PAD_L : HEAD_PAD_H; + double padR = align == TextAlign.LEFT ? CELL_PAD_R : HEAD_PAD_H; + cell.padding(new DocumentInsets(HEAD_PAD_V, padR, HEAD_PAD_V, padL)); + cell.accentBottom(HAIRLINE, TABLE_RULE); + cell.addParagraph(p -> p + .name("LineItemHeading") + .text(caption) + .textStyle(bold(TABLE_HEAD_SIZE, INK)) + .align(align)); + return DocumentTableCell.node(cell.build()).withStyle(DocumentTableStyle.builder() + .stroke(NO_RULE) + .padding(DocumentInsets.zero()) + .build()); + } + + private static DocumentTableCell dataCell(String value, DocumentTableTextAnchor anchor) { + return DocumentTableCell.text(value).withStyle(DocumentTableStyle.builder() + .stroke(NO_RULE) + .padding(new DocumentInsets(CELL_PAD_V, CELL_PAD_R, CELL_PAD_V, CELL_PAD_L)) + .textStyle(plain(TABLE_BODY_SIZE, INK)) + .textAnchor(anchor) + .build()); + } + + /** The hairline under a row: a filled cell across every column, one rule thick. */ + private static DocumentTableCell ruleRow() { + return DocumentTableCell + .node(new SpacerBuilder() + .name("LineItemRule") + .size(px(1), TABLE_RULE) + .build()) + .withStyle(DocumentTableStyle.builder() + .stroke(NO_RULE) + .padding(DocumentInsets.zero()) + .fillColor(HAIRLINE) + .build()) + .colSpan(COLUMN_PX.length); + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/SubscriptionMasthead.java b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/SubscriptionMasthead.java new file mode 100644 index 000000000..7c9d7fc90 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/SubscriptionMasthead.java @@ -0,0 +1,234 @@ +package com.demcha.compose.document.templates.invoice.presets; + +import com.demcha.compose.document.dsl.ImageBuilder; +import com.demcha.compose.document.dsl.PageFlowBuilder; +import com.demcha.compose.document.dsl.SectionBuilder; +import com.demcha.compose.document.node.DocumentLinkOptions; +import com.demcha.compose.document.node.RowVerticalAlign; +import com.demcha.compose.document.node.TextAlign; +import com.demcha.compose.document.style.DocumentColor; +import com.demcha.compose.document.style.DocumentInsets; +import com.demcha.compose.document.style.DocumentRowColumn; +import com.demcha.compose.document.templates.core.identity.ContactUri; +import com.demcha.compose.document.templates.data.invoice.InvoiceBrand; +import com.demcha.compose.document.templates.data.invoice.InvoiceContactBlock; +import com.demcha.compose.document.templates.data.invoice.InvoiceMasthead; + +import java.util.List; + +import static com.demcha.compose.document.templates.invoice.presets.SubscriptionStyles.BODY_SIZE; +import static com.demcha.compose.document.templates.invoice.presets.SubscriptionStyles.CONTACT_LABEL_W; +import static com.demcha.compose.document.templates.invoice.presets.SubscriptionStyles.CONTENT_PAD; +import static com.demcha.compose.document.templates.invoice.presets.SubscriptionStyles.GAP_MASTHEAD_TO_IDENTITY; +import static com.demcha.compose.document.templates.invoice.presets.SubscriptionStyles.HAIRLINE; +import static com.demcha.compose.document.templates.invoice.presets.SubscriptionStyles.IDENTITY_LEFT_RATIO; +import static com.demcha.compose.document.templates.invoice.presets.SubscriptionStyles.INK; +import static com.demcha.compose.document.templates.invoice.presets.SubscriptionStyles.MARK_GUTTER; +import static com.demcha.compose.document.templates.invoice.presets.SubscriptionStyles.MARK_TOP; +import static com.demcha.compose.document.templates.invoice.presets.SubscriptionStyles.MARK_W; +import static com.demcha.compose.document.templates.invoice.presets.SubscriptionStyles.META_BAR_H; +import static com.demcha.compose.document.templates.invoice.presets.SubscriptionStyles.META_BAR_W; +import static com.demcha.compose.document.templates.invoice.presets.SubscriptionStyles.META_LABEL_INDENT; +import static com.demcha.compose.document.templates.invoice.presets.SubscriptionStyles.META_LABEL_SIZE; +import static com.demcha.compose.document.templates.invoice.presets.SubscriptionStyles.META_RIGHT_INSET; +import static com.demcha.compose.document.templates.invoice.presets.SubscriptionStyles.META_ROW_GAP; +import static com.demcha.compose.document.templates.invoice.presets.SubscriptionStyles.META_RULE_GAP; +import static com.demcha.compose.document.templates.invoice.presets.SubscriptionStyles.META_RULE_INDENT; +import static com.demcha.compose.document.templates.invoice.presets.SubscriptionStyles.META_RULE_THICK; +import static com.demcha.compose.document.templates.invoice.presets.SubscriptionStyles.META_RULE_W; +import static com.demcha.compose.document.templates.invoice.presets.SubscriptionStyles.META_VALUE_SIZE; +import static com.demcha.compose.document.templates.invoice.presets.SubscriptionStyles.PARTY_LINE_PITCH; +import static com.demcha.compose.document.templates.invoice.presets.SubscriptionStyles.SUPPLIER_NAME_SIZE; +import static com.demcha.compose.document.templates.invoice.presets.SubscriptionStyles.SUPPLIER_TOP_OFFSET; +import static com.demcha.compose.document.templates.invoice.presets.SubscriptionStyles.TITLE_GREY; +import static com.demcha.compose.document.templates.invoice.presets.SubscriptionStyles.TITLE_SIZE; +import static com.demcha.compose.document.templates.invoice.presets.SubscriptionStyles.WORDMARK_SIZE; +import static com.demcha.compose.document.templates.invoice.presets.SubscriptionStyles.WORDMARK_TOP; +import static com.demcha.compose.document.templates.invoice.presets.SubscriptionStyles.baselineGap; +import static com.demcha.compose.document.templates.invoice.presets.SubscriptionStyles.bold; +import static com.demcha.compose.document.templates.invoice.presets.SubscriptionStyles.cycle; +import static com.demcha.compose.document.templates.invoice.presets.SubscriptionStyles.plain; +import static com.demcha.compose.document.templates.invoice.presets.SubscriptionStyles.px; +import static com.demcha.compose.document.templates.invoice.presets.SubscriptionWidgets.layeredRow; + +/** + * The head of the sheet: the lockup against the title, then the supplier's own + * details against a metadata panel whose rows each open with a coloured bar. + */ +final class SubscriptionMasthead { + + private SubscriptionMasthead() { + } + + /** + * Mark and wordmark on the left, the document's name pushed to the right + * edge. + * + *

    Three columns rather than a brand block beside a title block: a row + * cannot nest in a row cell, and the lockup needs no grouping node of its own + * to sit on the same line as the title. The mark is the caller's — the + * templates artifact carries none — and the wordmark is the brand's name.

    + */ + static void renderBrandHeader(PageFlowBuilder page, InvoiceBrand brand, + InvoiceMasthead masthead) { + page.addRow("Masthead", row -> { + row.padding(CONTENT_PAD); + row.spacing(0); + row.verticalAlign(RowVerticalAlign.TOP); + row.columns(DocumentRowColumn.fixed(MARK_W + MARK_GUTTER), + DocumentRowColumn.auto(), + DocumentRowColumn.weight(1)); + row.addSection("BrandMark", cell -> { + cell.spacing(0); + if (brand.logo() != null) { + cell.add(new ImageBuilder() + .name("BrandLogo") + .source(brand.logo()) + .width(MARK_W) + .margin(new DocumentInsets(MARK_TOP, 0, 0, 0)) + .build()); + } + }); + row.addParagraph(p -> p + .name("BrandWordmark") + .text(brand.name()) + .textStyle(plain(WORDMARK_SIZE, TITLE_GREY)) + .margin(new DocumentInsets(WORDMARK_TOP, 0, 0, 0))); + row.addParagraph(p -> p + .name("InvoiceTitle") + .text(masthead.title()) + .textStyle(bold(TITLE_SIZE, TITLE_GREY)) + .align(TextAlign.RIGHT)); + }); + } + + /** The supplier's details against the metadata panel. */ + static void renderIdentityBand(PageFlowBuilder page, InvoiceContactBlock supplier, + InvoiceMasthead masthead) { + page.addRow("IdentityBand", row -> { + row.padding(CONTENT_PAD); + row.margin(new DocumentInsets(GAP_MASTHEAD_TO_IDENTITY, 0, 0, 0)); + row.spacing(0); + row.weights(IDENTITY_LEFT_RATIO, 1 - IDENTITY_LEFT_RATIO); + row.addSection("SupplierIdentity", cell -> renderSupplier(cell, supplier)); + row.addSection("MetaPanel", cell -> renderMetaPanel(cell, masthead)); + }); + } + + private static void renderSupplier(SectionBuilder cell, InvoiceContactBlock supplier) { + cell.spacing(0); + // The panel's first accent bar opens the band; the name's cap sits a + // little lower than the bar's top, and that offset is the supplier's. + cell.margin(new DocumentInsets(SUPPLIER_TOP_OFFSET, 0, 0, 0)); + cell.addParagraph(p -> p + .name("SupplierName") + .text(supplier.legalName()) + .textStyle(bold(SUPPLIER_NAME_SIZE, INK))); + + cell.addSection("SupplierAddress", block -> { + block.margin(new DocumentInsets( + baselineGap(30, SUPPLIER_NAME_SIZE, BODY_SIZE), 0, 0, 0)); + block.spacing(PARTY_LINE_PITCH - 1.2 * BODY_SIZE); + List lines = supplier.addressLines(); + for (int i = 0; i < lines.size(); i++) { + String line = lines.get(i); + int index = i; + block.addParagraph(p -> p + .name("SupplierAddress_" + index) + .text(line) + .textStyle(plain(BODY_SIZE, INK))); + } + }); + + cell.addSection("SupplierContacts", block -> { + block.margin(new DocumentInsets(baselineGap(48.9, BODY_SIZE, BODY_SIZE), 0, 0, 0)); + block.spacing(px(30) - 1.2 * BODY_SIZE); + contact(block, "Email", "E", supplier.email(), + ContactUri.mailLink(supplier.email())); + contact(block, "Website", "W", supplier.website(), + ContactUri.webLink(supplier.website())); + contact(block, "Registration", supplier.taxRegistrationLabel(), + supplier.taxRegistrationNumber(), null); + }); + } + + /** + * One contact channel, over a fixed label column so every value starts on + * the same axis. + * + *

    The design abbreviates its channel labels to single letters; those are + * the preset's, because which letter opens a channel is a property of the + * design, while the registration's label is the document's — it differs by + * jurisdiction.

    + */ + private static void contact(SectionBuilder block, String name, String label, + String value, DocumentLinkOptions link) { + if (value.isBlank() || label.isBlank()) { + return; + } + layeredRow(block, "Contact" + name, line -> { + line.spacing(0); + line.columns(DocumentRowColumn.fixed(CONTACT_LABEL_W), DocumentRowColumn.weight(1)); + line.addParagraph(p -> p + .name("Contact" + name + "Label") + .text(label) + .textStyle(bold(BODY_SIZE, INK))); + line.addParagraph(p -> { + p.name("Contact" + name + "Value"); + if (link == null) { + p.inlineText(value, plain(BODY_SIZE, INK)); + } else { + p.inlineText(value, plain(BODY_SIZE, INK), link); + } + }); + }); + } + + /** + * The metadata panel: each row opens with a bar from the cycle and closes + * with a hairline, except the last, which the design leaves open. + */ + private static void renderMetaPanel(SectionBuilder cell, InvoiceMasthead masthead) { + cell.spacing(META_ROW_GAP); + List entries = masthead.entries(); + for (int i = 0; i < entries.size(); i++) { + InvoiceMasthead.Entry entry = entries.get(i); + DocumentColor accent = cycle(i); + boolean last = i == entries.size() - 1; + int index = i; + cell.addSection("MetaRow_" + index, block -> { + block.spacing(0); + layeredRow(block, "MetaLine_" + index, line -> { + line.spacing(0); + line.padding(new DocumentInsets(0, META_RIGHT_INSET, 0, 0)); + line.verticalAlign(RowVerticalAlign.CENTER); + line.columns(DocumentRowColumn.fixed(META_BAR_W), + DocumentRowColumn.weight(1), + DocumentRowColumn.weight(1)); + line.addShape(bar -> bar + .name("MetaAccent_" + index) + .size(META_BAR_W, META_BAR_H) + .fillColor(accent)); + line.addParagraph(p -> p + .name("MetaLabel_" + index) + .text(entry.label()) + .textStyle(bold(META_LABEL_SIZE, INK)) + .padding(new DocumentInsets(0, 0, 0, META_LABEL_INDENT))); + line.addParagraph(p -> p + .name("MetaValue_" + index) + .text(entry.value()) + .textStyle(plain(META_VALUE_SIZE, INK)) + .align(TextAlign.RIGHT)); + }); + if (!last) { + block.addLine(rule -> rule + .name("MetaSeparator_" + index) + .horizontal(META_RULE_W) + .thickness(META_RULE_THICK) + .color(HAIRLINE) + .margin(new DocumentInsets(META_RULE_GAP, 0, 0, META_RULE_INDENT))); + } + }); + } + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/SubscriptionParties.java b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/SubscriptionParties.java new file mode 100644 index 000000000..2f9567d39 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/SubscriptionParties.java @@ -0,0 +1,106 @@ +package com.demcha.compose.document.templates.invoice.presets; + +import com.demcha.compose.document.dsl.PageFlowBuilder; +import com.demcha.compose.document.dsl.SectionBuilder; +import com.demcha.compose.document.style.DocumentColor; +import com.demcha.compose.document.style.DocumentInsets; +import com.demcha.compose.document.templates.data.invoice.InvoiceRecipient; + +import java.util.ArrayList; +import java.util.List; + +import static com.demcha.compose.document.templates.invoice.presets.SubscriptionStyles.BODY_SIZE; +import static com.demcha.compose.document.templates.invoice.presets.SubscriptionStyles.CONTENT_PAD; +import static com.demcha.compose.document.templates.invoice.presets.SubscriptionStyles.GAP_IDENTITY_TO_PARTIES; +import static com.demcha.compose.document.templates.invoice.presets.SubscriptionStyles.HEADING_SIZE; +import static com.demcha.compose.document.templates.invoice.presets.SubscriptionStyles.INK; +import static com.demcha.compose.document.templates.invoice.presets.SubscriptionStyles.PARTY_BODY_GAP; +import static com.demcha.compose.document.templates.invoice.presets.SubscriptionStyles.PARTY_LINE_PITCH; +import static com.demcha.compose.document.templates.invoice.presets.SubscriptionStyles.PARTY_SPLIT; +import static com.demcha.compose.document.templates.invoice.presets.SubscriptionStyles.cycle; +import static com.demcha.compose.document.templates.invoice.presets.SubscriptionStyles.plain; +import static com.demcha.compose.document.templates.invoice.presets.SubscriptionWidgets.headingPlaque; + +/** + * The addressed parties, each under a heading underlined in the next colour of + * the cycle. + */ +final class SubscriptionParties { + + private SubscriptionParties() { + } + + /** + * The parties row. + * + *

    The split is the design's measured one when both parties are set. With + * one party there is nothing to split against, so the block takes the row.

    + * + * @param page the page flow + * @param billTo the billed party + * @param shipTo the shipped-to party, or {@code null} when the invoice ships + * nowhere + */ + static void render(PageFlowBuilder page, InvoiceRecipient billTo, InvoiceRecipient shipTo) { + List parties = new ArrayList<>(); + parties.add(billTo); + if (shipTo != null) { + parties.add(shipTo); + } + page.addRow("Parties", row -> { + row.padding(CONTENT_PAD); + row.margin(new DocumentInsets(GAP_IDENTITY_TO_PARTIES, 0, 0, 0)); + row.spacing(0); + if (parties.size() == 2) { + row.weights(PARTY_SPLIT, 1 - PARTY_SPLIT); + } else { + row.evenWeights(); + } + for (int i = 0; i < parties.size(); i++) { + InvoiceRecipient party = parties.get(i); + DocumentColor accent = cycle(i); + int index = i; + row.addSection("Party_" + index, cell -> renderParty(cell, index, party, accent)); + } + }); + } + + private static void renderParty(SectionBuilder cell, int index, InvoiceRecipient party, + DocumentColor accent) { + cell.spacing(0); + headingPlaque(cell, "PartyHeading_" + index, party.heading(), HEADING_SIZE, accent); + cell.addSection("PartyAddress_" + index, block -> { + block.margin(new DocumentInsets(PARTY_BODY_GAP, 0, 0, 0)); + block.spacing(PARTY_LINE_PITCH - 1.2 * BODY_SIZE); + // The attention line sits above the address, which is what the + // contract says it is and what this design draws. + if (!party.name().isBlank()) { + block.addParagraph(p -> p + .name("PartyName_" + index) + .text(party.name()) + .textStyle(plain(BODY_SIZE, INK))); + } + if (!party.subline().isBlank()) { + block.addParagraph(p -> p + .name("PartySubline_" + index) + .text(party.subline()) + .textStyle(plain(BODY_SIZE, INK))); + } + List address = party.addressLines(); + for (int i = 0; i < address.size(); i++) { + String line = address.get(i); + int lineIndex = i; + block.addParagraph(p -> p + .name("PartyAddress_" + index + "_" + lineIndex) + .text(line) + .textStyle(plain(BODY_SIZE, INK))); + } + if (!party.registrationNumber().isBlank()) { + block.addParagraph(p -> p + .name("PartyRegistration_" + index) + .text(party.registrationLabel() + " " + party.registrationNumber()) + .textStyle(plain(BODY_SIZE, INK))); + } + }); + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/SubscriptionSettlement.java b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/SubscriptionSettlement.java new file mode 100644 index 000000000..9febed945 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/SubscriptionSettlement.java @@ -0,0 +1,150 @@ +package com.demcha.compose.document.templates.invoice.presets; + +import com.demcha.compose.document.dsl.PageFlowBuilder; +import com.demcha.compose.document.dsl.SectionBuilder; +import com.demcha.compose.document.dsl.SpacerBuilder; +import com.demcha.compose.document.style.DocumentInsets; +import com.demcha.compose.document.table.DocumentTableCell; +import com.demcha.compose.document.table.DocumentTableColumn; +import com.demcha.compose.document.table.DocumentTableStyle; +import com.demcha.compose.document.table.DocumentTableTextAnchor; +import com.demcha.compose.document.templates.data.invoice.InvoiceNotesBlock; +import com.demcha.compose.document.templates.data.invoice.InvoiceTotalsBlock; + +import java.util.List; + +import static com.demcha.compose.document.templates.invoice.presets.SubscriptionStyles.ACCENT; +import static com.demcha.compose.document.templates.invoice.presets.SubscriptionStyles.CONTENT_PAD; +import static com.demcha.compose.document.templates.invoice.presets.SubscriptionStyles.CONTENT_W; +import static com.demcha.compose.document.templates.invoice.presets.SubscriptionStyles.CYCLE_AMBER; +import static com.demcha.compose.document.templates.invoice.presets.SubscriptionStyles.GAP_TABLE_TO_TOTALS; +import static com.demcha.compose.document.templates.invoice.presets.SubscriptionStyles.HEADING_SIZE; +import static com.demcha.compose.document.templates.invoice.presets.SubscriptionStyles.INK; +import static com.demcha.compose.document.templates.invoice.presets.SubscriptionStyles.NOTES_BODY_GAP; +import static com.demcha.compose.document.templates.invoice.presets.SubscriptionStyles.NOTES_BODY_SIZE; +import static com.demcha.compose.document.templates.invoice.presets.SubscriptionStyles.NOTES_LINE_PITCH; +import static com.demcha.compose.document.templates.invoice.presets.SubscriptionStyles.NOTES_TEXT_W; +import static com.demcha.compose.document.templates.invoice.presets.SubscriptionStyles.NOTES_TOP_OFFSET; +import static com.demcha.compose.document.templates.invoice.presets.SubscriptionStyles.NO_RULE; +import static com.demcha.compose.document.templates.invoice.presets.SubscriptionStyles.TOTALS_DUE_PAD_V; +import static com.demcha.compose.document.templates.invoice.presets.SubscriptionStyles.TOTALS_LABEL_SHARE; +import static com.demcha.compose.document.templates.invoice.presets.SubscriptionStyles.TOTALS_LEFT_RATIO; +import static com.demcha.compose.document.templates.invoice.presets.SubscriptionStyles.TOTALS_PAD_H; +import static com.demcha.compose.document.templates.invoice.presets.SubscriptionStyles.TOTALS_PAD_V; +import static com.demcha.compose.document.templates.invoice.presets.SubscriptionStyles.TOTALS_ROW_SIZE; +import static com.demcha.compose.document.templates.invoice.presets.SubscriptionStyles.TOTALS_RULE; +import static com.demcha.compose.document.templates.invoice.presets.SubscriptionStyles.TOTAL_DUE_LABEL_SIZE; +import static com.demcha.compose.document.templates.invoice.presets.SubscriptionStyles.TOTAL_DUE_VALUE_SIZE; +import static com.demcha.compose.document.templates.invoice.presets.SubscriptionStyles.bold; +import static com.demcha.compose.document.templates.invoice.presets.SubscriptionStyles.leading; +import static com.demcha.compose.document.templates.invoice.presets.SubscriptionStyles.plain; +import static com.demcha.compose.document.templates.invoice.presets.SubscriptionStyles.px; +import static com.demcha.compose.document.templates.invoice.presets.SubscriptionWidgets.headingPlaque; + +/** + * The band under the table: the notes on the left, the totals on the right. + */ +final class SubscriptionSettlement { + + private SubscriptionSettlement() { + } + + /** + * The notes-and-totals row. + * + *

    The two are not top-flush: the design starts the notes heading a + * heading-height below the first totals line beside it, which is what makes + * the block read as an aside to the sum rather than as a second column of + * it.

    + * + * @param page the page flow + * @param notes the note beside the totals + * @param totals the summed rows and the grand total + * @param currencyCode the code every money figure carries as a symbol + */ + static void render(PageFlowBuilder page, InvoiceNotesBlock notes, + InvoiceTotalsBlock totals, String currencyCode) { + page.addRow("NotesAndTotals", row -> { + row.padding(CONTENT_PAD); + row.margin(new DocumentInsets(GAP_TABLE_TO_TOTALS, 0, 0, 0)); + row.spacing(0); + row.weights(TOTALS_LEFT_RATIO, 1 - TOTALS_LEFT_RATIO); + row.addSection("Notes", cell -> renderNotes(cell, notes)); + row.addSection("Totals", cell -> renderTotals(cell, totals, currencyCode)); + }); + } + + private static void renderNotes(SectionBuilder cell, InvoiceNotesBlock notes) { + cell.spacing(0); + if (notes.heading().isBlank() && notes.paragraphs().isEmpty()) { + return; + } + cell.margin(new DocumentInsets(NOTES_TOP_OFFSET, 0, 0, 0)); + // The paragraph wraps at its container, and the design wraps it well + // before the cell ends, so the width belongs to the cell. + cell.padding(new DocumentInsets( + 0, CONTENT_W * TOTALS_LEFT_RATIO - NOTES_TEXT_W, 0, 0)); + if (!notes.heading().isBlank()) { + headingPlaque(cell, "NotesHeading", notes.heading(), HEADING_SIZE, CYCLE_AMBER); + } + List paragraphs = notes.paragraphs(); + for (int i = 0; i < paragraphs.size(); i++) { + String prose = paragraphs.get(i); + int index = i; + cell.addParagraph(p -> p + .name("NotesBody_" + index) + .text(prose) + .textStyle(plain(NOTES_BODY_SIZE, INK)) + .lineSpacing(leading(NOTES_LINE_PITCH, NOTES_BODY_SIZE)) + .margin(new DocumentInsets(NOTES_BODY_GAP, 0, 0, 0))); + } + } + + private static void renderTotals(SectionBuilder cell, InvoiceTotalsBlock totals, + String currencyCode) { + cell.spacing(0); + cell.keepTogether(); + double width = CONTENT_W * (1 - TOTALS_LEFT_RATIO); + cell.addTable(table -> { + table.name("TotalsTable"); + table.defaultCellStyle(DocumentTableStyle.builder().stroke(NO_RULE).build()); + table.columns(DocumentTableColumn.fixed(width * TOTALS_LABEL_SHARE), + DocumentTableColumn.fixed(width * (1 - TOTALS_LABEL_SHARE))); + for (InvoiceTotalsBlock.Row row : totals.rows()) { + table.rowCells( + totalsCell(row.label(), TOTALS_ROW_SIZE, + DocumentTableTextAnchor.CENTER_LEFT, TOTALS_PAD_V), + totalsCell(SubscriptionText.money(currencyCode, row.amount()), + TOTALS_ROW_SIZE, DocumentTableTextAnchor.CENTER_RIGHT, + TOTALS_PAD_V)); + } + table.rowCells(DocumentTableCell + .node(new SpacerBuilder() + .name("TotalsRule") + .size(px(1), TOTALS_RULE) + .build()) + .withStyle(DocumentTableStyle.builder() + .stroke(NO_RULE) + .padding(DocumentInsets.zero()) + .fillColor(ACCENT) + .build()) + .colSpan(2)); + table.rowCells( + totalsCell(totals.totalLabel(), TOTAL_DUE_LABEL_SIZE, + DocumentTableTextAnchor.CENTER_LEFT, TOTALS_DUE_PAD_V), + totalsCell(SubscriptionText.money(currencyCode, totals.totalAmount()), + TOTAL_DUE_VALUE_SIZE, DocumentTableTextAnchor.CENTER_RIGHT, + TOTALS_DUE_PAD_V)); + }); + } + + private static DocumentTableCell totalsCell(String value, double size, + DocumentTableTextAnchor anchor, double padV) { + return DocumentTableCell.text(value).withStyle(DocumentTableStyle.builder() + .stroke(NO_RULE) + .padding(new DocumentInsets(padV, TOTALS_PAD_H, padV, TOTALS_PAD_H)) + .textStyle(bold(size, INK)) + .textAnchor(anchor) + .build()); + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/SubscriptionStyles.java b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/SubscriptionStyles.java new file mode 100644 index 000000000..a8ff5dfe8 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/SubscriptionStyles.java @@ -0,0 +1,377 @@ +package com.demcha.compose.document.templates.invoice.presets; + +import com.demcha.compose.document.api.DocumentPageSize; +import com.demcha.compose.document.style.DocumentColor; +import com.demcha.compose.document.style.DocumentInsets; +import com.demcha.compose.document.style.DocumentStroke; +import com.demcha.compose.document.style.DocumentTextDecoration; +import com.demcha.compose.document.style.DocumentTextStyle; +import com.demcha.compose.font.FontName; + +import java.util.List; + +/** + * Every measurement, colour and type size of the Subscription invoice. + * + *

    Geometry is relational: every dimension comes from three base numbers — + * the page, {@link #px(double)} (the design's own pixel scale in points), and + * {@link #CONTENT_W}. A measurement taken off the design is written as + * {@code px(n)} so it stays readable as the thing that was measured while still + * rescaling with the page, and every type size is {@link #sizeForCap(double)} so + * it stays readable as the cap height it reproduces.

    + */ +final class SubscriptionStyles { + + private SubscriptionStyles() { + } + + // ------------------------------------------------------------------ + // The page + // ------------------------------------------------------------------ + + static final DocumentPageSize PAGE = DocumentPageSize.A4; + + static final double PAGE_W = PAGE.width(); + + /** The design image's pixel width — the scale every measurement below is in. */ + private static final double DESIGN_PX_WIDTH = 1055.0; + + /** Points per design pixel. The one conversion the whole preset goes through. */ + private static final double PX = PAGE_W / DESIGN_PX_WIDTH; + + static double px(double designPixels) { + return designPixels * PX; + } + + /** Measured: ink runs x=48..1006 on a 1055 px page, symmetric within a pixel. */ + static final double MARGIN_X = px(48); + + /** + * The flow's top. + * + *

    The mark's first ink row is at 46, but a paragraph's box starts above + * its cap band and the title is the tallest thing in the masthead. The flow + * therefore opens at the title's box top — 14 px above the mark — + * which is what puts the mark back at 46 once the masthead row aligns its + * cells against each other.

    + */ + static final double MARGIN_T = px(31); + + /** + * The room the body is held off the paper's bottom edge — roughly the + * padding the design already leaves below the last line of its closing band, + * so the band's fill still runs to the edge and only the text moves up. + */ + static final double FOOTER_RESERVE = px(20); + + /** + * The chrome band the enumeration is seated in, deliberately taller than the + * reserve. + * + *

    The two answer different questions. The reserve is how much page the + * body gives up, and it decides whether the closing band still fits + * on one page; the band height is where inside that foot the number + * sits. Seating it in a taller band lifts it off the paper edge + * without taking another point from the body.

    + */ + static final double FOOTER_H = px(24); + + static final double CONTENT_W = PAGE_W - 2 * MARGIN_X; + + /** The horizontal inset every block carries except the two that reach the paper edges. */ + static final DocumentInsets CONTENT_PAD = new DocumentInsets(0, MARGIN_X, 0, MARGIN_X); + + // ------------------------------------------------------------------ + // Colour + // ------------------------------------------------------------------ + + static final DocumentColor INK = DocumentColor.rgb(0, 4, 14); + static final DocumentColor TITLE_GREY = DocumentColor.rgb(71, 71, 70); + static final DocumentColor ACCENT = DocumentColor.rgb(0, 104, 218); + static final DocumentColor HAIRLINE = DocumentColor.rgb(234, 234, 235); + static final DocumentColor BAND_FILL = DocumentColor.rgb(245, 246, 247); + static final DocumentColor CHROME_GREY = DocumentColor.rgb(154, 160, 166); + + private static final DocumentColor CYCLE_RED = DocumentColor.rgb(242, 69, 34); + private static final DocumentColor CYCLE_GREEN = DocumentColor.rgb(114, 176, 22); + private static final DocumentColor CYCLE_BLUE = DocumentColor.rgb(20, 118, 212); + static final DocumentColor CYCLE_AMBER = DocumentColor.rgb(255, 180, 0); + + /** + * The cycle the metadata bars, the party underlines and the closing strip all + * draw from. Position, not meaning: the fourth row is amber because it is + * fourth, and nothing in the document ever names a colour. + */ + static final List CYCLE = + List.of(CYCLE_RED, CYCLE_GREEN, CYCLE_BLUE, CYCLE_AMBER); + + /** The colour at a position in the cycle. */ + static DocumentColor cycle(int index) { + return CYCLE.get(Math.floorMod(index, CYCLE.size())); + } + + /** + * A cell's stroke draws all four edges and there is no per-edge control, so + * every cell in this document carries none. The rules the design does show + * are drawn as accents and as rule rows instead. + */ + static final DocumentStroke NO_RULE = DocumentStroke.of(DocumentColor.rgba(255, 255, 255, 0), 0); + + // ------------------------------------------------------------------ + // Type + // ------------------------------------------------------------------ + + /** + * The design is set in a humanist grotesque that is not bundled. Lato is the + * usual substitute and matches the design's mixed-case widths to within 4%, + * but its uppercase is about 20% wider per unit of cap height, which shows up + * immediately as headings and their coloured rules overshooting. Measured + * across twenty strings, Fira Sans Condensed is the bundled family that + * reproduces both: all-caps within 5% and mixed-case within 1% at the + * measured cap heights. + * + *

    The templates artifact carries no fonts — register the family on the + * session, or the engine substitutes and the cap arithmetic below no longer + * describes the type sitting in it.

    + */ + static final FontName FACE = FontName.FIRA_SANS_CONDENSED; + + /** Fira Sans Condensed's cap heights, read off the bundled TTFs, on a 1000 unit em. */ + private static final double CAP_REGULAR = 0.688; + private static final double CAP_BOLD = 0.693; + + /** Its ascender and descender, same source. They sum to the 1.2 em line box. */ + private static final double ASCENT = 0.935; + private static final double DESCENT = 0.265; + + static double sizeForCap(double capPx) { + return px(capPx) / CAP_REGULAR; + } + + static double sizeForCapBold(double capPx) { + return px(capPx) / CAP_BOLD; + } + + /** + * The margin that puts the next block's first baseline {@code pitchPx} below + * the previous block's last baseline. Baselines are what the design can be + * measured on unambiguously; box edges are not. + */ + static double baselineGap(double pitchPx, double sizeAbove, double sizeBelow) { + return px(pitchPx) - DESCENT * sizeAbove - ASCENT * sizeBelow; + } + + /** + * Line spacing is points added between lines and the line box is + * 1.2× the type size, so a measured line pitch converts as + * {@code pitch - 1.2 * size}, never as a ratio. + */ + static double leading(double pitchPx, double size) { + return px(pitchPx) - 1.2 * size; + } + + static final double TITLE_SIZE = sizeForCapBold(52); + static final double WORDMARK_SIZE = sizeForCap(32); + static final double SUPPLIER_NAME_SIZE = sizeForCapBold(15); + static final double BODY_SIZE = sizeForCap(13); + static final double META_LABEL_SIZE = sizeForCapBold(13.5); + static final double META_VALUE_SIZE = sizeForCap(13.5); + static final double HEADING_SIZE = sizeForCapBold(15.5); + static final double TABLE_HEAD_SIZE = sizeForCapBold(13.5); + static final double TABLE_BODY_SIZE = sizeForCap(12.8); + static final double TOTALS_ROW_SIZE = sizeForCapBold(13); + static final double TOTAL_DUE_LABEL_SIZE = sizeForCapBold(16); + static final double TOTAL_DUE_VALUE_SIZE = sizeForCapBold(20.5); + static final double NOTES_BODY_SIZE = sizeForCap(13.5); + static final double PAY_LABEL_SIZE = sizeForCapBold(13); + static final double PAY_VALUE_SIZE = sizeForCap(13); + static final double PAY_NOTE_SIZE = sizeForCap(12.8); + static final double CLOSE_HEAD_SIZE = sizeForCapBold(12.5); + static final double CLOSE_BODY_SIZE = sizeForCap(12); + static final double CHROME_SIZE = 7.0; + + static DocumentTextStyle style(double size, DocumentColor colour, + DocumentTextDecoration decoration) { + return DocumentTextStyle.builder() + .fontName(FACE) + .size(size) + .color(colour) + .decoration(decoration) + .build(); + } + + static DocumentTextStyle plain(double size, DocumentColor colour) { + return style(size, colour, DocumentTextDecoration.DEFAULT); + } + + static DocumentTextStyle bold(double size, DocumentColor colour) { + return style(size, colour, DocumentTextDecoration.BOLD); + } + + // ------------------------------------------------------------------ + // Masthead + // ------------------------------------------------------------------ + + /** The mark is 54 px square and the wordmark's ink opens 16 px to its right. */ + static final double MARK_W = px(54); + static final double MARK_GUTTER = px(16); + + /** + * The mark's top against the masthead row's top. The design lines the title's + * cap band up with the mark rather than the title's line box, and + * the box is half as tall again as the cap — so the row is top-aligned and + * the mark carries the difference instead of the row centring the two. + */ + static final double MARK_TOP = px(15); + + /** The wordmark's cap sits below the mark's top by this much. */ + static final double WORDMARK_TOP = px(17.5); + + // ------------------------------------------------------------------ + // Identity band + // ------------------------------------------------------------------ + + /** The metadata panel's left edge, as a share of the content box. */ + static final double IDENTITY_LEFT_RATIO = (633.0 - 48.0) / 959.0; + + static final double SUPPLIER_TOP_OFFSET = px(2.7); + static final double META_BAR_W = px(4); + static final double META_BAR_H = px(39); + static final double META_LABEL_INDENT = px(17); + static final double META_RIGHT_INSET = px(15); + static final double META_RULE_W = px(368); + static final double META_RULE_INDENT = px(6); + static final double META_RULE_THICK = px(2); + + /** Bar bottom 179 → rule top 182, and rule bottom 184 → next bar top 189. */ + static final double META_RULE_GAP = px(3); + static final double META_ROW_GAP = px(5); + + /** + * The contact label gutter. The design puts two of the values at x=116 and + * the third at 132, which is a minimum width plus a gap; one fixed column is + * the honest reading of it, and it has to clear the longest label. + */ + static final double CONTACT_LABEL_W = px(70); + + // ------------------------------------------------------------------ + // Parties and headings + // ------------------------------------------------------------------ + + /** The second party column opens at x=539 of the content box. */ + static final double PARTY_SPLIT = (539.0 - 48.0) / 959.0; + + /** Heading rule bottom 449 → first address baseline 487. */ + static final double PARTY_BODY_GAP = px(22.7); + + /** Heading baseline 434 → rule 444..448: ten points of pad, then a five-pixel rule. */ + static final double HEADING_RULE_PAD = px(7); + static final double HEADING_RULE_THICK = px(4.2); + + static final double PARTY_LINE_PITCH = px(27); + + // ------------------------------------------------------------------ + // The line-item table + // ------------------------------------------------------------------ + + /** + * The six columns, solved from the ink: the number and description are left + * aligned, the money headings centred, the amounts right aligned. The widths + * sum to the content width by construction. + */ + static final double[] COLUMN_PX = {67, 413, 96, 116, 151, 116}; + static final double COLUMN_TOTAL_PX = 959.0; + + static final double CELL_PAD_L = px(17); + static final double CELL_PAD_R = px(25); + + /** The data row band is 47.5 px, of which the rule row below it takes one. */ + static final double CELL_PAD_V = px(12.2); + static final double HEAD_PAD_V = px(11.6); + + /** + * A centred caption needs room, not alignment: the widest one is 94 px inside + * a 116 px column, which leaves it seven px a side. A left-aligned caption is + * positioned by its padding instead, so those cells keep the data cells' + * measured inset. + */ + static final double HEAD_PAD_H = px(7); + + static final double TABLE_RULE = px(1); + static final double TABLE_TOP_RULE = px(5); + + // ------------------------------------------------------------------ + // Totals and notes + // ------------------------------------------------------------------ + + /** The totals block opens on the unit-price boundary, so it is derived, not measured. */ + static final double TOTALS_LEFT_RATIO = + (COLUMN_PX[0] + COLUMN_PX[1] + COLUMN_PX[2]) / COLUMN_TOTAL_PX; + static final double TOTALS_LABEL_SHARE = 0.50; + static final double TOTALS_PAD_H = px(25); + static final double TOTALS_PAD_V = px(12.6); + static final double TOTALS_DUE_PAD_V = px(8.45); + static final double TOTALS_RULE = px(3); + + /** The notes paragraph wraps well before its cell ends, so the width is the cell's. */ + static final double NOTES_TEXT_W = px(400); + static final double NOTES_BODY_GAP = px(20.7); + static final double NOTES_LINE_PITCH = 27; + + /** + * What makes the notes/totals row not top-flush: the design starts the notes + * heading a heading-height below the first totals line beside it. + */ + static final double NOTES_TOP_OFFSET = px(50.5); + + // ------------------------------------------------------------------ + // Payment band, strip and closing band + // ------------------------------------------------------------------ + + /** The five payment columns, from the dividers' measured positions. */ + static final double[] PAYMENT_PX = {207.5, 178, 184, 185, 204.5}; + + static final double PAY_ICON_W = px(42); + static final double PAY_DIVIDER = px(2); + static final double PAY_COLUMNS_GAP = px(18.9); + static final double PAY_LABEL_GAP = px(15.8); + static final double PAY_VALUE_GAP = px(6.6); + + /** + * The design's outer two payment columns are not centred on the space the + * dividers give them: their content sits about ten px inboard. The middle + * three are centred to within three px, so the nudge is on the outer pair. + */ + static final double PAY_OUTER_NUDGE = px(20); + + /** The closing strip: four segments across the full paper width. */ + static final double[] STRIP_PX = {272, 255, 252, 276}; + static final double STRIP_TOTAL_PX = DESIGN_PX_WIDTH; + static final double STRIP_H = px(6); + + /** + * The band's left inset is short of the page's, because the mark's SVG + * carries its own padding: the design puts the glyph's ink at the + * content edge, so the icon box has to start left of it to land there. + */ + static final double CLOSE_PAD_L = px(42); + static final double CLOSE_TEXT_INDENT = px(6); + + /** Lifts the mark off the text block's centre line, as the design does. */ + static final double CLOSE_ICON_LIFT = px(5); + static final double CLOSE_ICON_COLUMN = px(79); + static final double CLOSE_ICON_W = px(56); + static final double CLOSE_PAD_T = px(24.8); + static final double CLOSE_PAD_B = px(2); + static final double CLOSE_LINE_PITCH = 24; + + // Inter-block gaps, each named by the two measured ink edges it reproduces. + static final double GAP_MASTHEAD_TO_IDENTITY = px(19.9); + static final double GAP_IDENTITY_TO_PARTIES = px(37.7); + static final double GAP_PARTIES_TO_TABLE = px(43.8); + static final double GAP_TABLE_TO_TOTALS = px(2.1); + static final double GAP_TABLE_TO_PAYMENT = px(36.1); + static final double GAP_PAYMENT_TO_NOTE = px(21.3); + static final double GAP_NOTE_TO_STRIP = px(32.1); + static final double GAP_STRIP_TO_BAND = px(1); +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/SubscriptionText.java b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/SubscriptionText.java new file mode 100644 index 000000000..98a27aab7 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/SubscriptionText.java @@ -0,0 +1,74 @@ +package com.demcha.compose.document.templates.invoice.presets; + +import java.math.BigDecimal; +import java.text.NumberFormat; +import java.util.Currency; +import java.util.Locale; + +/** + * How the Subscription invoice writes its figures. + * + *

    This design writes money with the currency's symbol against the + * digits rather than with a code in front or a caption above, so every figure on + * the sheet carries its own currency and none of the columns state one. The + * locale is stated rather than inherited, so the same data renders the same + * sheet on any machine.

    + */ +final class SubscriptionText { + + private SubscriptionText() { + } + + /** + * An amount behind its currency symbol. + * + * @param currencyCode the code the invoice is billed in + * @param value the amount + * @return the amount as the sheet prints it + */ + static String money(String currencyCode, BigDecimal value) { + return symbol(currencyCode) + amount(value); + } + + /** + * A count, bare, with no fraction when it has none. + * + * @param value the count + * @return the count as the sheet prints it + */ + static String quantity(BigDecimal value) { + BigDecimal stripped = value.stripTrailingZeros(); + return stripped.scale() <= 0 + ? stripped.toBigInteger().toString() + : stripped.toPlainString(); + } + + /** + * The symbol a code is written with, or the code itself when the runtime + * knows no symbol for it. + * + *

    Either way it goes hard against the digits, which is this design's own + * rule and not only a convention for the short marks: a sheet that prints + * the code there is still readable, and one that prints nothing at all names + * no currency anywhere. The columns are measured for a one-character mark + * though, so a three-letter code leaves the amount that much less room.

    + */ + private static String symbol(String currencyCode) { + if (currencyCode == null || currencyCode.isBlank()) { + return ""; + } + try { + return Currency.getInstance(currencyCode.trim()).getSymbol(Locale.ENGLISH); + } catch (IllegalArgumentException notACurrency) { + return currencyCode.trim(); + } + } + + private static String amount(BigDecimal value) { + NumberFormat format = NumberFormat.getNumberInstance(Locale.ENGLISH); + format.setGroupingUsed(true); + format.setMinimumFractionDigits(2); + format.setMaximumFractionDigits(2); + return format.format(value); + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/SubscriptionWidgets.java b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/SubscriptionWidgets.java new file mode 100644 index 000000000..170d6380d --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/SubscriptionWidgets.java @@ -0,0 +1,81 @@ +package com.demcha.compose.document.templates.invoice.presets; + +import com.demcha.compose.document.dsl.RowBuilder; +import com.demcha.compose.document.dsl.SectionBuilder; +import com.demcha.compose.document.node.DocumentNode; +import com.demcha.compose.document.node.LayerAlign; +import com.demcha.compose.document.style.DocumentColor; +import com.demcha.compose.document.style.DocumentInsets; +import com.demcha.compose.document.style.DocumentRowColumn; + +import java.util.function.Consumer; + +import static com.demcha.compose.document.templates.invoice.presets.SubscriptionStyles.HEADING_RULE_PAD; +import static com.demcha.compose.document.templates.invoice.presets.SubscriptionStyles.HEADING_RULE_THICK; +import static com.demcha.compose.document.templates.invoice.presets.SubscriptionStyles.INK; +import static com.demcha.compose.document.templates.invoice.presets.SubscriptionStyles.bold; +import static com.demcha.compose.document.templates.invoice.presets.SubscriptionStyles.px; + +/** + * The two shapes the Subscription invoice repeats: a horizontal pair inside a + * column, and the heading plaque — a caption underlined in one of the cycle's + * colours, sized to the caption rather than to the column. + */ +final class SubscriptionWidgets { + + private SubscriptionWidgets() { + } + + /** + * A row wrapped in a layer, so it can sit inside a row cell. + * + *

    A row nested in a row cell is refused by the layout compiler, and + * through a section too — it is the ancestry that matters, not the immediate + * parent. A row wrapped in a LayerStack layer lays out horizontally there.

    + * + * @param parent the section the pair belongs to + * @param name the pair's name, which its holder and layer extend + * @param spec the row itself + */ + static void layeredRow(SectionBuilder parent, String name, Consumer spec) { + SectionBuilder holder = new SectionBuilder(); + holder.name(name + "Holder"); + holder.addRow(name, spec); + DocumentNode node = holder.build(); + parent.addLayerStack(stack -> stack + .name(name + "Layer") + .layer(node, LayerAlign.TOP_LEFT, 0)); + } + + /** + * A caption with a coloured rule under it, as wide as the caption. + * + *

    The rule is an accent on a cell that hugs its text, not a line of a + * stated width: the design's rules end where their headings do, and a stated + * width would have to be re-measured for every caption a document might set. + * The spacer beside it is what stops the cell from filling the row.

    + * + * @param host the section the plaque belongs to + * @param name the plaque's name + * @param heading the caption + * @param size the caption's type size + * @param accent the rule's colour + */ + static void headingPlaque(SectionBuilder host, String name, String heading, + double size, DocumentColor accent) { + layeredRow(host, name, row -> { + row.spacing(0); + row.columns(DocumentRowColumn.auto(), DocumentRowColumn.weight(1)); + row.addSection(name + "Rule", cell -> { + cell.spacing(0); + cell.padding(new DocumentInsets(0, 0, HEADING_RULE_PAD, 0)); + cell.accentBottom(accent, HEADING_RULE_THICK); + cell.addParagraph(p -> p + .name(name + "Text") + .text(heading) + .textStyle(bold(size, INK))); + }); + row.addSpacer(px(1)); + }); + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/WorkspaceClosing.java b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/WorkspaceClosing.java new file mode 100644 index 000000000..d2411fee2 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/WorkspaceClosing.java @@ -0,0 +1,165 @@ +package com.demcha.compose.document.templates.invoice.presets; + +import com.demcha.compose.document.dsl.PageFlowBuilder; +import com.demcha.compose.document.node.DocumentLinkOptions; +import com.demcha.compose.document.node.HorizontalAlign; +import com.demcha.compose.document.node.RowVerticalAlign; +import com.demcha.compose.document.node.TextAlign; +import com.demcha.compose.document.style.DocumentInsets; +import com.demcha.compose.document.style.DocumentRowColumn; +import com.demcha.compose.document.style.DocumentTextDecoration; +import com.demcha.compose.document.style.DocumentTextStyle; +import com.demcha.compose.document.templates.core.identity.ContactUri; +import com.demcha.compose.document.templates.data.invoice.InvoiceBrand; +import com.demcha.compose.document.templates.data.invoice.InvoiceContactBlock; +import com.demcha.compose.document.templates.data.invoice.InvoiceNotesBlock; + +import java.util.List; + +import static com.demcha.compose.document.templates.invoice.presets.WorkspaceStyles.ACCENT; +import static com.demcha.compose.document.templates.invoice.presets.WorkspaceStyles.BODY; +import static com.demcha.compose.document.templates.invoice.presets.WorkspaceStyles.CLOSING_INNER_LIFT; +import static com.demcha.compose.document.templates.invoice.presets.WorkspaceStyles.CLOSING_RULE_LIFT; +import static com.demcha.compose.document.templates.invoice.presets.WorkspaceStyles.FOOTER_LINK_COL; +import static com.demcha.compose.document.templates.invoice.presets.WorkspaceStyles.FOOTER_MARK_COL; +import static com.demcha.compose.document.templates.invoice.presets.WorkspaceStyles.FOOTER_SIZE; +import static com.demcha.compose.document.templates.invoice.presets.WorkspaceStyles.HAIRLINE_STRONG; +import static com.demcha.compose.document.templates.invoice.presets.WorkspaceStyles.INK; +import static com.demcha.compose.document.templates.invoice.presets.WorkspaceStyles.MUTED; +import static com.demcha.compose.document.templates.invoice.presets.WorkspaceStyles.RULE_THIN; +import static com.demcha.compose.document.templates.invoice.presets.WorkspaceStyles.THANKS_BODY_SIZE; +import static com.demcha.compose.document.templates.invoice.presets.WorkspaceStyles.THANKS_ICON; +import static com.demcha.compose.document.templates.invoice.presets.WorkspaceStyles.THANKS_ICON_COL; +import static com.demcha.compose.document.templates.invoice.presets.WorkspaceStyles.THANKS_INDENT; +import static com.demcha.compose.document.templates.invoice.presets.WorkspaceStyles.THANKS_TITLE_SIZE; +import static com.demcha.compose.document.templates.invoice.presets.WorkspaceStyles.WORDMARK_SIZE; +import static com.demcha.compose.document.templates.invoice.presets.WorkspaceStyles.blockGap; +import static com.demcha.compose.document.templates.invoice.presets.WorkspaceStyles.gap; +import static com.demcha.compose.document.templates.invoice.presets.WorkspaceStyles.px; +import static com.demcha.compose.document.templates.invoice.presets.WorkspaceStyles.style; + +/** + * What closes the sheet: the thank-you block and the brand band under it. + * + *

    The band is body content rather than page chrome — it carries a wordmark + * and a reachable address, and a header/footer zone takes only strings. Page + * identity on a continuation page is carried by the page number instead.

    + */ +final class WorkspaceClosing { + + private WorkspaceClosing() { + } + + /** + * Draws the closing blocks. + * + * @param page the page flow + * @param notes the closing words and the address inside them + * @param brand the wordmark the band opens with + * @param supplier who is invoicing, named again under the band + */ + static void render(PageFlowBuilder page, InvoiceNotesBlock notes, InvoiceBrand brand, + InvoiceContactBlock supplier) { + WorkspaceWidgets.fullWidthRule(page, "ClosingRule", HAIRLINE_STRONG, RULE_THIN, + px(12.4) - CLOSING_RULE_LIFT, px(19.2) - CLOSING_INNER_LIFT); + renderThankYou(page, notes); + WorkspaceWidgets.fullWidthRule(page, "FooterRule", HAIRLINE_STRONG, RULE_THIN, + blockGap(22.15, THANKS_BODY_SIZE, 0) - CLOSING_INNER_LIFT, + blockGap(7.9, 0, FOOTER_SIZE)); + renderBand(page, brand, supplier); + } + + /** + * The closing words, opened by a mark in its own column so a wrapped second + * line returns to the text axis rather than to the glyph's. + * + *

    The address inside the sentence is a separate run, so it reaches the + * PDF as a real annotation rather than as coloured text.

    + */ + private static void renderThankYou(PageFlowBuilder page, InvoiceNotesBlock notes) { + List paragraphs = notes.paragraphs(); + String title = notes.heading(); + String body = paragraphs.isEmpty() ? "" : paragraphs.get(0); + page.addSection("ThankYou", block -> { + block.spacing(0) + .padding(new DocumentInsets(0, 0, 0, THANKS_INDENT)) + .keepWithNext(); + block.addRow("ThankYouRow", row -> { + row.spacing(0) + .columns(DocumentRowColumn.fixed(THANKS_ICON_COL), + DocumentRowColumn.weight(1)); + row.addSection("ThankYouIcon", glyph -> glyph + .spacing(0) + .add(WorkspaceWidgets.glyph(WorkspaceIcons.INFO, THANKS_ICON, + HorizontalAlign.LEFT))); + row.addSection("ThankYouText", text -> { + text.spacing(gap(24, THANKS_TITLE_SIZE)); + text.addParagraph(p -> p + .name("ThankYouTitle") + .text(title) + .textStyle(style(THANKS_TITLE_SIZE, INK, + DocumentTextDecoration.BOLD))); + text.addParagraph(p -> { + p.name("ThankYouBody"); + DocumentTextStyle plain = style(THANKS_BODY_SIZE, MUTED, + DocumentTextDecoration.DEFAULT); + String email = notes.contactEmail(); + if (email.isBlank() || !body.contains(email)) { + p.inlineText(body, plain); + return; + } + // The address is set apart inside the sentence, so the + // words on either side of it stay one paragraph. + int at = body.indexOf(email); + p.inlineText(body.substring(0, at), plain); + p.inlineText(email, + style(THANKS_BODY_SIZE, ACCENT, DocumentTextDecoration.DEFAULT), + new DocumentLinkOptions("mailto:" + email)); + p.inlineText(body.substring(at + email.length()), plain); + }); + }); + }); + }); + } + + /** The brand band: the wordmark, who is invoicing, and where to find them. */ + private static void renderBand(PageFlowBuilder page, InvoiceBrand brand, + InvoiceContactBlock supplier) { + String website = supplier.website(); + page.addRow("DocumentFooter", row -> { + row.spacing(0) + .verticalAlign(RowVerticalAlign.CENTER) + .columns(DocumentRowColumn.fixed(FOOTER_MARK_COL), + DocumentRowColumn.weight(1), + DocumentRowColumn.fixed(FOOTER_LINK_COL)); + row.addParagraph(p -> p + .name("FooterWordmark") + .text(brand.name()) + .textStyle(style(WORDMARK_SIZE, INK, DocumentTextDecoration.BOLD))); + row.addSection("FooterAddress", text -> { + text.spacing(gap(23, FOOTER_SIZE)); + text.addParagraph(p -> p + .name("FooterName") + .text(supplier.legalName()) + .textStyle(style(FOOTER_SIZE, BODY, DocumentTextDecoration.BOLD))); + text.addParagraph(p -> p + .name("FooterAddressLine") + .text(supplier.addressLines().isEmpty() + ? "" : String.join(", ", supplier.addressLines())) + .textStyle(style(FOOTER_SIZE, MUTED, DocumentTextDecoration.DEFAULT))); + }); + row.addParagraph(p -> { + p.name("FooterSite"); + DocumentTextStyle linkStyle = + style(FOOTER_SIZE, ACCENT, DocumentTextDecoration.DEFAULT); + if (website.isBlank()) { + p.text(""); + } else { + p.inlineText(website, linkStyle, ContactUri.webLink(website)); + } + p.align(TextAlign.RIGHT); + p.margin(new DocumentInsets(0, px(3), 0, 0)); + }); + }); + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/WorkspaceIcons.java b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/WorkspaceIcons.java new file mode 100644 index 000000000..da19eb9b0 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/WorkspaceIcons.java @@ -0,0 +1,87 @@ +package com.demcha.compose.document.templates.invoice.presets; + +import com.demcha.compose.document.svg.SvgIcon; + +import java.io.IOException; +import java.io.InputStream; +import java.io.UncheckedIOException; +import java.nio.charset.StandardCharsets; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.Map; + +/** + * Classpath loader for the Workspace invoice marks: the two party glyphs, the + * three a service line may take, and the three the preset places itself — the + * bank on the payment card, the information mark on its note, and the calendar + * on the due panel. + * + *

    They ship inside the templates artifact under + * {@code templates/invoice/workspace/icons/} as SVG, so they scale with the box + * they are drawn into rather than being resampled. Parsed icons are cached per + * token, so a sheet that repeats a mark reads each file once per JVM.

    + * + *

    The set is coloured, not tinted

    + * + *

    Each glyph carries its colour in its own markup: white for the marks that + * are knocked out of a filled disc or tile, accent for the ones that stand on + * the page. A token therefore names a glyph and where it is meant to + * sit.

    + * + *

    {@link #LINE_TOKENS} is the vocabulary a document names through + * {@code InvoiceServiceLines.Line.icon()}. It is scoped to this preset — + * another preset packages its own set.

    + */ +final class WorkspaceIcons { + + static final String BILL_TO = "bill-to"; + static final String SHIP_TO = "ship-to"; + static final String BANK = "bank"; + static final String INFO = "info"; + static final String CALENDAR = "calendar"; + + private static final String ICON_ROOT = "/templates/invoice/workspace/icons/"; + + /** The marks a document may name on a service line. */ + static final Set LINE_TOKENS = Set.of("search", "grid", "shield"); + + private static final Set TOKENS = Set.of( + BILL_TO, SHIP_TO, BANK, INFO, CALENDAR, "search", "grid", "shield"); + + private static final Map CACHE = new ConcurrentHashMap<>(); + + private WorkspaceIcons() { + } + + /** + * Reads one mark. + * + * @param token one of the packaged tokens + * @return the parsed icon + * @throws IllegalArgumentException when the token names no packaged mark, + * listing the ones a document may choose — an icon token is data, + * so a wrong one is a data error + */ + static SvgIcon icon(String token) { + if (!TOKENS.contains(token)) { + throw new IllegalArgumentException( + "Unknown workspace invoice icon token '" + token + + "'. This preset packages " + LINE_TOKENS.stream().sorted().toList() + + " for a service line's mark."); + } + return CACHE.computeIfAbsent(token, WorkspaceIcons::read); + } + + private static SvgIcon read(String token) { + String resourcePath = ICON_ROOT + token + ".svg"; + try (InputStream input = WorkspaceIcons.class.getResourceAsStream(resourcePath)) { + if (input == null) { + throw new IllegalStateException("Missing workspace invoice icon: " + resourcePath); + } + return SvgIcon.parse(new String(input.readAllBytes(), StandardCharsets.UTF_8)); + } catch (IOException e) { + throw new UncheckedIOException( + "Failed to read workspace invoice icon: " + resourcePath, e); + } + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/WorkspaceInvoice.java b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/WorkspaceInvoice.java new file mode 100644 index 000000000..d14b17ed0 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/WorkspaceInvoice.java @@ -0,0 +1,174 @@ +package com.demcha.compose.document.templates.invoice.presets; + +import com.demcha.compose.document.api.DocumentSession; +import com.demcha.compose.document.api.PageMarginRule; +import com.demcha.compose.document.output.DocumentHeaderFooter; +import com.demcha.compose.document.output.DocumentHeaderFooterZone; +import com.demcha.compose.document.style.DocumentInsets; +import com.demcha.compose.document.templates.api.DocumentTemplate; +import com.demcha.compose.document.templates.data.invoice.StructuredInvoiceData; + +import java.util.List; +import java.util.Objects; + +import static com.demcha.compose.document.templates.invoice.presets.WorkspaceStyles.CONTINUATION_MARGIN; +import static com.demcha.compose.document.templates.invoice.presets.WorkspaceStyles.FACE; +import static com.demcha.compose.document.templates.invoice.presets.WorkspaceStyles.MUTED; +import static com.demcha.compose.document.templates.invoice.presets.WorkspaceStyles.PAGE; +import static com.demcha.compose.document.templates.invoice.presets.WorkspaceStyles.PAGE_MARGIN; +import static com.demcha.compose.document.templates.invoice.presets.WorkspaceStyles.PAGE_NUMBER_BAND; +import static com.demcha.compose.document.templates.invoice.presets.WorkspaceStyles.PAGE_NUMBER_SIZE; + +/** + * Workspace — a violet-accented SaaS invoice: a brand masthead over an accent + * bar, a half-split issuer and metadata header, two addressed parties on + * discs, a line-item table whose marks sit on coloured tiles, a settlement row + * pairing the bank details against the totals, and a closing band carrying the + * wordmark. + * + *

    It flows

    + * + *

    The design shows four service lines and real data brings dozens, so the + * table is the region that grows: its header repeats on every page it reaches, + * a continuation page reserves a deeper bottom margin than page one, and every + * page carries its number.

    + * + *

    Numbering every page including the first is a departure from the design, + * which is a single sheet and has none. Suppressing page one would match it and + * still make a missing page detectable — a document that opens on "Page 2 of 3" + * is obviously incomplete — but a page without a number cannot be told from a + * page that was lost, so every page carries one.

    + * + *

    One column, not five

    + * + *

    The table has an outer box and a rule between every row and no interior + * verticals, which is exactly what a single-column table draws: a cell strokes + * all four of its own edges and a one-column table has no interior vertical + * edge. The five columns are a row inside each cell. Built the other way round + * — the box from a stroked section wrapped round the table — every continuation + * page would end in an empty bordered strip, and on page one that strip's + * bottom edge lands on the page number.

    + * + *

    The lockup and the wordmark are the caller's

    + * + *

    The design opens with a mark and closes with a wordmark. Both come from + * {@code InvoiceBrand}: the masthead draws the logo at the design's measured + * width, or the brand's name when there is no logo, and the closing band always + * sets the name. The templates artifact carries no mark of its own.

    + * + *

    What the document says, and what the preset chooses

    + * + *

    A service line names its own mark through + * {@code InvoiceServiceLines.Line.icon()}, from this preset's own vocabulary, + * and an unknown token is reported as a data error naming the set. A mark the + * design gives a colour is knocked out of a tile in it; one it does not is drawn + * bare. A line with no token is drawn without a mark. The party discs, the bank + * mark, the information mark and the calendar are the preset's: which glyph + * opens which block is a property of the design rather than of the document.

    + * + *

    Figures are written with the locale stated rather than inherited, so the + * same data renders the same sheet on any machine.

    + * + *

    Fonts

    + * + *

    The sheet is set in Lato throughout, and the face was chosen by + * measurement: its natural widths for eight of the design's strings land within + * 2–5% of the measured ink, where other grotesques are 8–20% out at the same cap + * height. The templates artifact carries no fonts — register the family on the + * session, or the engine substitutes and the measured geometry no longer + * matches the type sitting in it.

    + * + *

    Usage:

    + *
    {@code
    + * DocumentTemplate template = WorkspaceInvoice.create();
    + * template.compose(session, invoice);
    + * }
    + * + * @since 2.4.0 + */ +public final class WorkspaceInvoice { + + /** Stable identifier of this preset. */ + public static final String ID = "workspace-invoice"; + + /** Human-readable name of this preset. */ + public static final String DISPLAY_NAME = "Workspace Invoice"; + + private WorkspaceInvoice() { + } + + /** + * Creates the template. + * + * @return a template composing a {@link StructuredInvoiceData} + */ + public static DocumentTemplate create() { + return new Template(); + } + + private record Template() implements DocumentTemplate { + + @Override + public String id() { + return ID; + } + + @Override + public String displayName() { + return DISPLAY_NAME; + } + + @Override + public void compose(DocumentSession document, StructuredInvoiceData data) { + Objects.requireNonNull(document, "document"); + Objects.requireNonNull(data, "data"); + + // Every length on the sheet is a share of the design's own grid, so + // the preset sets the page and its margins rather than following + // what the caller configured. + document.pageSize(PAGE); + document.margin(PAGE_MARGIN); + document.pageMargins(List.of(PageMarginRule.from(2, CONTINUATION_MARGIN))); + renderPageNumber(document); + + String currency = data.currencyCode(); + document.pageFlow(page -> { + page.name("WorkspaceInvoice").padding(DocumentInsets.zero()).spacing(0); + WorkspaceMasthead.renderHeader(page, data.brand(), data.supplier(), + data.masthead()); + WorkspaceMasthead.renderParties(page, data.billTo(), data.shipTo()); + WorkspaceLines.render(page, data.serviceLines(), currency); + WorkspaceSettlement.render(page, data.payment(), data.totals(), currency); + WorkspaceClosing.render(page, data.notes(), data.brand(), data.supplier()); + }); + } + + /** + * The only chrome the sheet has. The design's own closing band cannot be + * chrome — a header/footer zone carries left, centre and right strings + * and nothing else, and the band holds a wordmark and a link — so page + * identity is carried by the page number alone. + * + *

    The height is stated because a default band is taller than the + * design leaves under its closing block, and a default-height band takes + * that much off the content area and pushes the band onto a second + * page. Sized to the bottom margin it sits inside the margin instead of + * taking content space, and the type is sized to the band rather than + * the other way round.

    + */ + private static void renderPageNumber(DocumentSession document) { + document.footer(DocumentHeaderFooter.builder() + .zone(DocumentHeaderFooterZone.FOOTER) + .centerText("Page {page} of {pages}") + // The zone's own default is the standard-14 face, which + // would leave the page number the one line on the sheet in + // a family the design never uses. + .fontName(FACE) + .height((float) PAGE_NUMBER_BAND) + .fontSize((float) PAGE_NUMBER_SIZE) + .textColor(MUTED) + .showSeparator(false) + .build()); + } + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/WorkspaceLines.java b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/WorkspaceLines.java new file mode 100644 index 000000000..301593ccf --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/WorkspaceLines.java @@ -0,0 +1,200 @@ +package com.demcha.compose.document.templates.invoice.presets; + +import com.demcha.compose.document.dsl.PageFlowBuilder; +import com.demcha.compose.document.dsl.RowBuilder; +import com.demcha.compose.document.dsl.TableBuilder; +import com.demcha.compose.document.node.DocumentNode; +import com.demcha.compose.document.node.RowVerticalAlign; +import com.demcha.compose.document.node.TextAlign; +import com.demcha.compose.document.style.DocumentInsets; +import com.demcha.compose.document.style.DocumentRowColumn; +import com.demcha.compose.document.style.DocumentTextDecoration; +import com.demcha.compose.document.table.DocumentTableCell; +import com.demcha.compose.document.table.DocumentTableColumn; +import com.demcha.compose.document.templates.data.invoice.InvoiceServiceLines; + +import java.util.List; + +import static com.demcha.compose.document.templates.invoice.presets.WorkspaceStyles.ACCENT; +import static com.demcha.compose.document.templates.invoice.presets.WorkspaceStyles.BODY_SIZE; +import static com.demcha.compose.document.templates.invoice.presets.WorkspaceStyles.CELL_PAD_L; +import static com.demcha.compose.document.templates.invoice.presets.WorkspaceStyles.CELL_SIZE; +import static com.demcha.compose.document.templates.invoice.presets.WorkspaceStyles.COLUMN_SHARES; +import static com.demcha.compose.document.templates.invoice.presets.WorkspaceStyles.CONTENT_W; +import static com.demcha.compose.document.templates.invoice.presets.WorkspaceStyles.HEADER_CELL_STYLE; +import static com.demcha.compose.document.templates.invoice.presets.WorkspaceStyles.HEAD_PAD_L; +import static com.demcha.compose.document.templates.invoice.presets.WorkspaceStyles.INK; +import static com.demcha.compose.document.templates.invoice.presets.WorkspaceStyles.ITEM_CELL_STYLE; +import static com.demcha.compose.document.templates.invoice.presets.WorkspaceStyles.ITEM_SUB_SIZE; +import static com.demcha.compose.document.templates.invoice.presets.WorkspaceStyles.ITEM_TEXT_GAP; +import static com.demcha.compose.document.templates.invoice.presets.WorkspaceStyles.ITEM_TILE_D; +import static com.demcha.compose.document.templates.invoice.presets.WorkspaceStyles.ITEM_TITLE_SIZE; +import static com.demcha.compose.document.templates.invoice.presets.WorkspaceStyles.MUTED; +import static com.demcha.compose.document.templates.invoice.presets.WorkspaceStyles.TABLE_HEAD_SIZE; +import static com.demcha.compose.document.templates.invoice.presets.WorkspaceStyles.blockGap; +import static com.demcha.compose.document.templates.invoice.presets.WorkspaceStyles.gap; +import static com.demcha.compose.document.templates.invoice.presets.WorkspaceStyles.px; +import static com.demcha.compose.document.templates.invoice.presets.WorkspaceStyles.style; + +/** + * The service lines. + * + *

    One column, not five

    + * + *

    The design's table has an outer box and a rule between every row and + * no interior verticals — which is exactly the rule topology a + * single-column table draws, because a cell strokes all four of its own edges + * and a one-column table has no interior vertical edge to draw. The five + * columns are a row inside each cell, laid out on shares of the table's width, + * so they stay aligned down the page without the engine drawing a line between + * them.

    + * + *

    Reaching the same topology the other way — unstroked cells, separators as + * full-width rule rows, and the outer box from a stroked section wrapped round + * the table — is wrong on a document that paginates: a section's box fills its + * page fragment rather than hugging its rows, so every continuation page would + * end with an empty bordered strip below its last line, and on page one that + * strip's bottom edge lands on the page number.

    + */ +final class WorkspaceLines { + + private WorkspaceLines() { + } + + /** + * Draws the table. + * + * @param page the page flow + * @param serviceLines the columns and the lines + * @param currencyCode the ISO code the figures are stated in + */ + static void render(PageFlowBuilder page, InvoiceServiceLines serviceLines, + String currencyCode) { + page.addSection("LineItemsBox", box -> { + box.spacing(0) + .padding(DocumentInsets.zero()) + .margin(new DocumentInsets(blockGap(45, BODY_SIZE, 0), 0, px(27), 0)); + box.addTable(table -> { + table.name("LineItems") + .width(CONTENT_W) + .columns(DocumentTableColumn.fixed(CONTENT_W)); + renderHeader(table, serviceLines.columns(), currencyCode); + List lines = serviceLines.lines(); + for (int i = 0; i < lines.size(); i++) { + table.rowCells(DocumentTableCell + .node(renderLine(lines.get(i), i)) + .withStyle(ITEM_CELL_STYLE)); + } + }); + }); + } + + /** + * The header repeats on every page its table reaches. It is one cell like + * every other row, so its own stroke draws the table's top edge and the rule + * under the band — in the accent's border step, which is what the design + * tints the band's edges with. + */ + private static void renderHeader(TableBuilder table, InvoiceServiceLines.Columns columns, + String currencyCode) { + RowBuilder row = new RowBuilder(); + row.name("LineItemsHeader").spacing(0).columns(columnWidths()); + row.addParagraph(p -> p + .name("HeadDescription") + .text(columns.description()) + .textStyle(style(TABLE_HEAD_SIZE, ACCENT, DocumentTextDecoration.BOLD)) + .margin(new DocumentInsets(0, 0, 0, HEAD_PAD_L))); + headCell(row, "HeadPlan", columns.servicePeriod()); + headCell(row, "HeadQuantity", columns.quantity()); + // The currency is named once per money column rather than on every + // figure under it, which is where this design states it. + headCell(row, "HeadUnitPrice", + WorkspaceText.labelWithCurrency(columns.unitPrice(), currencyCode)); + headCell(row, "HeadAmount", + WorkspaceText.labelWithCurrency(columns.amount(), currencyCode)); + table.headerCells(DocumentTableCell.node(row.build()).withStyle(HEADER_CELL_STYLE)) + .repeatHeader(); + } + + private static void headCell(RowBuilder row, String name, String label) { + row.addParagraph(p -> p + .name(name) + .text(label) + .textStyle(style(TABLE_HEAD_SIZE, ACCENT, DocumentTextDecoration.BOLD)) + .align(TextAlign.CENTER)); + } + + /** The five columns, as a row inside the row's single cell. */ + private static DocumentNode renderLine(InvoiceServiceLines.Line line, int index) { + int n = index + 1; + RowBuilder row = new RowBuilder(); + row.name("Item" + n) + .spacing(0) + .verticalAlign(RowVerticalAlign.CENTER) + .columns(columnWidths()); + row.addSection("ItemDescription" + n, cell -> { + cell.spacing(0).padding(new DocumentInsets(0, 0, 0, CELL_PAD_L)); + // Mark and text are a horizontal pair inside a row cell, which the + // layout compiler refuses as a bare row. + WorkspaceWidgets.layeredRow(cell, "ItemBody" + n, body -> { + body.spacing(ITEM_TEXT_GAP) + .verticalAlign(RowVerticalAlign.CENTER) + .columns(DocumentRowColumn.fixed(ITEM_TILE_D), + DocumentRowColumn.weight(1)); + body.addSection("ItemGlyph" + n, glyph -> { + glyph.spacing(0); + if (!line.icon().isBlank()) { + glyph.add(WorkspaceWidgets.tile(line.icon())); + } + }); + body.addSection("ItemText" + n, text -> { + text.spacing(gap(25, ITEM_TITLE_SIZE)); + text.addParagraph(p -> p + .name("ItemTitle" + n) + .text(line.title()) + .textStyle(style(ITEM_TITLE_SIZE, INK, + DocumentTextDecoration.BOLD))); + if (!line.description().isBlank()) { + text.addParagraph(p -> p + .name("ItemSubtitle" + n) + .text(line.description()) + .textStyle(style(ITEM_SUB_SIZE, MUTED, + DocumentTextDecoration.DEFAULT))); + } + }); + }); + }); + valueCell(row, "ItemPlan" + n, line.servicePeriod()); + valueCell(row, "ItemQuantity" + n, + WorkspaceText.quantityWithUnit(line.quantity(), line.unit())); + valueCell(row, "ItemUnitPrice" + n, WorkspaceText.money(line.unitPrice())); + valueCell(row, "ItemAmount" + n, WorkspaceText.money(line.amount())); + return row.build(); + } + + private static void valueCell(RowBuilder row, String name, String value) { + row.addParagraph(p -> p + .name(name) + .text(value) + .textStyle(style(CELL_SIZE, MUTED, DocumentTextDecoration.DEFAULT)) + .align(TextAlign.CENTER)); + } + + /** + * The five column widths as shares of the table's own width. The last takes + * whatever the others leave: five independently rounded shares sum to the + * width plus a half-thousandth of a point, and the engine refuses a row + * narrower than its columns. + */ + private static DocumentRowColumn[] columnWidths() { + DocumentRowColumn[] columns = new DocumentRowColumn[COLUMN_SHARES.length]; + double assigned = 0; + for (int i = 0; i < COLUMN_SHARES.length - 1; i++) { + double width = CONTENT_W * COLUMN_SHARES[i]; + assigned += width; + columns[i] = DocumentRowColumn.fixed(width); + } + columns[COLUMN_SHARES.length - 1] = DocumentRowColumn.fixed(CONTENT_W - assigned); + return columns; + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/WorkspaceMasthead.java b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/WorkspaceMasthead.java new file mode 100644 index 000000000..e3364d569 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/WorkspaceMasthead.java @@ -0,0 +1,304 @@ +package com.demcha.compose.document.templates.invoice.presets; + +import com.demcha.compose.document.dsl.ImageBuilder; +import com.demcha.compose.document.dsl.PageFlowBuilder; +import com.demcha.compose.document.dsl.SectionBuilder; +import com.demcha.compose.document.node.RowVerticalAlign; +import com.demcha.compose.document.node.TextAlign; +import com.demcha.compose.document.style.DocumentBorders; +import com.demcha.compose.document.style.DocumentInsets; +import com.demcha.compose.document.style.DocumentRowColumn; +import com.demcha.compose.document.style.DocumentStroke; +import com.demcha.compose.document.style.DocumentTextDecoration; +import com.demcha.compose.document.templates.data.invoice.InvoiceBrand; +import com.demcha.compose.document.templates.data.invoice.InvoiceContactBlock; +import com.demcha.compose.document.templates.data.invoice.InvoiceMasthead; +import com.demcha.compose.document.templates.data.invoice.InvoiceRecipient; + +import java.util.ArrayList; +import java.util.List; + +import static com.demcha.compose.document.templates.invoice.presets.WorkspaceStyles.ACCENT; +import static com.demcha.compose.document.templates.invoice.presets.WorkspaceStyles.ACCENT_RULE_T; +import static com.demcha.compose.document.templates.invoice.presets.WorkspaceStyles.ACCENT_RULE_W; +import static com.demcha.compose.document.templates.invoice.presets.WorkspaceStyles.BODY_SIZE; +import static com.demcha.compose.document.templates.invoice.presets.WorkspaceStyles.DISC_D; +import static com.demcha.compose.document.templates.invoice.presets.WorkspaceStyles.DISC_GAP; +import static com.demcha.compose.document.templates.invoice.presets.WorkspaceStyles.HAIRLINE; +import static com.demcha.compose.document.templates.invoice.presets.WorkspaceStyles.HALF; +import static com.demcha.compose.document.templates.invoice.presets.WorkspaceStyles.INK; +import static com.demcha.compose.document.templates.invoice.presets.WorkspaceStyles.ISSUER_TAX_LABEL_W; +import static com.demcha.compose.document.templates.invoice.presets.WorkspaceStyles.LINE_BOX; +import static com.demcha.compose.document.templates.invoice.presets.WorkspaceStyles.LOCKUP_W; +import static com.demcha.compose.document.templates.invoice.presets.WorkspaceStyles.META_CELL_INSET; +import static com.demcha.compose.document.templates.invoice.presets.WorkspaceStyles.META_LABEL_SIZE; +import static com.demcha.compose.document.templates.invoice.presets.WorkspaceStyles.META_LABEL_W; +import static com.demcha.compose.document.templates.invoice.presets.WorkspaceStyles.NAME_SIZE; +import static com.demcha.compose.document.templates.invoice.presets.WorkspaceStyles.PARTY_TAX_LABEL_W; +import static com.demcha.compose.document.templates.invoice.presets.WorkspaceStyles.PARTY_TEXT_INDENT; +import static com.demcha.compose.document.templates.invoice.presets.WorkspaceStyles.RULE_THIN; +import static com.demcha.compose.document.templates.invoice.presets.WorkspaceStyles.SECTION_LABEL_SIZE; +import static com.demcha.compose.document.templates.invoice.presets.WorkspaceStyles.TITLE_RIGHT_INSET; +import static com.demcha.compose.document.templates.invoice.presets.WorkspaceStyles.TITLE_SIZE; +import static com.demcha.compose.document.templates.invoice.presets.WorkspaceStyles.TITLE_TOP_LIFT; +import static com.demcha.compose.document.templates.invoice.presets.WorkspaceStyles.WORDMARK_SIZE; +import static com.demcha.compose.document.templates.invoice.presets.WorkspaceStyles.blockGap; +import static com.demcha.compose.document.templates.invoice.presets.WorkspaceStyles.capOffset; +import static com.demcha.compose.document.templates.invoice.presets.WorkspaceStyles.gap; +import static com.demcha.compose.document.templates.invoice.presets.WorkspaceStyles.px; +import static com.demcha.compose.document.templates.invoice.presets.WorkspaceStyles.style; + +/** + * The header and the two addressed parties: the lockup beside the title, the + * accent bar under it, the issuer against the metadata grid, and the bill-to / + * ship-to split. + * + *

    Both splits divide the content box in half: measured, their two vertical + * dividers land on the same x. Neither divider is a node — each is the right + * cell's left border, which is why it begins and ends with that cell's + * content rather than running the row's full height.

    + */ +final class WorkspaceMasthead { + + private WorkspaceMasthead() { + } + + /** + * Draws the header, the accent bar, the issuer/metadata row and the rule + * that closes it. + * + * @param page the page flow + * @param brand the lockup + * @param supplier who is invoicing + * @param masthead the title and the metadata rows + */ + static void renderHeader(PageFlowBuilder page, InvoiceBrand brand, + InvoiceContactBlock supplier, InvoiceMasthead masthead) { + renderLockupRow(page, brand, masthead); + renderAccentBar(page); + page.addRow("IssuerMetaRow", row -> { + row.spacing(0).weights(HALF, HALF); + row.addSection("IssuerAddress", cell -> renderIssuer(cell, supplier)); + row.addSection("InvoiceMeta", cell -> { + cell.spacing(0) + .borders(DocumentBorders.left(DocumentStroke.of(HAIRLINE, RULE_THIN))) + .padding(new DocumentInsets(0, 0, 0, META_CELL_INSET)); + renderMeta(cell, masthead.entries()); + }); + }); + WorkspaceWidgets.fullWidthRule(page, "PartiesRule", HAIRLINE, RULE_THIN, + blockGap(28.5, BODY_SIZE, 0), px(24)); + } + + /** + * TOP, not CENTER: the title's line box is taller than the lockup, so + * centring pushes the lockup down the row it is supposed to set the top edge + * of. + * + *

    What fills the lockup column comes from the brand — a logo drawn to the + * design's measured width, or the brand's name set as a wordmark. The + * templates artifact carries no mark of its own.

    + */ + private static void renderLockupRow(PageFlowBuilder page, InvoiceBrand brand, + InvoiceMasthead masthead) { + page.addRow("Masthead", row -> { + row.spacing(0) + .verticalAlign(RowVerticalAlign.TOP) + .columns(DocumentRowColumn.fixed(LOCKUP_W), DocumentRowColumn.weight(1)); + row.addSection("BrandLockup", cell -> { + cell.spacing(0); + if (brand.logo() != null) { + cell.add(new ImageBuilder() + .name("BrandLogo") + .source(brand.logo()) + .width(LOCKUP_W) + .build()); + } else if (!brand.name().isBlank()) { + cell.addParagraph(p -> p + .name("BrandWordmark") + .text(brand.name()) + .textStyle(style(WORDMARK_SIZE, INK, DocumentTextDecoration.BOLD))); + } + }); + row.addSection("InvoiceTitle", cell -> cell + .spacing(0) + .padding(new DocumentInsets(-TITLE_TOP_LIFT, TITLE_RIGHT_INSET, 0, 0)) + .addParagraph(p -> p + .name("InvoiceTitleText") + .text(masthead.title()) + .textStyle(style(TITLE_SIZE, INK, DocumentTextDecoration.BOLD)) + .align(TextAlign.RIGHT))); + }); + } + + /** + * The bar is anchored to the content box's left edge, not to the mark above + * it, so its width derives from the page rather than from the asset. Stating + * its own position rather than a gap keeps the two facts separable when + * either changes. + */ + private static void renderAccentBar(PageFlowBuilder page) { + page.addLine(line -> line + .name("BrandAccentRule") + .horizontal(ACCENT_RULE_W) + .thickness(ACCENT_RULE_T) + .color(ACCENT) + .margin(new DocumentInsets( + px(89) - (LINE_BOX * TITLE_SIZE - TITLE_TOP_LIFT), + 0, + blockGap(19, 0, META_LABEL_SIZE), + 0))); + } + + private static void renderIssuer(SectionBuilder cell, InvoiceContactBlock supplier) { + // The two cells of the split do not share a top edge: the metadata block + // starts above the issuer name, and the divider starts with it. + cell.spacing(0).padding(new DocumentInsets(px(12), 0, 0, px(2))); + cell.addParagraph(p -> p + .name("IssuerName") + .text(supplier.legalName()) + .textStyle(style(NAME_SIZE, INK, DocumentTextDecoration.BOLD))); + cell.addSection("IssuerAddressLines", lines -> { + lines.spacing(gap(27.5, BODY_SIZE)) + .margin(new DocumentInsets(gap(38, NAME_SIZE), 0, 0, 0)); + WorkspaceWidgets.textLines(lines, "IssuerAddressLine", supplier.addressLines(), + BODY_SIZE, INK); + }); + renderLabelledRow(cell, "Issuer", supplier.taxRegistrationLabel(), + supplier.taxRegistrationNumber(), ISSUER_TAX_LABEL_W, gap(38, BODY_SIZE)); + } + + private static void renderMeta(SectionBuilder cell, List entries) { + cell.addSection("InvoiceMetaRows", rows -> { + rows.spacing(gap(36.2, BODY_SIZE)); + int index = 0; + for (InvoiceMasthead.Entry entry : entries) { + String name = "MetaRow" + (++index); + WorkspaceWidgets.layeredRow(rows, name, row -> { + row.spacing(0) + .columns(DocumentRowColumn.fixed(META_LABEL_W), + DocumentRowColumn.weight(1)); + row.addParagraph(p -> p + .name(name + "Label") + .text(entry.label()) + .textStyle(style(META_LABEL_SIZE, INK, + DocumentTextDecoration.DEFAULT))); + row.addParagraph(p -> p + .name(name + "Value") + .text(entry.value()) + .textStyle(style(BODY_SIZE, + entry.emphasized() ? ACCENT : INK, + entry.emphasized() + ? DocumentTextDecoration.BOLD + : DocumentTextDecoration.DEFAULT))); + }); + } + }); + } + + /** + * Draws the two addressed parties. + * + * @param page the page flow + * @param billTo who is billed + * @param shipTo where the work went, or a party with no name when absent + */ + static void renderParties(PageFlowBuilder page, InvoiceRecipient billTo, + InvoiceRecipient shipTo) { + List parties = new ArrayList<>(); + parties.add(billTo); + if (!shipTo.name().isBlank()) { + parties.add(shipTo); + } + List tokens = List.of(WorkspaceIcons.BILL_TO, WorkspaceIcons.SHIP_TO); + + page.addRow("PartiesRow", row -> { + row.spacing(0).weights(WorkspaceWidgets.evenWeights(parties.size())); + for (int i = 0; i < parties.size(); i++) { + InvoiceRecipient party = parties.get(i); + String token = tokens.get(Math.min(i, tokens.size() - 1)); + boolean first = i == 0; + row.addSection("Party" + (i + 1), cell -> { + cell.spacing(0); + if (!first) { + // The divider belongs to the split, so it is the cell's + // own left edge rather than a rule drawn between them. + cell.borders(DocumentBorders.left(DocumentStroke.of(HAIRLINE, RULE_THIN))) + .padding(new DocumentInsets(0, 0, 0, px(28))); + } + renderParty(cell, party, token); + }); + } + }); + } + + /** + * One method for both parties: they are the same component with different + * data, down to the registration only one of them usually prints. + */ + private static void renderParty(SectionBuilder cell, InvoiceRecipient party, String token) { + WorkspaceWidgets.layeredRow(cell, "PartyHead", row -> { + row.spacing(DISC_GAP) + .verticalAlign(RowVerticalAlign.TOP) + .columns(DocumentRowColumn.fixed(DISC_D), DocumentRowColumn.weight(1)); + row.addSection("PartyDisc", disc -> disc + .spacing(0) + .add(WorkspaceWidgets.disc(token))); + row.addParagraph(p -> p + .name("PartyLabel") + .text(party.heading()) + .textStyle(style(SECTION_LABEL_SIZE, ACCENT, DocumentTextDecoration.BOLD)) + .margin(new DocumentInsets(capOffset(8, SECTION_LABEL_SIZE), 0, 0, 0))); + }); + cell.addSection("PartyBody", block -> { + block.spacing(0).padding(new DocumentInsets(0, 0, 0, PARTY_TEXT_INDENT)); + block.addParagraph(p -> p + .name("PartyName") + .text(party.name()) + .textStyle(style(NAME_SIZE, INK, DocumentTextDecoration.BOLD)) + .margin(new DocumentInsets(px(2), 0, 0, 0))); + if (!party.subline().isBlank()) { + block.addParagraph(p -> p + .name("PartySubline") + .text(party.subline()) + .textStyle(style(BODY_SIZE, INK, DocumentTextDecoration.DEFAULT)) + .margin(new DocumentInsets(gap(30, NAME_SIZE), 0, 0, 0))); + } + block.addSection("PartyAddressLines", lines -> { + lines.spacing(gap(28, BODY_SIZE)) + .margin(new DocumentInsets( + gap(35, party.subline().isBlank() ? NAME_SIZE : BODY_SIZE), + 0, 0, 0)); + WorkspaceWidgets.textLines(lines, "PartyAddressLine", party.addressLines(), + BODY_SIZE, INK); + }); + renderLabelledRow(block, "Party", party.registrationLabel(), + party.registrationNumber(), PARTY_TAX_LABEL_W, gap(47, BODY_SIZE)); + }); + } + + /** + * A labelled fact under an address — a registration, printed as a label + * column beside its value. Drawn only when there is a value: a label on its + * own is a heading over an absence. + */ + private static void renderLabelledRow(SectionBuilder parent, String prefix, String label, + String value, double labelWidth, double gapAbove) { + if (value == null || value.isBlank()) { + return; + } + WorkspaceWidgets.layeredRow(parent, prefix + "TaxLine", row -> { + row.spacing(0) + .margin(new DocumentInsets(gapAbove, 0, 0, 0)) + .columns(DocumentRowColumn.fixed(labelWidth), DocumentRowColumn.weight(1)); + row.addParagraph(p -> p + .name(prefix + "TaxLabel") + .text(label) + .textStyle(style(BODY_SIZE, INK, DocumentTextDecoration.DEFAULT))); + row.addParagraph(p -> p + .name(prefix + "TaxValue") + .text(value) + .textStyle(style(BODY_SIZE, INK, DocumentTextDecoration.DEFAULT))); + }); + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/WorkspaceSettlement.java b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/WorkspaceSettlement.java new file mode 100644 index 000000000..1e4a8ed77 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/WorkspaceSettlement.java @@ -0,0 +1,273 @@ +package com.demcha.compose.document.templates.invoice.presets; + +import com.demcha.compose.document.dsl.PageFlowBuilder; +import com.demcha.compose.document.dsl.SectionBuilder; +import com.demcha.compose.document.dsl.ShapeContainerBuilder; +import com.demcha.compose.document.node.HorizontalAlign; +import com.demcha.compose.document.node.RowVerticalAlign; +import com.demcha.compose.document.node.TextAlign; +import com.demcha.compose.document.style.DocumentInsets; +import com.demcha.compose.document.style.DocumentRowColumn; +import com.demcha.compose.document.style.DocumentStroke; +import com.demcha.compose.document.style.DocumentTextDecoration; +import com.demcha.compose.document.templates.data.invoice.InvoicePaymentBlock; +import com.demcha.compose.document.templates.data.invoice.InvoiceTotalsBlock; + +import static com.demcha.compose.document.templates.invoice.presets.WorkspaceStyles.ACCENT; +import static com.demcha.compose.document.templates.invoice.presets.WorkspaceStyles.ACCENT_BORDER; +import static com.demcha.compose.document.templates.invoice.presets.WorkspaceStyles.ACCENT_SURFACE; +import static com.demcha.compose.document.templates.invoice.presets.WorkspaceStyles.BODY; +import static com.demcha.compose.document.templates.invoice.presets.WorkspaceStyles.CARD_ICON; +import static com.demcha.compose.document.templates.invoice.presets.WorkspaceStyles.CARD_ICON_GAP; +import static com.demcha.compose.document.templates.invoice.presets.WorkspaceStyles.CARD_INNER_W; +import static com.demcha.compose.document.templates.invoice.presets.WorkspaceStyles.CARD_LABEL_W; +import static com.demcha.compose.document.templates.invoice.presets.WorkspaceStyles.CARD_PAD; +import static com.demcha.compose.document.templates.invoice.presets.WorkspaceStyles.CARD_RADIUS; +import static com.demcha.compose.document.templates.invoice.presets.WorkspaceStyles.CARD_TEXT_SIZE; +import static com.demcha.compose.document.templates.invoice.presets.WorkspaceStyles.CARD_TITLE_SIZE; +import static com.demcha.compose.document.templates.invoice.presets.WorkspaceStyles.DUE_GLYPH; +import static com.demcha.compose.document.templates.invoice.presets.WorkspaceStyles.DUE_LABEL_SIZE; +import static com.demcha.compose.document.templates.invoice.presets.WorkspaceStyles.DUE_PAD; +import static com.demcha.compose.document.templates.invoice.presets.WorkspaceStyles.DUE_RADIUS; +import static com.demcha.compose.document.templates.invoice.presets.WorkspaceStyles.DUE_TILE; +import static com.demcha.compose.document.templates.invoice.presets.WorkspaceStyles.DUE_TILE_GAP; +import static com.demcha.compose.document.templates.invoice.presets.WorkspaceStyles.DUE_TILE_RADIUS; +import static com.demcha.compose.document.templates.invoice.presets.WorkspaceStyles.DUE_VALUE_SIZE; +import static com.demcha.compose.document.templates.invoice.presets.WorkspaceStyles.HAIRLINE; +import static com.demcha.compose.document.templates.invoice.presets.WorkspaceStyles.HALF; +import static com.demcha.compose.document.templates.invoice.presets.WorkspaceStyles.INK; +import static com.demcha.compose.document.templates.invoice.presets.WorkspaceStyles.MUTED; +import static com.demcha.compose.document.templates.invoice.presets.WorkspaceStyles.NOTE_ICON; +import static com.demcha.compose.document.templates.invoice.presets.WorkspaceStyles.NOTE_ICON_COL; +import static com.demcha.compose.document.templates.invoice.presets.WorkspaceStyles.RULE_MED; +import static com.demcha.compose.document.templates.invoice.presets.WorkspaceStyles.RULE_THIN; +import static com.demcha.compose.document.templates.invoice.presets.WorkspaceStyles.SETTLEMENT_GUTTER; +import static com.demcha.compose.document.templates.invoice.presets.WorkspaceStyles.SETTLEMENT_LEFT; +import static com.demcha.compose.document.templates.invoice.presets.WorkspaceStyles.SETTLEMENT_RIGHT; +import static com.demcha.compose.document.templates.invoice.presets.WorkspaceStyles.SETTLEMENT_RIGHT_INSET; +import static com.demcha.compose.document.templates.invoice.presets.WorkspaceStyles.SURFACE; +import static com.demcha.compose.document.templates.invoice.presets.WorkspaceStyles.TOTALS_PAD_L; +import static com.demcha.compose.document.templates.invoice.presets.WorkspaceStyles.TOTALS_PAD_R; +import static com.demcha.compose.document.templates.invoice.presets.WorkspaceStyles.TOTALS_RULE_W; +import static com.demcha.compose.document.templates.invoice.presets.WorkspaceStyles.TOTALS_SIZE; +import static com.demcha.compose.document.templates.invoice.presets.WorkspaceStyles.TOTAL_LABEL_SIZE; +import static com.demcha.compose.document.templates.invoice.presets.WorkspaceStyles.TOTAL_VALUE_SIZE; +import static com.demcha.compose.document.templates.invoice.presets.WorkspaceStyles.capOffset; +import static com.demcha.compose.document.templates.invoice.presets.WorkspaceStyles.gap; +import static com.demcha.compose.document.templates.invoice.presets.WorkspaceStyles.px; +import static com.demcha.compose.document.templates.invoice.presets.WorkspaceStyles.style; + +/** + * The settlement row: the payment card against the totals and the panel that + * closes them. + * + *

    The split is not the half the two rows above it use — the left cell is + * narrower than the right, with a wide gutter between. That asymmetry is in the + * design.

    + */ +final class WorkspaceSettlement { + + private WorkspaceSettlement() { + } + + /** + * Draws the row. + * + * @param page the page flow + * @param payment the bank details, the note and the due notice + * @param totals the summed rows and the amount due + * @param currencyCode the ISO code the figures are stated in + */ + static void render(PageFlowBuilder page, InvoicePaymentBlock payment, + InvoiceTotalsBlock totals, String currencyCode) { + page.addRow("SettlementRow", row -> { + row.spacing(SETTLEMENT_GUTTER).weights(SETTLEMENT_LEFT, SETTLEMENT_RIGHT); + row.addSection("PaymentDetails", cell -> renderCard(cell, payment)); + row.addSection("Settlement", cell -> { + cell.spacing(0).padding(new DocumentInsets(0, SETTLEMENT_RIGHT_INSET, 0, 0)); + renderTotals(cell, totals, currencyCode); + renderDuePanel(cell, payment); + }); + }); + } + + /** + * An outlined rounded card. Built from a fill, a stroke, a radius and a + * padding rather than a panel helper, which takes one padding value where + * this card's insets are asymmetric. + */ + private static void renderCard(SectionBuilder cell, InvoicePaymentBlock payment) { + cell.spacing(0) + .fillColor(SURFACE) + .stroke(DocumentStroke.of(ACCENT_BORDER, RULE_MED)) + .cornerRadius(CARD_RADIUS) + .padding(CARD_PAD) + .keepTogether(); + + WorkspaceWidgets.layeredRow(cell, "CardHead", row -> { + row.spacing(CARD_ICON_GAP) + .verticalAlign(RowVerticalAlign.TOP) + .columns(DocumentRowColumn.fixed(CARD_ICON), DocumentRowColumn.weight(1)); + row.addSection("CardIcon", glyph -> glyph + .spacing(0) + .add(WorkspaceWidgets.glyph(WorkspaceIcons.BANK, CARD_ICON))); + row.addParagraph(p -> p + .name("CardLabel") + .text(payment.heading()) + .textStyle(style(CARD_TITLE_SIZE, ACCENT, DocumentTextDecoration.BOLD)) + .margin(new DocumentInsets(capOffset(10, CARD_TITLE_SIZE), 0, 0, 0))); + }); + + cell.addSection("CardFields", fields -> { + // The head row is as tall as its glyph, and the first field sits a + // measured distance below the head's top — not below the heading's + // own line box, which is shorter than the glyph. + fields.spacing(gap(22.4, CARD_TEXT_SIZE)) + .margin(new DocumentInsets(px(41 - 27 - 3), 0, 0, 0)); + int index = 0; + for (InvoicePaymentBlock.Field field : payment.fields()) { + String name = "CardField" + (++index); + WorkspaceWidgets.layeredRow(fields, name, row -> { + row.spacing(0) + .columns(DocumentRowColumn.fixed(CARD_LABEL_W), + DocumentRowColumn.weight(1)); + row.addParagraph(p -> p + .name(name + "Label") + .text(field.label()) + .textStyle(style(CARD_TEXT_SIZE, BODY, + DocumentTextDecoration.DEFAULT))); + row.addParagraph(p -> p + .name(name + "Value") + .text(field.value()) + .textStyle(style(CARD_TEXT_SIZE, BODY, + DocumentTextDecoration.DEFAULT))); + }); + } + }); + + if (payment.instruction().isBlank()) { + return; + } + cell.addLine(line -> line + .name("CardRule") + .horizontal(CARD_INNER_W) + .thickness(RULE_THIN) + .color(HAIRLINE) + .margin(new DocumentInsets(gap(23, CARD_TEXT_SIZE), 0, px(16), 0))); + + // Two cells rather than an inline glyph: the design's wrapped second + // line returns to the TEXT axis, not to the glyph's. + WorkspaceWidgets.layeredRow(cell, "CardNote", row -> { + row.spacing(0) + .columns(DocumentRowColumn.fixed(NOTE_ICON_COL), DocumentRowColumn.weight(1)); + row.addSection("CardNoteIcon", glyph -> glyph + .spacing(0) + .add(WorkspaceWidgets.glyph(WorkspaceIcons.INFO, NOTE_ICON, + HorizontalAlign.LEFT))); + row.addParagraph(p -> p + .name("CardNoteText") + .text(payment.instruction()) + .textStyle(style(CARD_TEXT_SIZE, MUTED, DocumentTextDecoration.DEFAULT)) + .lineSpacing(gap(22, CARD_TEXT_SIZE))); + }); + } + + private static void renderTotals(SectionBuilder cell, InvoiceTotalsBlock totals, + String currencyCode) { + cell.addSection("Totals", block -> { + block.spacing(gap(35, TOTALS_SIZE)) + .padding(new DocumentInsets(0, TOTALS_PAD_R, 0, TOTALS_PAD_L)) + .margin(new DocumentInsets(px(10.3), 0, 0, 0)) + .keepTogether(); + int index = 0; + for (InvoiceTotalsBlock.Row entry : totals.rows()) { + String name = "Total" + (++index); + WorkspaceWidgets.layeredRow(block, name, row -> { + row.spacing(0).weights(HALF, HALF); + row.addParagraph(p -> p + .name(name + "Label") + .text(entry.label()) + .textStyle(style(TOTALS_SIZE, INK, DocumentTextDecoration.DEFAULT))); + row.addParagraph(p -> p + .name(name + "Value") + .text(WorkspaceText.money(entry.amount())) + .textStyle(style(TOTALS_SIZE, INK, DocumentTextDecoration.DEFAULT)) + .align(TextAlign.RIGHT)); + }); + } + }); + + // Accent-coloured, not a grey hairline: a colour scan across it returns + // a clear violet hue. + cell.addLine(line -> line + .name("TotalsRule") + .horizontal(TOTALS_RULE_W) + .thickness(RULE_MED) + .color(ACCENT) + .margin(new DocumentInsets(gap(35, TOTALS_SIZE), 0, px(24.3), TOTALS_PAD_L))); + + cell.addSection("TotalDue", block -> { + block.spacing(0) + .padding(new DocumentInsets(0, TOTALS_PAD_R, 0, TOTALS_PAD_L)) + .keepTogether(); + WorkspaceWidgets.layeredRow(block, "TotalDueRow", row -> { + row.spacing(0).verticalAlign(RowVerticalAlign.CENTER).weights(HALF, HALF); + row.addParagraph(p -> p + .name("TotalDueLabel") + .text(totals.totalLabel()) + .textStyle(style(TOTAL_LABEL_SIZE, INK, DocumentTextDecoration.BOLD))); + row.addParagraph(p -> p + .name("TotalDueValue") + .text(WorkspaceText.coded(currencyCode, totals.totalAmount())) + .textStyle(style(TOTAL_VALUE_SIZE, ACCENT, DocumentTextDecoration.BOLD)) + .align(TextAlign.RIGHT)); + }); + }); + } + + /** + * The calendar tile has to be a shape: a bare glyph on the panel would lose + * the white square the design draws behind it, which is the only thing + * separating the glyph from the panel's tint. + */ + private static void renderDuePanel(SectionBuilder cell, InvoicePaymentBlock payment) { + if (payment.dueNotice().isBlank() && payment.dueNoticeEmphasis().isBlank()) { + return; + } + cell.addSection("PaymentDuePanel", panel -> { + panel.spacing(0) + .fillColor(ACCENT_SURFACE) + .stroke(DocumentStroke.of(ACCENT_BORDER, RULE_MED)) + .cornerRadius(DUE_RADIUS) + .padding(DUE_PAD) + .margin(new DocumentInsets(px(42.7), 0, 0, 0)) + .keepTogether(); + WorkspaceWidgets.layeredRow(panel, "PaymentDueRow", row -> { + row.spacing(DUE_TILE_GAP) + .verticalAlign(RowVerticalAlign.TOP) + .columns(DocumentRowColumn.fixed(DUE_TILE), DocumentRowColumn.weight(1)); + row.addSection("DueTile", tile -> tile + .spacing(0) + .add(new ShapeContainerBuilder() + .name("DueTileShape") + .roundedRect(DUE_TILE, DUE_TILE, DUE_TILE_RADIUS) + .fillColor(SURFACE) + .center(WorkspaceWidgets.glyph(WorkspaceIcons.CALENDAR, DUE_GLYPH)) + .build())); + row.addSection("DueText", text -> { + text.spacing(gap(23.6, DUE_LABEL_SIZE)) + .margin(new DocumentInsets(capOffset(5, DUE_LABEL_SIZE), 0, 0, 0)); + text.addParagraph(p -> p + .name("DueLabel") + .text(payment.dueNotice()) + .textStyle(style(DUE_LABEL_SIZE, INK, + DocumentTextDecoration.DEFAULT))); + text.addParagraph(p -> p + .name("DueValue") + .text(payment.dueNoticeEmphasis()) + .textStyle(style(DUE_VALUE_SIZE, INK, DocumentTextDecoration.BOLD))); + }); + }); + }); + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/WorkspaceStyles.java b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/WorkspaceStyles.java new file mode 100644 index 000000000..e0ccf1fa5 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/WorkspaceStyles.java @@ -0,0 +1,344 @@ +package com.demcha.compose.document.templates.invoice.presets; + +import com.demcha.compose.document.api.DocumentPageSize; +import com.demcha.compose.document.style.DocumentColor; +import com.demcha.compose.document.style.DocumentInsets; +import com.demcha.compose.document.style.DocumentStroke; +import com.demcha.compose.document.style.DocumentTextDecoration; +import com.demcha.compose.document.style.DocumentTextStyle; +import com.demcha.compose.document.table.DocumentTableStyle; +import com.demcha.compose.document.table.DocumentTableTextAnchor; +import com.demcha.compose.font.FontName; + +import java.util.Map; + +/** + * Design tokens for {@link WorkspaceInvoice} — the palette, the type scale and + * the geometry every part of the sheet measures against. + * + *

    The design was drawn at 1055 px across an A4 page, so {@link #PX} converts + * every length below from that raster to points.

    + * + *

    Sizes carry two corrections, both measured

    + * + *

    A cap measured on a screenshot includes about half a pixel of antialiasing + * on each edge and the whole of it lands in the size, so {@link #sizeForCap} + * takes it back off. And the body face sets uppercase about 6% wider than the + * design's face at the same cap height — measured across three all-caps runs, + * while mixed-case runs at the same sizes matched within 2% — so + * {@link #sizeForCaps} carries that as a size correction. There is no + * letter-spacing API to take it out with instead.

    + */ +final class WorkspaceStyles { + + private WorkspaceStyles() { + } + + static final DocumentPageSize PAGE = DocumentPageSize.A4; + static final double PAGE_WIDTH = PAGE.width(); + + /** The design's raster is 1055 px wide; every length below came off it. */ + static final double PX = PAGE_WIDTH / 1055.0; + + static final double MARGIN_T = px(46); + static final double MARGIN_R = px(47); + static final double MARGIN_L = px(45); + + /** + * Page one's bottom margin is the page-number band: whatever else is on the + * page, the band has to be reserved, or a paginated invoice runs its last + * line straight through the number. + */ + static final double MARGIN_B_FIRST = px(28); + static final double PAGE_NUMBER_BAND = px(28); + + /** + * How far the closing rule, the thank-you block and the brand band sit above + * the design's own positions. It buys the page number a band to sit in; + * without it there is nowhere on the page for one, because the design's + * closing block runs to within 15 px of the paper. + * + *

    Distributed rather than taken in one place, and the distribution is not + * arbitrary: the closing rule sits 17 px under the payment card, so taking + * the whole lift off its top margin would pull it into the card's bottom + * border. Nine px come off the rule — leaving it 8 px clear of the card — + * and the rest out of the two gaps below it. The three shares are not + * independent: a gap inside the block moves everything below it as well, so + * the band's total lift is their sum.

    + */ + static final double CLOSING_LIFT = px(18); + static final double CLOSING_RULE_LIFT = px(9); + static final double CLOSING_INNER_LIFT = (CLOSING_LIFT - CLOSING_RULE_LIFT) / 2.0; + + /** + * A continuation page reserves more: its last row would otherwise end + * against the paper rather than against a margin. + */ + static final double MARGIN_B_LATER = px(58); + + static final DocumentInsets PAGE_MARGIN = + new DocumentInsets(MARGIN_T, MARGIN_R, MARGIN_B_FIRST, MARGIN_L); + static final DocumentInsets CONTINUATION_MARGIN = + new DocumentInsets(MARGIN_T, MARGIN_R, MARGIN_B_LATER, MARGIN_L); + + /** Every width in this preset is a fraction of this. */ + static final double CONTENT_W = PAGE_WIDTH - MARGIN_L - MARGIN_R; + + /** + * The issuer/metadata and bill-to/ship-to rows both split the content box in + * half: measured, both vertical dividers land on the same x. + */ + static final double HALF = 0.5; + + static final DocumentColor ACCENT = DocumentColor.rgb(85, 42, 164); + static final DocumentColor ACCENT_SURFACE = DocumentColor.rgb(245, 242, 253); + static final DocumentColor ACCENT_BORDER = DocumentColor.rgb(199, 184, 236); + static final DocumentColor INK = DocumentColor.rgb(15, 16, 20); + static final DocumentColor BODY = DocumentColor.rgb(45, 47, 53); + static final DocumentColor MUTED = DocumentColor.rgb(100, 102, 108); + static final DocumentColor HAIRLINE = DocumentColor.rgb(232, 234, 238); + static final DocumentColor HAIRLINE_STRONG = DocumentColor.rgb(214, 216, 221); + static final DocumentColor SURFACE = DocumentColor.WHITE; + static final DocumentColor TILE_BLUE = DocumentColor.rgb(45, 90, 242); + static final DocumentColor TILE_GREEN = DocumentColor.rgb(31, 122, 85); + + static final FontName FACE = FontName.LATO; + + /** The face's cap height in ems, read off the TTF. */ + private static final double FACE_CAP_RATIO = 0.7165; + + /** The antialiasing a cap measured on a screenshot carries on each edge. */ + private static final double CAP_INK_BLEED = 0.5; + + /** How far below its own box top a line of text sits. */ + static final double TEXT_TOP_BEARING = 0.235; + + /** + * {@code lineSpacing} is points added between lines over a line box + * of 1.2× the type size, so a measured pitch converts as + * {@code pitch - 1.2 * size}, never as a ratio. + */ + static final double LINE_BOX = 1.2; + + /** + * The one size not taken straight from its cap. At the size its 45 px cap + * implies the title's ink came back 2.9% narrower than the design's with the + * right edges aligned, so it is set 3% larger — which costs 1.4 px of cap + * height to buy back 7 px of width. + */ + static final double TITLE_SIZE = sizeForCap(45) * 1.03; + + /** + * 3% under the size its 15 px cap implies. The two names set at it straddle + * the design — one comes back 1.9% wide and the other 6.9% — so the + * correction halves the larger error rather than making one exact and the + * other worse. + */ + static final double NAME_SIZE = sizeForCap(15) * 0.97; + static final double BODY_SIZE = sizeForCap(12.5); + static final double META_LABEL_SIZE = sizeForCap(12); + + /** + * The party labels keep the uncorrected size: measured against the design + * they come back 5% narrow plain and 11% narrow with the caps correction, so + * they are the one all-caps run the correction does not help. + */ + static final double SECTION_LABEL_SIZE = sizeForCap(12.5); + static final double TABLE_HEAD_SIZE = sizeForCaps(11); + static final double ITEM_TITLE_SIZE = sizeForCap(13); + static final double ITEM_SUB_SIZE = sizeForCap(11); + static final double CELL_SIZE = sizeForCaps(12); + static final double TOTALS_SIZE = sizeForCap(12); + static final double TOTAL_LABEL_SIZE = sizeForCaps(15); + static final double TOTAL_VALUE_SIZE = sizeForCap(19); + static final double CARD_TITLE_SIZE = sizeForCaps(12); + static final double CARD_TEXT_SIZE = sizeForCap(11); + static final double DUE_LABEL_SIZE = sizeForCaps(12); + static final double DUE_VALUE_SIZE = sizeForCap(16); + static final double THANKS_TITLE_SIZE = sizeForCap(12); + static final double THANKS_BODY_SIZE = sizeForCap(10); + static final double FOOTER_SIZE = sizeForCap(11); + static final double WORDMARK_SIZE = sizeForCap(25); + static final double PAGE_NUMBER_SIZE = sizeForCap(8); + + static final double RULE_THIN = px(1.4); + static final double RULE_MED = px(1.6); + static final double ACCENT_RULE_W = px(41); + static final double ACCENT_RULE_T = px(4); + + /** + * The lockup's box, measured off the design's own ink. A caller's logo is + * sized by the width, which is the dimension the design fixes. + */ + static final double LOCKUP_W = px(212); + + /** The title's ink stops short of the content box, not on it. */ + static final double TITLE_RIGHT_INSET = px(13); + + /** + * The lockup sets the row's top edge, so the title's own line box hangs its + * caps below where the design draws them. Nothing else in the row can absorb + * that, so the title is lifted by it. + */ + static final double TITLE_TOP_LIFT = px(7.3); + + static final double DISC_D = px(42); + static final double DISC_GLYPH = px(20); + static final double DISC_GAP = px(18); + + /** The party block's text sits on the label's axis, indented past the disc. */ + static final double PARTY_TEXT_INDENT = DISC_D + DISC_GAP; + static final double ISSUER_TAX_LABEL_W = px(63); + static final double PARTY_TAX_LABEL_W = px(79); + + /** Metadata labels start at the cell's edge and values 180 px along it. */ + static final double META_LABEL_W = px(180); + static final double META_CELL_INSET = px(80); + + /** + * Column shares of the content width, derived from the header labels' + * measured centres rather than from boundaries the design never draws. + */ + static final double[] COLUMN_SHARES = {0.383178, 0.176532, 0.118380, 0.172378, 0.149533}; + + static final double HEAD_PAD_V = px(18.4); + static final double HEAD_PAD_L = px(67); + + /** + * Asymmetric, measured: the design's row content sits 19 px below its top + * rule and 16 px above the next. Symmetric padding centres it and leaves the + * text about 3 px low. + */ + static final double CELL_PAD_T = px(17.0); + static final double CELL_PAD_B = px(14.0); + static final double CELL_PAD_L = px(15); + static final double ITEM_TILE_D = px(33); + static final double ITEM_TILE_RADIUS = px(8); + static final double ITEM_GLYPH = px(19); + static final double ITEM_TEXT_GAP = px(19); + + /** + * Every rule in the table comes from one of these two styles. A cell strokes + * all four of its own edges, and a one-column table has no interior vertical + * edge — so the same stroke that draws the separator between two rows draws + * the table's left and right sides, and nothing else. + */ + static final DocumentTableStyle HEADER_CELL_STYLE = DocumentTableStyle.builder() + .fillColor(ACCENT_SURFACE) + .stroke(DocumentStroke.of(ACCENT_BORDER, RULE_THIN)) + .padding(new DocumentInsets(HEAD_PAD_V, 0, HEAD_PAD_V, 0)) + .textAnchor(DocumentTableTextAnchor.CENTER_LEFT) + .build(); + + static final DocumentTableStyle ITEM_CELL_STYLE = DocumentTableStyle.builder() + .stroke(DocumentStroke.of(HAIRLINE, RULE_THIN)) + .padding(new DocumentInsets(CELL_PAD_T, 0, CELL_PAD_B, 0)) + .textAnchor(DocumentTableTextAnchor.CENTER_LEFT) + .build(); + + /** + * The settlement row is NOT the half split the two rows above it use: its + * left cell is 420 design px, its right 433, with a 110 px gutter. That + * asymmetry is in the design, not in the measurement. + */ + static final double SETTLEMENT_LEFT = px(420); + static final double SETTLEMENT_GUTTER = px(110); + static final double SETTLEMENT_RIGHT = px(433); + static final double SETTLEMENT_RIGHT_INSET = px(29); + + static final double CARD_RADIUS = px(12); + static final DocumentInsets CARD_PAD = new DocumentInsets(px(17), px(24), px(22), px(24)); + static final double CARD_LABEL_W = px(162); + + /** The card's inner width — what its interior rule spans. */ + static final double CARD_INNER_W = SETTLEMENT_LEFT - px(24) - px(24); + static final double CARD_ICON = px(27); + static final double CARD_ICON_GAP = px(21); + static final double NOTE_ICON_COL = px(30); + static final double NOTE_ICON = px(20); + + static final double TOTALS_PAD_L = px(6); + static final double TOTALS_PAD_R = px(10); + static final double TOTALS_RULE_W = px(397); + + static final double DUE_RADIUS = px(12); + static final DocumentInsets DUE_PAD = new DocumentInsets(px(25), px(24), px(28), px(23)); + static final double DUE_TILE = px(38); + static final double DUE_TILE_RADIUS = px(9); + static final double DUE_GLYPH = px(22); + static final double DUE_TILE_GAP = px(24); + + static final double THANKS_INDENT = px(6); + static final double THANKS_ICON_COL = px(46); + static final double THANKS_ICON = px(30); + + static final double FOOTER_MARK_COL = px(144); + static final double FOOTER_LINK_COL = px(150); + + /** + * Which surface a line's glyph is knocked out of. A token with no colour + * here is drawn bare, which is how the design sets a mark that carries its + * own. + */ + static final Map TILE_COLOR = Map.of( + "search", ACCENT, + "grid", TILE_BLUE, + "shield", TILE_GREEN); + + static double px(double designPixels) { + return designPixels * PX; + } + + /** A size named by the cap height it reproduces, in design px. */ + static double sizeForCap(double capPx) { + return px(capPx - CAP_INK_BLEED) / FACE_CAP_RATIO; + } + + /** The same for an all-caps run, which the face sets 6% wide. */ + static double sizeForCaps(double capPx) { + return sizeForCap(capPx) * 0.94; + } + + /** + * Where to start a text box so its caps land {@code capPx} below the top of + * the block it shares a row with — a label beside a glyph, whose design + * position is its own baseline rather than the glyph's centre. + */ + static double capOffset(double capPx, double size) { + return Math.max(0, px(capPx) - TEXT_TOP_BEARING * size); + } + + /** What a text box hangs below its last cap: the rest of the line box. */ + static double bottomBearing(double size) { + return (LINE_BOX - TEXT_TOP_BEARING - FACE_CAP_RATIO) * size; + } + + /** + * A block gap read ink-to-ink on the design, turned into the margin the + * engine wants. Pass 0 for a neighbour that is not text — a rule, a disc, a + * table — which has no bearing to lose. + */ + static double blockGap(double inkGapPx, double sizeAbove, double sizeBelow) { + return Math.max(0, px(inkGapPx) + - (sizeAbove > 0 ? bottomBearing(sizeAbove) : 0) + - (sizeBelow > 0 ? TEXT_TOP_BEARING * sizeBelow : 0)); + } + + /** + * A measured top-to-top pitch turned into the gap the engine wants: the + * first box's own height comes off it, because spacing is between boxes. + */ + static double gap(double pitchPx, double sizeOfFirst) { + return Math.max(0, px(pitchPx) - LINE_BOX * sizeOfFirst); + } + + static DocumentTextStyle style(double size, DocumentColor color, + DocumentTextDecoration decoration) { + return DocumentTextStyle.builder() + .fontName(FACE) + .size(size) + .color(color) + .decoration(decoration) + .build(); + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/WorkspaceText.java b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/WorkspaceText.java new file mode 100644 index 000000000..5111f41aa --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/WorkspaceText.java @@ -0,0 +1,98 @@ +package com.demcha.compose.document.templates.invoice.presets; + +import java.math.BigDecimal; +import java.text.NumberFormat; +import java.util.Currency; +import java.util.Locale; + +/** + * How the Workspace sheet writes its figures. + * + *

    The locale is stated, never inherited

    + * + *

    Both the grouping of a number and the symbol for a currency code are + * locale-dependent — {@code USD} is {@code $} in English and {@code US$} in the + * root locale, {@code JPY} is {@code ¥} or {@code JP¥} — so a preset that let + * the JVM's default locale decide would render a different sheet on a different + * machine, and its baselines would move with the machine rather than with the + * design. Both helpers below pin {@link Locale#ENGLISH}.

    + */ +final class WorkspaceText { + + private WorkspaceText() { + } + + /** An amount, grouped and always to two places. */ + static String money(BigDecimal value) { + NumberFormat format = NumberFormat.getNumberInstance(Locale.ENGLISH); + format.setGroupingUsed(true); + format.setMinimumFractionDigits(2); + format.setMaximumFractionDigits(2); + return format.format(value); + } + + /** A quantity, written without trailing zeros a whole number does not need. */ + static String quantity(BigDecimal value) { + BigDecimal stripped = value.stripTrailingZeros(); + return stripped.scale() <= 0 ? stripped.toBigInteger().toString() : stripped.toPlainString(); + } + + /** + * The symbol for an ISO currency code, or the code itself when there is no + * symbol for it and the empty string when a document states no currency. + * + * @param currencyCode the ISO code the figures are stated in + * @return what to print before an amount + */ + static String symbol(String currencyCode) { + if (currencyCode == null || currencyCode.isBlank()) { + return ""; + } + try { + return Currency.getInstance(currencyCode.trim()).getSymbol(Locale.ENGLISH); + } catch (IllegalArgumentException notACurrency) { + return currencyCode.trim(); + } + } + + /** + * An amount named by its currency code rather than its symbol — this design + * writes {@code USD 587.50} on the one figure it names at all. + * + * @param currencyCode the ISO code the figures are stated in + * @param value the amount + * @return the amount behind its code, or bare when no currency is stated + */ + static String coded(String currencyCode, BigDecimal value) { + return currencyCode == null || currencyCode.isBlank() + ? money(value) + : currencyCode.trim() + " " + money(value); + } + + /** + * A column label with the currency named after it, which is where this + * design states it for the figures under it. + * + * @param label the column's own words + * @param currencyCode the ISO code the figures are stated in + * @return the label, with the code in brackets when there is one + */ + static String labelWithCurrency(String label, String currencyCode) { + return currencyCode == null || currencyCode.isBlank() + ? label + : label + " (" + currencyCode.trim() + ")"; + } + + /** + * A quantity and what it counts, as the design writes them: one run, so the + * column stays centred on the pair rather than on the number. + * + * @param value the quantity + * @param unit what it counts; blank when the number stands alone + * @return the written quantity + */ + static String quantityWithUnit(BigDecimal value, String unit) { + String written = quantity(value); + return unit == null || unit.isBlank() ? written : written + " " + unit; + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/WorkspaceWidgets.java b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/WorkspaceWidgets.java new file mode 100644 index 000000000..7bb318b4b --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/invoice/presets/WorkspaceWidgets.java @@ -0,0 +1,158 @@ +package com.demcha.compose.document.templates.invoice.presets; + +import com.demcha.compose.document.dsl.PageFlowBuilder; +import com.demcha.compose.document.dsl.RowBuilder; +import com.demcha.compose.document.dsl.SectionBuilder; +import com.demcha.compose.document.dsl.ShapeContainerBuilder; +import com.demcha.compose.document.node.DocumentNode; +import com.demcha.compose.document.node.HorizontalAlign; +import com.demcha.compose.document.node.LayerAlign; +import com.demcha.compose.document.style.DocumentColor; +import com.demcha.compose.document.style.DocumentInsets; +import com.demcha.compose.document.style.DocumentTextDecoration; + +import java.util.List; +import java.util.function.Consumer; + +import static com.demcha.compose.document.templates.invoice.presets.WorkspaceStyles.ACCENT; +import static com.demcha.compose.document.templates.invoice.presets.WorkspaceStyles.CONTENT_W; +import static com.demcha.compose.document.templates.invoice.presets.WorkspaceStyles.DISC_D; +import static com.demcha.compose.document.templates.invoice.presets.WorkspaceStyles.DISC_GLYPH; +import static com.demcha.compose.document.templates.invoice.presets.WorkspaceStyles.ITEM_GLYPH; +import static com.demcha.compose.document.templates.invoice.presets.WorkspaceStyles.ITEM_TILE_D; +import static com.demcha.compose.document.templates.invoice.presets.WorkspaceStyles.ITEM_TILE_RADIUS; +import static com.demcha.compose.document.templates.invoice.presets.WorkspaceStyles.TILE_COLOR; +import static com.demcha.compose.document.templates.invoice.presets.WorkspaceStyles.style; + +/** + * The pieces more than one part of the Workspace sheet draws: the wrapper every + * horizontal pair inside a cell goes through, the disc and the tile that own + * their glyphs, and the full-width rules that separate the closing blocks. + */ +final class WorkspaceWidgets { + + private WorkspaceWidgets() { + } + + /** + * A row that survives inside a row cell. + * + *

    A row nested directly in a row cell is refused by the layout compiler. + * Every horizontal pair that lives inside a cell in this preset goes through + * here, wrapped in a single layer of a stack.

    + * + * @param parent the section to add the row to + * @param name the node name, for review and rollback + * @param spec describes the row + */ + static void layeredRow(SectionBuilder parent, String name, Consumer spec) { + SectionBuilder holder = new SectionBuilder(); + holder.name(name + "Holder"); + holder.addRow(name, spec); + DocumentNode node = holder.build(); + parent.addLayerStack(stack -> stack + .name(name + "Layer") + .layer(node, LayerAlign.TOP_LEFT, 0)); + } + + /** + * A filled disc that owns its glyph: the glyph is knocked out of the accent + * fill, so it is a child of the shape rather than a coloured circle with an + * icon dropped near it. + * + * @param token the packaged mark + * @return the disc node + */ + static DocumentNode disc(String token) { + return new ShapeContainerBuilder() + .name("Disc" + token) + .circle(DISC_D) + .fillColor(ACCENT) + .center(glyph(token, DISC_GLYPH)) + .build(); + } + + /** + * A line's mark on its coloured tile, or bare when the design gives that + * token no tile. + * + * @param token the packaged mark + * @return the tile node + */ + static DocumentNode tile(String token) { + DocumentColor fill = TILE_COLOR.get(token); + if (fill == null) { + return glyph(token, ITEM_TILE_D); + } + return new ShapeContainerBuilder() + .name("ItemTile" + token) + .roundedRect(ITEM_TILE_D, ITEM_TILE_D, ITEM_TILE_RADIUS) + .fillColor(fill) + .center(glyph(token, ITEM_GLYPH)) + .build(); + } + + /** A glyph centred in a box of its own, so a shape can own it. */ + static DocumentNode glyph(String token, double size) { + return glyph(token, size, HorizontalAlign.CENTER); + } + + /** + * The same, aligned. A glyph that leads a text row is left-aligned: its + * column's width is the distance to the text, not a box for the glyph to sit + * in the middle of. + * + * @param token the packaged mark + * @param size the glyph's box + * @param align where the glyph sits in it + * @return the glyph node + */ + static DocumentNode glyph(String token, double size, HorizontalAlign align) { + return new SectionBuilder() + .name("Glyph" + token) + .spacing(0) + .addSvgIcon(WorkspaceIcons.icon(token), size, align) + .build(); + } + + /** One method for all three full-width hairlines; the page uses two weights. */ + static void fullWidthRule(PageFlowBuilder page, String name, DocumentColor color, + double thickness, double gapAbove, double gapBelow) { + page.addLine(line -> line + .name(name) + .horizontal(CONTENT_W) + .thickness(thickness) + .color(color) + .margin(new DocumentInsets(gapAbove, 0, gapBelow, 0))); + } + + /** + * A block of plain lines, one paragraph each. + * + * @param block the section to fill + * @param prefix the node-name prefix, numbered from one + * @param lines the lines + * @param size their type size + * @param color their colour + */ + static void textLines(SectionBuilder block, String prefix, List lines, + double size, DocumentColor color) { + int index = 0; + for (String line : lines) { + String name = prefix + (++index); + block.addParagraph(p -> p + .name(name) + .text(line) + .textStyle(style(size, color, DocumentTextDecoration.DEFAULT))); + } + } + + /** Even weights for a row of {@code count} cells. */ + static double[] evenWeights(int count) { + double[] weights = new double[count]; + for (int i = 0; i < count; i++) { + weights[i] = 1.0 / count; + } + return weights; + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/proposal/presets/EditorialIcons.java b/templates/src/main/java/com/demcha/compose/document/templates/proposal/presets/EditorialIcons.java new file mode 100644 index 000000000..33a66bfd8 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/proposal/presets/EditorialIcons.java @@ -0,0 +1,66 @@ +package com.demcha.compose.document.templates.proposal.presets; + +import com.demcha.compose.document.dsl.ShapeContainerBuilder; +import com.demcha.compose.document.node.DocumentNode; +import com.demcha.compose.document.style.ClipPolicy; +import com.demcha.compose.document.svg.SvgIcon; + +import java.io.IOException; +import java.io.InputStream; +import java.io.UncheckedIOException; +import java.nio.charset.StandardCharsets; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Classpath loader for the Editorial Proposal icon set — the marks beside + * the at-a-glance facts and the check that opens every goal cell. + * + *

    They ship inside the templates artifact under + * {@code templates/proposal/editorial/icons/} as SVG, so they scale with + * the box they are drawn into rather than being resampled. Parsed icons are + * cached per token, so a two-page proposal reads each file once per JVM.

    + */ +final class EditorialIcons { + + private static final String ICON_ROOT = "/templates/proposal/editorial/icons/"; + private static final Map CACHE = new ConcurrentHashMap<>(); + + private EditorialIcons() { + } + + /** + * Builds an icon node at a given width. + * + * @param token the icon token carried by the data + * @param width the drawn width; the height follows the icon's ratio + * @return the icon node + * @throws IllegalArgumentException when the token names no packaged icon + * — an icon token is data, so a wrong one is a data error + */ + static DocumentNode icon(String token, double width) { + // Named after the token: an icon swapped in the data then reaches the + // layout snapshot as a changed node path, not just as moved pixels. + return new ShapeContainerBuilder() + .name("Icon-" + token) + .rectangle(width, width) + .clipPolicy(ClipPolicy.OVERFLOW_VISIBLE) + .centerLeft(CACHE.computeIfAbsent(token, EditorialIcons::read).node(width)) + .build(); + } + + private static SvgIcon read(String token) { + String resourcePath = ICON_ROOT + token + ".svg"; + try (InputStream input = EditorialIcons.class.getResourceAsStream(resourcePath)) { + if (input == null) { + throw new IllegalArgumentException( + "Unknown editorial proposal icon token '" + token + + "' — no packaged icon at " + resourcePath + "."); + } + return SvgIcon.parse(new String(input.readAllBytes(), StandardCharsets.UTF_8)); + } catch (IOException e) { + throw new UncheckedIOException( + "Failed to read editorial proposal icon: " + resourcePath, e); + } + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/proposal/presets/EditorialPageOne.java b/templates/src/main/java/com/demcha/compose/document/templates/proposal/presets/EditorialPageOne.java new file mode 100644 index 000000000..380322ea4 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/proposal/presets/EditorialPageOne.java @@ -0,0 +1,307 @@ +package com.demcha.compose.document.templates.proposal.presets; + +import com.demcha.compose.document.dsl.PageFlowBuilder; +import com.demcha.compose.document.dsl.SectionBuilder; +import com.demcha.compose.document.dsl.ShapeContainerBuilder; +import com.demcha.compose.document.node.DocumentNode; +import com.demcha.compose.document.node.LayerAlign; +import com.demcha.compose.document.node.RowVerticalAlign; +import com.demcha.compose.document.style.ClipPolicy; +import com.demcha.compose.document.style.DocumentInsets; +import com.demcha.compose.document.style.DocumentRowColumn; +import com.demcha.compose.document.style.DocumentStroke; +import com.demcha.compose.document.templates.data.proposal.ProposalGlance; +import com.demcha.compose.document.templates.data.proposal.ProposalGoals; +import com.demcha.compose.document.templates.data.proposal.ProposalScope; +import com.demcha.compose.document.templates.data.proposal.ProposalSummaryBlock; +import com.demcha.compose.document.templates.data.proposal.StructuredProposalData; + +import java.util.List; + +import static com.demcha.compose.document.templates.proposal.presets.EditorialStyles.BAND_TOP; +import static com.demcha.compose.document.templates.proposal.presets.EditorialStyles.BODY; +import static com.demcha.compose.document.templates.proposal.presets.EditorialStyles.BODY_LEADING; +import static com.demcha.compose.document.templates.proposal.presets.EditorialStyles.CONTENT_WIDTH; +import static com.demcha.compose.document.templates.proposal.presets.EditorialStyles.FACT_DIVIDER_GAP; +import static com.demcha.compose.document.templates.proposal.presets.EditorialStyles.FACT_GAP; +import static com.demcha.compose.document.templates.proposal.presets.EditorialStyles.FACT_ICON; +import static com.demcha.compose.document.templates.proposal.presets.EditorialStyles.FACT_LABEL; +import static com.demcha.compose.document.templates.proposal.presets.EditorialStyles.FACT_NOTE; +import static com.demcha.compose.document.templates.proposal.presets.EditorialStyles.FACT_VALUE; +import static com.demcha.compose.document.templates.proposal.presets.EditorialStyles.FACT_VALUE_SIZE; +import static com.demcha.compose.document.templates.proposal.presets.EditorialStyles.GAP_AFTER_RULE; +import static com.demcha.compose.document.templates.proposal.presets.EditorialStyles.GAP_BEFORE_GOALS_HEADING; +import static com.demcha.compose.document.templates.proposal.presets.EditorialStyles.GAP_META; +import static com.demcha.compose.document.templates.proposal.presets.EditorialStyles.GAP_PARAGRAPH; +import static com.demcha.compose.document.templates.proposal.presets.EditorialStyles.GAP_TITLE; +import static com.demcha.compose.document.templates.proposal.presets.EditorialStyles.GLANCE_WEIGHT; +import static com.demcha.compose.document.templates.proposal.presets.EditorialStyles.GLANCE_WIDTH; +import static com.demcha.compose.document.templates.proposal.presets.EditorialStyles.GOAL_GAP; +import static com.demcha.compose.document.templates.proposal.presets.EditorialStyles.GOAL_ICON; +import static com.demcha.compose.document.templates.proposal.presets.EditorialStyles.GOAL_TEXT; +import static com.demcha.compose.document.templates.proposal.presets.EditorialStyles.GOAL_TEXT_LINES; +import static com.demcha.compose.document.templates.proposal.presets.EditorialStyles.HAIRLINE; +import static com.demcha.compose.document.templates.proposal.presets.EditorialStyles.HEADING_CAP_LEAD; +import static com.demcha.compose.document.templates.proposal.presets.EditorialStyles.HEADING_SIZE; +import static com.demcha.compose.document.templates.proposal.presets.EditorialStyles.LABEL_SIZE; +import static com.demcha.compose.document.templates.proposal.presets.EditorialStyles.META; +import static com.demcha.compose.document.templates.proposal.presets.EditorialStyles.META_SEPARATOR; +import static com.demcha.compose.document.templates.proposal.presets.EditorialStyles.PANEL_BACKGROUND; +import static com.demcha.compose.document.templates.proposal.presets.EditorialStyles.PANEL_PADDING; +import static com.demcha.compose.document.templates.proposal.presets.EditorialStyles.PANEL_RADIUS; +import static com.demcha.compose.document.templates.proposal.presets.EditorialStyles.RULE; +import static com.demcha.compose.document.templates.proposal.presets.EditorialStyles.RULE_QUIET; +import static com.demcha.compose.document.templates.proposal.presets.EditorialStyles.SCOPE_BODY; +import static com.demcha.compose.document.templates.proposal.presets.EditorialStyles.SCOPE_DESC_WEIGHT; +import static com.demcha.compose.document.templates.proposal.presets.EditorialStyles.SCOPE_GAP; +import static com.demcha.compose.document.templates.proposal.presets.EditorialStyles.SCOPE_LEADING; +import static com.demcha.compose.document.templates.proposal.presets.EditorialStyles.SCOPE_NUMBER; +import static com.demcha.compose.document.templates.proposal.presets.EditorialStyles.SCOPE_NUMBER_WIDTH; +import static com.demcha.compose.document.templates.proposal.presets.EditorialStyles.SCOPE_ROW_PAD; +import static com.demcha.compose.document.templates.proposal.presets.EditorialStyles.SCOPE_TITLE; +import static com.demcha.compose.document.templates.proposal.presets.EditorialStyles.SCOPE_TITLE_SPLIT; +import static com.demcha.compose.document.templates.proposal.presets.EditorialStyles.SUMMARY_GAP; +import static com.demcha.compose.document.templates.proposal.presets.EditorialStyles.SUMMARY_WEIGHT; +import static com.demcha.compose.document.templates.proposal.presets.EditorialStyles.TABLE_SIZE; +import static com.demcha.compose.document.templates.proposal.presets.EditorialStyles.TITLE_ACCENT; +import static com.demcha.compose.document.templates.proposal.presets.EditorialStyles.TITLE_INK; +import static com.demcha.compose.document.templates.proposal.presets.EditorialStyles.TITLE_PITCH; +import static com.demcha.compose.document.templates.proposal.presets.EditorialStyles.TITLE_SIZE; +import static com.demcha.compose.document.templates.proposal.presets.EditorialStyles.lineHeight; +import static com.demcha.compose.document.templates.proposal.presets.EditorialWidgets.sectionHeading; +import static com.demcha.compose.document.templates.proposal.presets.EditorialWidgets.stackedLines; + +/** + * Page one of the Editorial Proposal: the stacked title, the meta line, the + * summary beside the fact card, the goal cells, and the numbered scope list. + */ +final class EditorialPageOne { + + private EditorialPageOne() { + } + + static void compose(PageFlowBuilder page, StructuredProposalData data) { + renderTitleBlock(page, data); + renderTitleMeta(page, data); + renderSummaryBand(page, data); + renderGoalCells(page, data.goals()); + renderScopeOfWork(page, data.scope()); + } + + /** The three title lines, stacked on the reference's own pitch. */ + private static void renderTitleBlock(PageFlowBuilder page, StructuredProposalData data) { + page.addSection("TitleBlock", block -> { + block.margin(GAP_TITLE, 0f, 0f, 0f); + block.spacing(0); + block.add(stackedLines("TitleLines", CONTENT_WIDTH, TITLE_PITCH, TITLE_SIZE, + List.of(data.title().lead(), data.title().second(), data.title().third()), + List.of(TITLE_INK, TITLE_ACCENT, TITLE_ACCENT))); + }); + } + + /** Prepared-for, prepared-by and date, split by accent pipes. */ + private static void renderTitleMeta(PageFlowBuilder page, StructuredProposalData data) { + page.addSection("TitleMeta", section -> { + section.margin(GAP_META, 0f, 0f, 0f); + section.addParagraph(p -> p + .name("MetaLine") + .inlineText(data.meta().preparedFor(), META) + .inlineText(" | ", META_SEPARATOR) + .inlineText(data.meta().preparedBy(), META) + .inlineText(" | ", META_SEPARATOR) + .inlineText(data.meta().date(), META)); + }); + } + + /** The two-column band: summary and the goals heading left, fact card right. */ + private static void renderSummaryBand(PageFlowBuilder page, StructuredProposalData data) { + page.addRow("SummaryBand", row -> { + row.margin(DocumentInsets.top(BAND_TOP)); + row.verticalAlign(RowVerticalAlign.TOP) + .gap(SUMMARY_GAP) + .weights(SUMMARY_WEIGHT, GLANCE_WEIGHT); + row.addSection("SummaryColumn", column -> { + renderExecutiveSummary(column, data.executiveSummary()); + column.addSpacer(spacer -> spacer.height(GAP_BEFORE_GOALS_HEADING)); + column.add(sectionHeading(data.goals().heading())); + }); + row.addSection("GlanceColumn", column -> { + // The card's top edge lines up with the heading's CAP top, not + // with its line box, which starts HEADING_CAP_LEAD of the type + // size higher. Both children start at the band's top, so the + // card is padded down by the difference — stated once here + // rather than baked into BAND_TOP, which every band follows. + column.padding((float) (HEADING_SIZE * HEADING_CAP_LEAD - 1.6), 0f, 0f, 0f); + renderGlancePanel(column, data.glance()); + }); + }); + } + + private static void renderExecutiveSummary(SectionBuilder column, + ProposalSummaryBlock summary) { + column.spacing(0); + column.add(sectionHeading(summary.heading())); + List paragraphs = summary.paragraphs(); + for (int i = 0; i < paragraphs.size(); i++) { + String text = paragraphs.get(i); + float top = i == 0 ? GAP_AFTER_RULE : GAP_PARAGRAPH; + column.addParagraph(p -> p + .text(text) + .textStyle(BODY) + .lineSpacing(BODY_LEADING) + .margin(top, 0f, 0f, 0f)); + } + } + + /** + * The fact card. Unlike its teal sibling the card is untitled — the + * data's glance heading is not drawn — and its facts are divided by + * quiet rules inside a stroked panel. + */ + private static void renderGlancePanel(SectionBuilder column, ProposalGlance glance) { + List facts = glance.facts(); + double inner = GLANCE_WIDTH - 2 * PANEL_PADDING; + column.addSection("GlancePanel", panel -> { + panel.spacing(0).softPanel(PANEL_BACKGROUND, PANEL_RADIUS, PANEL_PADDING, + DocumentStroke.of(RULE_QUIET, HAIRLINE)); + for (int i = 0; i < facts.size(); i++) { + panel.add(renderGlanceFact(facts.get(i), inner)); + if (i < facts.size() - 1) { + panel.addLine(line -> line + .fill() + .thickness(HAIRLINE) + .color(RULE_QUIET) + .margin(DocumentInsets.symmetric(FACT_DIVIDER_GAP, 0))); + } + } + }); + } + + /** + * One fact: the icon anchored left of a label-over-value stack. The + * container's height derives from the lines the fact actually has, so an + * optional note makes its own room. + */ + private static DocumentNode renderGlanceFact(ProposalGlance.Fact fact, double width) { + boolean hasNote = !fact.note().isBlank(); + double height = lineHeight(LABEL_SIZE) + + lineHeight(FACT_VALUE_SIZE) + + (hasNote ? lineHeight(LABEL_SIZE + 0.5) : 0); + + SectionBuilder text = new SectionBuilder(); + text.name("FactText"); + text.spacing(0); + text.addParagraph(p -> p.text(fact.label()).textStyle(FACT_LABEL)); + text.addParagraph(p -> p.text(fact.value()).textStyle(FACT_VALUE)); + if (hasNote) { + text.addParagraph(p -> p.text(fact.note()).textStyle(FACT_NOTE)); + } + + ShapeContainerBuilder container = new ShapeContainerBuilder() + .name("GlanceFact") + .rectangle(width, height) + .clipPolicy(ClipPolicy.OVERFLOW_VISIBLE); + if (!fact.icon().isBlank()) { + container.position(EditorialIcons.icon(fact.icon(), FACT_ICON), + 0, 0, LayerAlign.CENTER_LEFT); + } + return container + .position(text.build(), FACT_ICON + FACT_GAP, 0, LayerAlign.CENTER_LEFT) + .build(); + } + + /** + * The goal cells, flattened into one row: the engine refuses a row inside + * a row cell, so the column widths carry the structure. A cell opens with + * the goal's own icon token, falling back to the band's — which is how + * this design marks every cell alike, since its goals name no token. + */ + private static void renderGoalCells(PageFlowBuilder page, ProposalGoals goals) { + List items = goals.items(); + if (items.isEmpty()) { + return; + } + double dividerHeight = GOAL_TEXT_LINES * lineHeight(TABLE_SIZE); + page.addRow("GoalCells", row -> { + row.margin(DocumentInsets.top(BAND_TOP)); + row.verticalAlign(RowVerticalAlign.TOP).gap(GOAL_GAP); + row.columns(goalColumns(items.size())); + for (int i = 0; i < items.size(); i++) { + if (i > 0) { + row.addLine(line -> line + .vertical(dividerHeight) + .thickness(HAIRLINE) + .color(RULE)); + } + ProposalGoals.Goal goal = items.get(i); + // The goal's own token when it carries one; the band's token + // otherwise, which is how this design marks every cell the + // same. A goal that names neither keeps the column mapping of + // the flattened row with a spacer. + String mark = goal.icon().isBlank() ? goals.icon() : goal.icon(); + if (mark.isBlank()) { + row.addSpacer(GOAL_ICON); + } else { + row.add(EditorialIcons.icon(mark, GOAL_ICON)); + } + row.addParagraph(p -> p + .text(goal.text()) + .textStyle(GOAL_TEXT) + .lineSpacing(BODY_LEADING)); + } + }); + } + + private static DocumentRowColumn[] goalColumns(int goalCount) { + DocumentRowColumn[] columns = new DocumentRowColumn[goalCount * 2 + (goalCount - 1)]; + int at = 0; + for (int i = 0; i < goalCount; i++) { + if (i > 0) { + columns[at++] = DocumentRowColumn.fixed(HAIRLINE); + } + columns[at++] = DocumentRowColumn.fixed(GOAL_ICON); + columns[at++] = DocumentRowColumn.weight(1); + } + return columns; + } + + /** + * The numbered scope list: rows plus hairlines. The ordinal is plain + * accent text — the teal sibling encloses the same number in a pill. + */ + private static void renderScopeOfWork(PageFlowBuilder page, ProposalScope scope) { + double titleWidth = + CONTENT_WIDTH * SCOPE_TITLE_SPLIT - SCOPE_NUMBER_WIDTH - 2 * SCOPE_GAP; + page.addSection("ScopeOfWork", section -> { + section.margin(BAND_TOP, 0f, 0f, 0f); + section.spacing(0); + section.add(sectionHeading(scope.heading())); + section.addLine(line -> line + .fill() + .thickness(HAIRLINE) + .color(RULE) + .margin(DocumentInsets.top(GAP_AFTER_RULE))); + List items = scope.items(); + for (int i = 0; i < items.size(); i++) { + ProposalScope.Item item = items.get(i); + section.addRow("ScopeRow" + item.number(), row -> { + row.verticalAlign(RowVerticalAlign.TOP) + .gap(SCOPE_GAP) + .padding(DocumentInsets.symmetric(SCOPE_ROW_PAD, 0)) + .columns(DocumentRowColumn.fixed(SCOPE_NUMBER_WIDTH), + DocumentRowColumn.fixed(titleWidth), + DocumentRowColumn.fixed(CONTENT_WIDTH * SCOPE_DESC_WEIGHT)); + row.addParagraph(p -> p.text(item.number()).textStyle(SCOPE_NUMBER)); + row.addParagraph(p -> p.text(item.title()).textStyle(SCOPE_TITLE)); + row.addParagraph(p -> p + .text(item.description()) + .textStyle(SCOPE_BODY) + .lineSpacing(SCOPE_LEADING)); + }); + if (i < items.size() - 1) { + section.addLine(line -> line.fill().thickness(HAIRLINE).color(RULE)); + } + } + }); + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/proposal/presets/EditorialPageTwo.java b/templates/src/main/java/com/demcha/compose/document/templates/proposal/presets/EditorialPageTwo.java new file mode 100644 index 000000000..a99370f96 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/proposal/presets/EditorialPageTwo.java @@ -0,0 +1,360 @@ +package com.demcha.compose.document.templates.proposal.presets; + +import com.demcha.compose.document.dsl.PageFlowBuilder; +import com.demcha.compose.document.dsl.ParagraphBuilder; +import com.demcha.compose.document.dsl.RowBuilder; +import com.demcha.compose.document.dsl.SectionBuilder; +import com.demcha.compose.document.node.DocumentNode; +import com.demcha.compose.document.node.RowVerticalAlign; +import com.demcha.compose.document.style.DocumentColor; +import com.demcha.compose.document.style.DocumentInsets; +import com.demcha.compose.document.style.DocumentRowColumn; +import com.demcha.compose.document.style.DocumentStroke; +import com.demcha.compose.document.style.DocumentTextStyle; +import com.demcha.compose.document.table.DocumentTableCell; +import com.demcha.compose.document.table.DocumentTableColumn; +import com.demcha.compose.document.table.DocumentTableStyle; +import com.demcha.compose.document.table.DocumentTableTextAnchor; +import com.demcha.compose.document.templates.data.proposal.ProposalAcceptance; +import com.demcha.compose.document.templates.data.proposal.ProposalDeliverables; +import com.demcha.compose.document.templates.data.proposal.ProposalInvestment; +import com.demcha.compose.document.templates.data.proposal.ProposalPhaseGrid; +import com.demcha.compose.document.templates.data.proposal.ProposalTermsBlock; +import com.demcha.compose.document.templates.data.proposal.StructuredProposalData; + +import java.util.List; + +import static com.demcha.compose.document.templates.proposal.presets.EditorialStyles.ACCENT; +import static com.demcha.compose.document.templates.proposal.presets.EditorialStyles.ACCEPTANCE_FIELD_GAP; +import static com.demcha.compose.document.templates.proposal.presets.EditorialStyles.ACCEPTANCE_PADDING; +import static com.demcha.compose.document.templates.proposal.presets.EditorialStyles.ACCEPTANCE_SIDE_PADDING; +import static com.demcha.compose.document.templates.proposal.presets.EditorialStyles.BAND_TOP; +import static com.demcha.compose.document.templates.proposal.presets.EditorialStyles.BODY; +import static com.demcha.compose.document.templates.proposal.presets.EditorialStyles.BODY_LEADING; +import static com.demcha.compose.document.templates.proposal.presets.EditorialStyles.CONTENT_WIDTH; +import static com.demcha.compose.document.templates.proposal.presets.EditorialStyles.DELIVERABLES_COLUMN_WEIGHT; +import static com.demcha.compose.document.templates.proposal.presets.EditorialStyles.DELIVERABLES_LEFT_WEIGHT; +import static com.demcha.compose.document.templates.proposal.presets.EditorialStyles.GAP_AFTER_RULE; +import static com.demcha.compose.document.templates.proposal.presets.EditorialStyles.GAP_BEFORE_TABLE; +import static com.demcha.compose.document.templates.proposal.presets.EditorialStyles.GAP_BULLET; +import static com.demcha.compose.document.templates.proposal.presets.EditorialStyles.GAP_PAGE_TWO_OPENING; +import static com.demcha.compose.document.templates.proposal.presets.EditorialStyles.HAIRLINE; +import static com.demcha.compose.document.templates.proposal.presets.EditorialStyles.INK; +import static com.demcha.compose.document.templates.proposal.presets.EditorialStyles.INVESTMENT_EMPHASIS_PAD; +import static com.demcha.compose.document.templates.proposal.presets.EditorialStyles.INVESTMENT_LABEL_SPLIT; +import static com.demcha.compose.document.templates.proposal.presets.EditorialStyles.INVESTMENT_ROW_PAD; +import static com.demcha.compose.document.templates.proposal.presets.EditorialStyles.INVESTMENT_TOTAL_PAD; +import static com.demcha.compose.document.templates.proposal.presets.EditorialStyles.INVESTMENT_WEIGHT; +import static com.demcha.compose.document.templates.proposal.presets.EditorialStyles.INVESTMENT_WIDTH; +import static com.demcha.compose.document.templates.proposal.presets.EditorialStyles.MONEY_GAP; +import static com.demcha.compose.document.templates.proposal.presets.EditorialStyles.PANEL_BACKGROUND; +import static com.demcha.compose.document.templates.proposal.presets.EditorialStyles.PANEL_RADIUS; +import static com.demcha.compose.document.templates.proposal.presets.EditorialStyles.RULE; +import static com.demcha.compose.document.templates.proposal.presets.EditorialStyles.RULE_QUIET; +import static com.demcha.compose.document.templates.proposal.presets.EditorialStyles.SIGNATURE_LABEL; +import static com.demcha.compose.document.templates.proposal.presets.EditorialStyles.SIGNATURE_LABEL_GAP; +import static com.demcha.compose.document.templates.proposal.presets.EditorialStyles.SIGNATURE_RULE_WEIGHTS; +import static com.demcha.compose.document.templates.proposal.presets.EditorialStyles.SIGNATURE_SPACER; +import static com.demcha.compose.document.templates.proposal.presets.EditorialStyles.SUBTOTAL_FILL; +import static com.demcha.compose.document.templates.proposal.presets.EditorialStyles.TABLE_BODY; +import static com.demcha.compose.document.templates.proposal.presets.EditorialStyles.TABLE_BODY_BOLD; +import static com.demcha.compose.document.templates.proposal.presets.EditorialStyles.TABLE_CELL_INSET; +import static com.demcha.compose.document.templates.proposal.presets.EditorialStyles.TABLE_HEADER; +import static com.demcha.compose.document.templates.proposal.presets.EditorialStyles.TERMS_WEIGHT; +import static com.demcha.compose.document.templates.proposal.presets.EditorialStyles.TIMELINE_ROW_PAD; +import static com.demcha.compose.document.templates.proposal.presets.EditorialStyles.TIMELINE_WEIGHTS; +import static com.demcha.compose.document.templates.proposal.presets.EditorialStyles.TOTAL_TEXT; +import static com.demcha.compose.document.templates.proposal.presets.EditorialWidgets.renderBullet; +import static com.demcha.compose.document.templates.proposal.presets.EditorialWidgets.sectionHeading; + +/** + * Page two of the Editorial Proposal: the deliverable columns, the phase + * grid, the investment / terms band, and the signing card. + */ +final class EditorialPageTwo { + + private EditorialPageTwo() { + } + + static void compose(PageFlowBuilder page, StructuredProposalData data) { + renderDeliverables(page, data.deliverables()); + renderTimelineTable(page, data.timeline()); + renderMoneyBand(page, data); + renderAcceptance(page, data.acceptance()); + } + + /** Heading plus two bullet columns of measured width. */ + private static void renderDeliverables(PageFlowBuilder page, + ProposalDeliverables deliverables) { + page.addSection("Deliverables", section -> { + section.margin(GAP_PAGE_TWO_OPENING, 0f, 0f, 0f); + section.spacing(0); + section.add(sectionHeading(deliverables.heading())); + section.addRow("DeliverableColumns", row -> { + row.verticalAlign(RowVerticalAlign.TOP) + .gap(0) + .padding(DocumentInsets.top(GAP_AFTER_RULE)) + // Fixed, not weights: weights would divide the whole + // band between the two columns and neither would wrap + // where the reference wraps. The left column places the + // right one; the right one is its own measure, and the + // rest of the band stays empty as the reference leaves it. + .columns(DocumentRowColumn.fixed( + CONTENT_WIDTH * DELIVERABLES_LEFT_WEIGHT), + DocumentRowColumn.fixed( + CONTENT_WIDTH * DELIVERABLES_COLUMN_WEIGHT)); + row.addSection("DeliverablesLeft", column -> { + column.spacing(GAP_BULLET); + deliverables.leftColumn().forEach(item -> renderBullet(column, item)); + }); + row.addSection("DeliverablesRight", column -> { + column.spacing(GAP_BULLET); + deliverables.rightColumn().forEach(item -> renderBullet(column, item)); + }); + }); + }); + } + + /** + * The phase grid. Requires one authored header per rendered column, and + * is skipped entirely when the data carries no phases. + * + *

    The two single-line columns are centred in their row rather than + * sat on its first line; the prose columns keep TOP because they are + * what makes the row tall, so centring them would be a no-op.

    + */ + private static void renderTimelineTable(PageFlowBuilder page, ProposalPhaseGrid timeline) { + if (timeline.phases().isEmpty()) { + return; + } + List headers = timeline.columnHeaders(); + if (headers.size() != TIMELINE_WEIGHTS.length) { + throw new IllegalArgumentException( + "The phase grid renders " + TIMELINE_WEIGHTS.length + + " columns and needs exactly one header per column; " + + "columnHeaders carries " + headers.size() + "."); + } + DocumentTableStyle headerStyle = DocumentTableStyle.builder() + .fillColor(INK) + .stroke(DocumentStroke.of(INK, HAIRLINE)) + .padding(DocumentInsets.symmetric(7, TABLE_CELL_INSET)) + .textStyle(TABLE_HEADER) + .textAnchor(DocumentTableTextAnchor.CENTER_LEFT) + .build(); + DocumentTableStyle cellStyle = DocumentTableStyle.builder() + .stroke(DocumentStroke.of(RULE, HAIRLINE)) + .padding(DocumentInsets.symmetric(TIMELINE_ROW_PAD, TABLE_CELL_INSET)) + .textStyle(TABLE_BODY) + .textAnchor(DocumentTableTextAnchor.TOP_LEFT) + .lineSpacing(BODY_LEADING) + .build(); + DocumentTableStyle centredCellStyle = DocumentTableStyle.builder() + .stroke(DocumentStroke.of(RULE, HAIRLINE)) + .padding(DocumentInsets.symmetric(TIMELINE_ROW_PAD, TABLE_CELL_INSET)) + .textStyle(TABLE_BODY) + .textAnchor(DocumentTableTextAnchor.CENTER_LEFT) + .build(); + + page.addSection("Timeline", section -> { + section.margin(BAND_TOP, 0f, 0f, 0f); + section.spacing(0); + section.add(sectionHeading(timeline.heading())); + section.addTable(table -> { + table.name("TimelineTable") + .width(CONTENT_WIDTH) + .margin(DocumentInsets.top(GAP_BEFORE_TABLE)) + .columns(DocumentTableColumn.fixed(CONTENT_WIDTH * TIMELINE_WEIGHTS[0]), + DocumentTableColumn.fixed(CONTENT_WIDTH * TIMELINE_WEIGHTS[1]), + DocumentTableColumn.fixed(CONTENT_WIDTH * TIMELINE_WEIGHTS[2]), + DocumentTableColumn.fixed(CONTENT_WIDTH * TIMELINE_WEIGHTS[3])) + .defaultCellStyle(cellStyle); + table.headerCells( + DocumentTableCell.text(headers.get(0)).withStyle(headerStyle), + DocumentTableCell.text(headers.get(1)).withStyle(headerStyle), + DocumentTableCell.text(headers.get(2)).withStyle(headerStyle), + DocumentTableCell.text(headers.get(3)).withStyle(headerStyle)); + for (ProposalPhaseGrid.Phase phase : timeline.phases()) { + table.rowCells( + DocumentTableCell.text(phase.number() + " " + phase.name()) + .withStyle(centredCellStyle), + DocumentTableCell.node(wrappingCell(phase.focus())) + .withStyle(cellStyle), + DocumentTableCell.text(phase.duration()) + .withStyle(centredCellStyle), + DocumentTableCell.node(wrappingCell(phase.output())) + .withStyle(cellStyle)); + } + table.repeatHeader(); + }); + }); + } + + /** A table cell that has to wrap: a composed paragraph wraps to its column. */ + private static DocumentNode wrappingCell(String text) { + return new ParagraphBuilder() + .name("Cell") + .text(text) + .textStyle(TABLE_BODY) + .lineSpacing(BODY_LEADING) + .build(); + } + + /** The two-column money band: the priced table left, the terms right. */ + private static void renderMoneyBand(PageFlowBuilder page, StructuredProposalData data) { + page.addRow("MoneyBand", row -> { + row.margin(DocumentInsets.top(BAND_TOP)); + row.verticalAlign(RowVerticalAlign.TOP) + .gap(MONEY_GAP) + .weights(INVESTMENT_WEIGHT, TERMS_WEIGHT); + row.addSection("InvestmentColumn", + column -> renderInvestmentTable(column, data.investment())); + row.addSection("TermsColumn", column -> renderTerms(column, data.terms())); + }); + } + + /** The priced block; the total band closes it in the accent. */ + private static void renderInvestmentTable(SectionBuilder column, + ProposalInvestment investment) { + double labelWidth = INVESTMENT_WIDTH * INVESTMENT_LABEL_SPLIT; + double amountWidth = INVESTMENT_WIDTH - labelWidth; + + DocumentTableStyle headerLabel = investmentStyle(INK, TABLE_HEADER, + DocumentTableTextAnchor.CENTER_LEFT, INVESTMENT_ROW_PAD + 1.5, INK); + DocumentTableStyle headerAmount = investmentStyle(INK, TABLE_HEADER, + DocumentTableTextAnchor.CENTER_RIGHT, INVESTMENT_ROW_PAD + 1.5, INK); + DocumentTableStyle rowLabel = investmentStyle(null, TABLE_BODY, + DocumentTableTextAnchor.CENTER_LEFT, INVESTMENT_ROW_PAD, RULE_QUIET); + DocumentTableStyle rowAmount = investmentStyle(null, TABLE_BODY, + DocumentTableTextAnchor.CENTER_RIGHT, INVESTMENT_ROW_PAD, RULE_QUIET); + DocumentTableStyle subtotalLabel = investmentStyle(SUBTOTAL_FILL, TABLE_BODY_BOLD, + DocumentTableTextAnchor.CENTER_LEFT, INVESTMENT_EMPHASIS_PAD, RULE_QUIET); + DocumentTableStyle subtotalAmount = investmentStyle(SUBTOTAL_FILL, TABLE_BODY_BOLD, + DocumentTableTextAnchor.CENTER_RIGHT, INVESTMENT_EMPHASIS_PAD, RULE_QUIET); + DocumentTableStyle optionalAmount = investmentStyle(null, TABLE_BODY_BOLD, + DocumentTableTextAnchor.CENTER_RIGHT, INVESTMENT_ROW_PAD, RULE_QUIET); + DocumentTableStyle totalLabel = investmentStyle(ACCENT, TOTAL_TEXT, + DocumentTableTextAnchor.CENTER_LEFT, INVESTMENT_TOTAL_PAD, ACCENT); + DocumentTableStyle totalAmount = investmentStyle(ACCENT, TOTAL_TEXT, + DocumentTableTextAnchor.CENTER_RIGHT, INVESTMENT_TOTAL_PAD, ACCENT); + + column.spacing(0); + column.add(sectionHeading(investment.heading())); + column.addTable(table -> { + table.name("InvestmentTable") + .width(INVESTMENT_WIDTH) + .margin(DocumentInsets.top(GAP_BEFORE_TABLE)) + .columns(DocumentTableColumn.fixed(labelWidth), + DocumentTableColumn.fixed(amountWidth)) + .defaultCellStyle(rowLabel); + table.headerCells( + DocumentTableCell.text(investment.itemHeader()).withStyle(headerLabel), + DocumentTableCell.text(investment.amountHeader()).withStyle(headerAmount)); + for (ProposalInvestment.Row line : investment.rows()) { + switch (line.role()) { + case SUBTOTAL -> table.rowCells( + DocumentTableCell.text(line.label()).withStyle(subtotalLabel), + DocumentTableCell.text(line.amount()).withStyle(subtotalAmount)); + case OPTIONAL -> table.rowCells( + DocumentTableCell.text(line.label()).withStyle(rowLabel), + DocumentTableCell.text(line.amount()).withStyle(optionalAmount)); + default -> table.rowCells( + DocumentTableCell.text(line.label()).withStyle(rowLabel), + DocumentTableCell.text(line.amount()).withStyle(rowAmount)); + } + } + table.rowCells( + DocumentTableCell.text(investment.totalLabel()).withStyle(totalLabel), + DocumentTableCell.text(investment.totalAmount()).withStyle(totalAmount)); + table.repeatHeader(); + }); + } + + /** One investment cell style; {@code fill} may be null for the plain rows. */ + private static DocumentTableStyle investmentStyle(DocumentColor fill, + DocumentTextStyle text, + DocumentTableTextAnchor anchor, + double verticalPad, + DocumentColor strokeColor) { + DocumentTableStyle.Builder builder = DocumentTableStyle.builder() + .stroke(DocumentStroke.of(strokeColor, HAIRLINE)) + .padding(DocumentInsets.symmetric(verticalPad, TABLE_CELL_INSET)) + .textStyle(text) + .textAnchor(anchor); + if (fill != null) { + builder.fillColor(fill); + } + return builder.build(); + } + + /** Heading plus the bulleted terms, reusing the deliverables bullet. */ + private static void renderTerms(SectionBuilder column, ProposalTermsBlock terms) { + column.spacing(0); + column.add(sectionHeading(terms.heading())); + column.addSection("TermsList", list -> { + list.spacing(GAP_BULLET).padding(GAP_AFTER_RULE, 0f, 0f, 0f); + terms.items().forEach(item -> renderBullet(list, item)); + }); + } + + /** + * The signing card. {@code softPanel} carries one padding for all four + * sides, so the wider horizontal inset the reference uses belongs to the + * body section inside it. + */ + private static void renderAcceptance(PageFlowBuilder page, ProposalAcceptance acceptance) { + double side = ACCEPTANCE_SIDE_PADDING - ACCEPTANCE_PADDING; + page.addSection("Acceptance", card -> { + card.margin(BAND_TOP, 0f, 0f, 0f); + card.spacing(0).keepTogether().softPanel(PANEL_BACKGROUND, PANEL_RADIUS, + ACCEPTANCE_PADDING, DocumentStroke.of(RULE, HAIRLINE)); + card.addSection("AcceptanceBody", body -> { + body.spacing(0).padding(0f, (float) side, 0f, (float) side); + body.add(sectionHeading(acceptance.heading())); + body.addParagraph(p -> p + .text(acceptance.statement()) + .textStyle(BODY) + .lineSpacing(BODY_LEADING) + .margin(GAP_AFTER_RULE, 0f, 0f, 0f)); + if (!acceptance.fields().isEmpty()) { + body.addRow("SignatureFields", + row -> renderSignatureFields(row, acceptance.fields())); + } + }); + }); + } + + /** label, rule, spacer, label, rule, spacer, label, rule. */ + private static void renderSignatureFields(RowBuilder row, List fields) { + DocumentRowColumn[] columns = new DocumentRowColumn[fields.size() * 3 - 1]; + int at = 0; + for (int i = 0; i < fields.size(); i++) { + if (i > 0) { + columns[at++] = DocumentRowColumn.fixed(SIGNATURE_SPACER); + } + columns[at++] = DocumentRowColumn.auto(); + columns[at++] = DocumentRowColumn.weight(signatureRuleWeight(i, fields.size())); + } + row.verticalAlign(RowVerticalAlign.BOTTOM) + .gap(SIGNATURE_LABEL_GAP) + .padding(DocumentInsets.top(ACCEPTANCE_FIELD_GAP)) + .columns(columns); + for (int i = 0; i < fields.size(); i++) { + if (i > 0) { + row.addSpacer(SIGNATURE_SPACER); + } + String field = fields.get(i); + row.addParagraph(p -> p.text(field).textStyle(SIGNATURE_LABEL)); + row.addLine(line -> line.fill().thickness(HAIRLINE).color(INK)); + } + } + + /** + * The measured rule widths apply to the three-field card the reference + * sets; any other count divides the row evenly. + */ + private static double signatureRuleWeight(int index, int fieldCount) { + return fieldCount == SIGNATURE_RULE_WEIGHTS.length + ? SIGNATURE_RULE_WEIGHTS[index] + : 1.0 / fieldCount; + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/proposal/presets/EditorialProposal.java b/templates/src/main/java/com/demcha/compose/document/templates/proposal/presets/EditorialProposal.java new file mode 100644 index 000000000..1ecc0b218 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/proposal/presets/EditorialProposal.java @@ -0,0 +1,162 @@ +package com.demcha.compose.document.templates.proposal.presets; + +import com.demcha.compose.document.api.DocumentSession; +import com.demcha.compose.document.api.PageBackgroundFill; +import com.demcha.compose.document.output.DocumentHeaderFooter; +import com.demcha.compose.document.output.DocumentHeaderFooterZone; +import com.demcha.compose.document.style.DocumentInsets; +import com.demcha.compose.document.templates.api.DocumentTemplate; +import com.demcha.compose.document.templates.data.proposal.ProposalBrand; +import com.demcha.compose.document.templates.data.proposal.StructuredProposalData; +import com.demcha.compose.document.templates.data.proposal.StructuredProposalDocumentSpec; + +import java.util.List; +import java.util.Objects; + +import static com.demcha.compose.document.templates.proposal.presets.EditorialStyles.CONTENT_WIDTH; +import static com.demcha.compose.document.templates.proposal.presets.EditorialStyles.FOOTER_BAND; +import static com.demcha.compose.document.templates.proposal.presets.EditorialStyles.FOOTER_RULE_Y; +import static com.demcha.compose.document.templates.proposal.presets.EditorialStyles.FOOTER_TEXT_SIZE; +import static com.demcha.compose.document.templates.proposal.presets.EditorialStyles.FOOTER_ZONE_HEIGHT; +import static com.demcha.compose.document.templates.proposal.presets.EditorialStyles.HAIRLINE; +import static com.demcha.compose.document.templates.proposal.presets.EditorialStyles.INK; +import static com.demcha.compose.document.templates.proposal.presets.EditorialStyles.MARGIN_SIDE; +import static com.demcha.compose.document.templates.proposal.presets.EditorialStyles.MARGIN_TOP; +import static com.demcha.compose.document.templates.proposal.presets.EditorialStyles.PAGE_BACKGROUND; +import static com.demcha.compose.document.templates.proposal.presets.EditorialStyles.PAGE_HEIGHT; +import static com.demcha.compose.document.templates.proposal.presets.EditorialStyles.PAGE_WIDTH; +import static com.demcha.compose.document.templates.proposal.presets.EditorialStyles.RULE; + +/** + * Editorial Proposal — the second structured proposal preset: a two-page + * business proposal set in a serif display face with an orange accent. + * + *

    Page one opens with the masthead — a drawn brand mark, the stacked + * wordmark, the tracked document label — then the three title lines, the + * meta line, the executive summary beside an at-a-glance fact card, the + * goal cells, and the numbered scope list. Page two repeats the masthead + * and carries the deliverable columns, the phase grid, the investment / + * terms band, and the signing card.

    + * + *

    What sets it apart from {@link NorthlineProposal}, which renders the + * same {@link StructuredProposalDocumentSpec}: section headings are set in + * the display serif over a short accent rule rather than in the body sans + * inside an icon badge, so the data's section icon tokens are not drawn; + * the brand mark is drawn from vector paths rather than set as a monogram + * letter; the fact card is untitled, so its heading is not drawn either; + * the scope ordinal is plain accent text rather than a filled pill; and the + * page foot is a hairline over the content width rather than a filled band. + * Both presets set their titles in the same display face.

    + * + *

    Moving a document between the two presets is a + * one-line change in the code and two things to check in the data. Icon + * tokens are preset-scoped vocabulary — each preset packages its own set, + * and a token this one does not carry is reported as a data error; the four + * {@code fact-*} tokens are named alike in both sets, the badge and goal + * tokens are not. Heading text is drawn as authored, so a document written + * for the sibling's tracked capitals keeps them here, where this design + * sets its headings in title case.

    + * + *

    The preset owns its session geometry — A4, the margins published as + * {@link #RECOMMENDED_MARGIN}, the near-white page fill — and draws the + * brand line and page numbers as footer chrome, so they repeat on every + * page. It issues no explicit page break: the page-two masthead is + * {@code keepWithNext}, which is what stops it being orphaned at the foot + * of page one, and a break issued after the compiler has already advanced + * would leave a blank page between the two.

    + */ +public final class EditorialProposal { + + /** + * Stable template identifier. + */ + public static final String ID = "proposal-editorial"; + + /** + * Human-readable display name. + */ + public static final String DISPLAY_NAME = "Editorial Proposal"; + + /** + * The side margin (in points) the preset sets. It is published so a + * caller can measure against the same frame; the preset applies its own + * margins inside {@code compose}, so callers leave the session + * unconfigured. + */ + public static final double RECOMMENDED_MARGIN = MARGIN_SIDE; + + private EditorialProposal() { + } + + /** + * Builds the preset. + * + * @return ready-to-use template + */ + public static DocumentTemplate create() { + return new Template(); + } + + private record Template() implements DocumentTemplate { + + @Override + public String id() { + return ID; + } + + @Override + public String displayName() { + return DISPLAY_NAME; + } + + @Override + public void compose(DocumentSession document, StructuredProposalDocumentSpec spec) { + Objects.requireNonNull(document, "document"); + StructuredProposalData data = Objects.requireNonNull(spec, "spec").proposal(); + + document.pageSize(PAGE_WIDTH, PAGE_HEIGHT) + // The bottom margin is the footer band: content stops + // above it, so nothing runs under the chrome. + .margin(new DocumentInsets(MARGIN_TOP, MARGIN_SIDE, + FOOTER_BAND, MARGIN_SIDE)) + .pageBackground(PAGE_BACKGROUND); + + renderChrome(document, data.brand()); + + document.pageFlow(page -> { + // spacing(0): flow spacing survives a page break, so a band + // opening a page would inherit a gap the first never had. + page.name("Proposal").spacing(0); + EditorialWidgets.renderMasthead(page, data.brand()); + EditorialPageOne.compose(page, data); + EditorialWidgets.renderMasthead(page, data.brand()); + EditorialPageTwo.compose(page, data); + }); + } + + /** + * The page foot: a hairline across the content width as a page-ratio + * background fill, and the brand line with the page number as + * FOOTER-zone chrome, both repeating on every page. The number is + * written {@code 0{page}} because the reference numbers its pages 01 + * and 02 and there is no zero-padded page-number style. + */ + private static void renderChrome(DocumentSession document, ProposalBrand brand) { + document.pageBackgrounds(List.of(new PageBackgroundFill( + MARGIN_SIDE / PAGE_WIDTH, + FOOTER_RULE_Y / PAGE_HEIGHT, + CONTENT_WIDTH / PAGE_WIDTH, + HAIRLINE / PAGE_HEIGHT, + RULE))); + + document.footer(DocumentHeaderFooter.builder() + .zone(DocumentHeaderFooterZone.FOOTER) + .height((float) FOOTER_ZONE_HEIGHT) + .leftText(brand.footerName() + " | " + brand.website()) + .rightText("0{page}") + .fontSize((float) FOOTER_TEXT_SIZE) + .textColor(INK) + .build()); + } + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/proposal/presets/EditorialStyles.java b/templates/src/main/java/com/demcha/compose/document/templates/proposal/presets/EditorialStyles.java new file mode 100644 index 000000000..603e57a66 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/proposal/presets/EditorialStyles.java @@ -0,0 +1,222 @@ +package com.demcha.compose.document.templates.proposal.presets; + +import com.demcha.compose.document.style.DocumentColor; +import com.demcha.compose.document.style.DocumentTextDecoration; +import com.demcha.compose.document.style.DocumentTextStyle; +import com.demcha.compose.document.templates.core.text.TextOrnaments; +import com.demcha.compose.font.FontName; + +/** + * The Editorial Proposal look: page geometry, the colour roles, the + * Spectral/Lato scale, and the measured widths every band works against. + * + *

    Preset-local for the same reason as its sibling's tokens: every value + * was measured against the ported template's reference render, and no other + * family shares this look.

    + */ +final class EditorialStyles { + + private EditorialStyles() { + } + + // --- page --------------------------------------------------------------- + static final double PAGE_WIDTH = 595.276; // A4 portrait, points + static final double PAGE_HEIGHT = 841.890; + static final double MARGIN_SIDE = 30.0; + static final double MARGIN_TOP = 19.7; + static final double CONTENT_WIDTH = PAGE_WIDTH - 2 * MARGIN_SIDE; + static final double FOOTER_BAND = 49.0; + + // --- band splits, measured from the reference --------------------------- + static final double SUMMARY_WEIGHT = 0.60; + static final double GLANCE_WEIGHT = 0.338; + static final double SUMMARY_GAP = CONTENT_WIDTH * (1 - SUMMARY_WEIGHT - GLANCE_WEIGHT); + static final double INVESTMENT_WEIGHT = 0.596; + static final double TERMS_WEIGHT = 0.330; + static final double MONEY_GAP = CONTENT_WIDTH * (1 - INVESTMENT_WEIGHT - TERMS_WEIGHT); + static final double DELIVERABLES_LEFT_WEIGHT = 0.491; + static final double DELIVERABLES_COLUMN_WEIGHT = 0.362; + /** PHASE / FOCUS / DURATION / OUTPUT. */ + static final double[] TIMELINE_WEIGHTS = {0.222, 0.307, 0.179, 0.291}; + static final double SCOPE_TITLE_SPLIT = 0.430; + static final double SCOPE_DESC_WEIGHT = 0.532; + static final double INVESTMENT_LABEL_SPLIT = 0.64; + + static final double GLANCE_WIDTH = CONTENT_WIDTH * GLANCE_WEIGHT; + static final double INVESTMENT_WIDTH = CONTENT_WIDTH * INVESTMENT_WEIGHT; + + // --- independent dimensions --------------------------------------------- + static final double LOGO_WIDTH = 30.0; + static final double LOGO_HEIGHT = 26.0; + static final double LOGO_GAP = 9.0; + /** Wordmark line pitch, as a fraction of its type size. */ + static final double WORDMARK_LINE = 0.82; + static final double WORDMARK_WIDTH = CONTENT_WIDTH * 0.19; + static final double DOC_LABEL_WIDTH = CONTENT_WIDTH * 0.13; + static final double DOC_LABEL_RULE_WIDTH = CONTENT_WIDTH * 0.115; + static final double DOC_LABEL_INSET = 8.5; + static final double FACT_ICON = 20.0; + static final double FACT_GAP = 14.0; + static final double GOAL_ICON = 16.0; + static final double GOAL_GAP = 8.0; + static final int GOAL_TEXT_LINES = 3; + static final double PANEL_PADDING = 17.0; + static final double PANEL_RADIUS = 5.0; + static final double ACCEPTANCE_PADDING = 9.0; + static final double ACCEPTANCE_SIDE_PADDING = 18.0; + static final double SIGNATURE_LABEL_GAP = 4.0; + static final double SIGNATURE_FIELD_GAP = 15.5; + static final double SIGNATURE_SPACER = SIGNATURE_FIELD_GAP - 2 * SIGNATURE_LABEL_GAP; + /** Rule widths of the three signature fields the reference sets. */ + static final double[] SIGNATURE_RULE_WEIGHTS = {0.374, 0.385, 0.241}; + static final double SCOPE_NUMBER_WIDTH = 22.0; + static final double SCOPE_GAP = 12.0; + static final double HAIRLINE = 0.7; + static final double MASTHEAD_RULE = 1.1; + static final double HEADING_RULE_WIDTH = CONTENT_WIDTH * 0.048; + static final double HEADING_RULE_THICKNESS = 1.6; + + // --- vertical rhythm ----------------------------------------------------- + /** + * The gap above a band, carried by the band rather than by the page flow: + * flow spacing survives a page break, so a band opening a page would + * inherit a leading gap the first band never had. + */ + static final float BAND_TOP = 17.8f; + static final double GAP_MASTHEAD_RULE = 16.1; + static final float GAP_TITLE = 15.5f; + static final float GAP_META = 16.0f; + static final float GAP_PAGE_TWO_OPENING = 19.0f; + static final double GAP_HEADING = 1.0; + static final float GAP_AFTER_RULE = 9.5f; + static final float GAP_BEFORE_TABLE = 13.7f; + static final float GAP_PARAGRAPH = 9.0f; + static final double GAP_BEFORE_GOALS_HEADING = 26.5; + static final double FACT_DIVIDER_GAP = 7.1; + static final double SCOPE_ROW_PAD = 2.9; + static final double INVESTMENT_ROW_PAD = 4.0; + static final double INVESTMENT_EMPHASIS_PAD = 7.0; + static final double INVESTMENT_TOTAL_PAD = 5.4; + static final double TIMELINE_ROW_PAD = 7.0; + static final double TABLE_CELL_INSET = 10.0; + static final double ACCEPTANCE_FIELD_GAP = 9.0; + static final float GAP_BULLET = 7.6f; + static final double LIST_LEADING = 0.87; + + // --- type scale ---------------------------------------------------------- + static final double BODY_SIZE = 10.0; + /** EXTRA leading in points (the engine adds it to the font's line height). */ + static final double BODY_LEADING = 3.05; + /** Lato line height as a multiple of the type size (measured). */ + static final double LATO_LINE = 1.213; + static final double TITLE_SIZE = 41.8; + static final double TITLE_PITCH = 41.9; + static final double HEADING_SIZE = 16.3; + static final double WORDMARK_SIZE = 16.1; + static final double WORDMARK_SUB_SIZE = 14.0; + static final double TABLE_SIZE = 9.0; + static final double LABEL_SIZE = 7.5; + static final double DOC_LABEL_SIZE = 8.3; + static final double META_SIZE = 10.5; + static final double FACT_VALUE_SIZE = 14.0; + static final double SCOPE_BODY_SIZE = 9.9; + static final double SCOPE_LEADING = 0.6; + static final double FOOTER_TEXT_SIZE = 8.5; + /** Helvetica cap height as a fraction of the type size. */ + static final double HELVETICA_CAP = 0.717; + /** + * Not the band height: the backend places the footer baseline at + * (height - fontSize) above the page edge, so this seats the cap band in + * the middle of the footer band. + */ + static final double FOOTER_ZONE_HEIGHT = + (FOOTER_BAND - FOOTER_TEXT_SIZE * HELVETICA_CAP) / 2 + FOOTER_TEXT_SIZE; + static final double FOOTER_RULE_Y = PAGE_HEIGHT - FOOTER_BAND; + /** How far a heading's line box starts above its cap top, as a fraction of the size. */ + static final double HEADING_CAP_LEAD = 0.42; + + // --- colour roles: named for the job, not the hue ------------------------ + static final DocumentColor ACCENT = DocumentColor.rgb(237, 76, 15); + static final DocumentColor INK = DocumentColor.rgb(35, 35, 35); + static final DocumentColor BODY_TEXT = DocumentColor.rgb(46, 46, 46); + static final DocumentColor MUTED_TEXT = DocumentColor.rgb(90, 90, 90); + static final DocumentColor PAGE_BACKGROUND = DocumentColor.rgb(254, 254, 254); + static final DocumentColor PANEL_BACKGROUND = DocumentColor.rgb(250, 246, 243); + static final DocumentColor SUBTOTAL_FILL = DocumentColor.rgb(243, 241, 239); + static final DocumentColor RULE = DocumentColor.rgb(216, 213, 210); + static final DocumentColor RULE_QUIET = DocumentColor.rgb(231, 227, 223); + static final DocumentColor ON_DARK = DocumentColor.rgb(255, 255, 255); + + static final FontName TITLE_FONT = FontName.SPECTRAL; + static final FontName BODY_FONT = FontName.LATO; + + // --- text roles ----------------------------------------------------------- + static final DocumentTextStyle TITLE_INK = + style(TITLE_FONT, TITLE_SIZE, DocumentTextDecoration.BOLD, INK); + static final DocumentTextStyle TITLE_ACCENT = + style(TITLE_FONT, TITLE_SIZE, DocumentTextDecoration.BOLD, ACCENT); + static final DocumentTextStyle SECTION_HEADING = + style(TITLE_FONT, HEADING_SIZE, DocumentTextDecoration.BOLD, INK); + static final DocumentTextStyle BODY = + style(BODY_FONT, BODY_SIZE, DocumentTextDecoration.DEFAULT, BODY_TEXT); + static final DocumentTextStyle META = + style(BODY_FONT, META_SIZE, DocumentTextDecoration.DEFAULT, BODY_TEXT); + static final DocumentTextStyle META_SEPARATOR = + style(BODY_FONT, META_SIZE, DocumentTextDecoration.DEFAULT, ACCENT); + static final DocumentTextStyle FACT_LABEL = + style(BODY_FONT, LABEL_SIZE, DocumentTextDecoration.BOLD, MUTED_TEXT); + static final DocumentTextStyle FACT_VALUE = + style(BODY_FONT, FACT_VALUE_SIZE, DocumentTextDecoration.BOLD, INK); + static final DocumentTextStyle FACT_NOTE = + style(BODY_FONT, LABEL_SIZE + 0.5, DocumentTextDecoration.DEFAULT, MUTED_TEXT); + static final DocumentTextStyle GOAL_TEXT = + style(BODY_FONT, TABLE_SIZE, DocumentTextDecoration.DEFAULT, BODY_TEXT); + static final DocumentTextStyle SCOPE_NUMBER = + style(BODY_FONT, TABLE_SIZE, DocumentTextDecoration.BOLD, ACCENT); + static final DocumentTextStyle SCOPE_TITLE = + style(BODY_FONT, TABLE_SIZE + 0.5, DocumentTextDecoration.BOLD, INK); + static final DocumentTextStyle SCOPE_BODY = + style(BODY_FONT, SCOPE_BODY_SIZE, DocumentTextDecoration.DEFAULT, MUTED_TEXT); + static final DocumentTextStyle TABLE_HEADER = + style(BODY_FONT, TABLE_SIZE - 0.5, DocumentTextDecoration.BOLD, ON_DARK); + static final DocumentTextStyle TABLE_BODY = + style(BODY_FONT, TABLE_SIZE, DocumentTextDecoration.DEFAULT, BODY_TEXT); + static final DocumentTextStyle TABLE_BODY_BOLD = + style(BODY_FONT, TABLE_SIZE, DocumentTextDecoration.BOLD, INK); + static final DocumentTextStyle TOTAL_TEXT = + style(BODY_FONT, TABLE_SIZE + 1.5, DocumentTextDecoration.BOLD, ON_DARK); + static final DocumentTextStyle WORDMARK = + style(BODY_FONT, WORDMARK_SIZE, DocumentTextDecoration.BOLD, INK); + static final DocumentTextStyle WORDMARK_SUB = + style(BODY_FONT, WORDMARK_SUB_SIZE, DocumentTextDecoration.BOLD, INK); + /** The document label, spaced on the style so its caller passes the words. */ + static final DocumentTextStyle DOC_LABEL = + style(BODY_FONT, DOC_LABEL_SIZE, DocumentTextDecoration.BOLD, INK) + .withLetterSpacing(TextOrnaments.SPACED_CAPS); + static final DocumentTextStyle SIGNATURE_LABEL = + style(BODY_FONT, TABLE_SIZE, DocumentTextDecoration.DEFAULT, INK); + + /** + * The height one line of body-scale text occupies: the font's own line + * height plus the additive leading. Multiplying the size by the leading + * is the wrong model and makes derived heights half again too tall. + * + * @param size the type size + * @return the line height in points + */ + static double lineHeight(double size) { + return size * LATO_LINE + BODY_LEADING; + } + + private static DocumentTextStyle style(FontName font, + double size, + DocumentTextDecoration decoration, + DocumentColor color) { + return DocumentTextStyle.builder() + .fontName(font) + .size(size) + .decoration(decoration) + .color(color) + .build(); + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/proposal/presets/EditorialWidgets.java b/templates/src/main/java/com/demcha/compose/document/templates/proposal/presets/EditorialWidgets.java new file mode 100644 index 000000000..ed1fc8051 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/proposal/presets/EditorialWidgets.java @@ -0,0 +1,224 @@ +package com.demcha.compose.document.templates.proposal.presets; + +import com.demcha.compose.document.dsl.LineBuilder; +import com.demcha.compose.document.dsl.PageFlowBuilder; +import com.demcha.compose.document.dsl.ParagraphBuilder; +import com.demcha.compose.document.dsl.PathBuilder; +import com.demcha.compose.document.dsl.SectionBuilder; +import com.demcha.compose.document.dsl.ShapeContainerBuilder; +import com.demcha.compose.document.node.DocumentNode; +import com.demcha.compose.document.node.HorizontalAlign; +import com.demcha.compose.document.node.LayerAlign; +import com.demcha.compose.document.node.RowVerticalAlign; +import com.demcha.compose.document.node.TextAlign; +import com.demcha.compose.document.style.ClipPolicy; +import com.demcha.compose.document.style.DocumentColor; +import com.demcha.compose.document.style.DocumentInsets; +import com.demcha.compose.document.style.DocumentRowColumn; +import com.demcha.compose.document.style.DocumentTextIndent; +import com.demcha.compose.document.style.DocumentTextStyle; +import com.demcha.compose.document.svg.SvgPath; +import com.demcha.compose.document.templates.core.text.TextOrnaments; +import com.demcha.compose.document.templates.data.proposal.ProposalBrand; + +import java.util.List; + +import static com.demcha.compose.document.templates.proposal.presets.EditorialStyles.ACCENT; +import static com.demcha.compose.document.templates.proposal.presets.EditorialStyles.BODY; +import static com.demcha.compose.document.templates.proposal.presets.EditorialStyles.DOC_LABEL; +import static com.demcha.compose.document.templates.proposal.presets.EditorialStyles.DOC_LABEL_INSET; +import static com.demcha.compose.document.templates.proposal.presets.EditorialStyles.DOC_LABEL_RULE_WIDTH; +import static com.demcha.compose.document.templates.proposal.presets.EditorialStyles.DOC_LABEL_WIDTH; +import static com.demcha.compose.document.templates.proposal.presets.EditorialStyles.GAP_HEADING; +import static com.demcha.compose.document.templates.proposal.presets.EditorialStyles.GAP_MASTHEAD_RULE; +import static com.demcha.compose.document.templates.proposal.presets.EditorialStyles.HEADING_RULE_THICKNESS; +import static com.demcha.compose.document.templates.proposal.presets.EditorialStyles.HEADING_RULE_WIDTH; +import static com.demcha.compose.document.templates.proposal.presets.EditorialStyles.INK; +import static com.demcha.compose.document.templates.proposal.presets.EditorialStyles.LIST_LEADING; +import static com.demcha.compose.document.templates.proposal.presets.EditorialStyles.LOGO_GAP; +import static com.demcha.compose.document.templates.proposal.presets.EditorialStyles.LOGO_HEIGHT; +import static com.demcha.compose.document.templates.proposal.presets.EditorialStyles.LOGO_WIDTH; +import static com.demcha.compose.document.templates.proposal.presets.EditorialStyles.MASTHEAD_RULE; +import static com.demcha.compose.document.templates.proposal.presets.EditorialStyles.PAGE_BACKGROUND; +import static com.demcha.compose.document.templates.proposal.presets.EditorialStyles.SECTION_HEADING; +import static com.demcha.compose.document.templates.proposal.presets.EditorialStyles.WORDMARK; +import static com.demcha.compose.document.templates.proposal.presets.EditorialStyles.WORDMARK_LINE; +import static com.demcha.compose.document.templates.proposal.presets.EditorialStyles.WORDMARK_SIZE; +import static com.demcha.compose.document.templates.proposal.presets.EditorialStyles.WORDMARK_SUB; +import static com.demcha.compose.document.templates.proposal.presets.EditorialStyles.WORDMARK_WIDTH; + +/** + * The building blocks the Editorial Proposal bands share: the masthead that + * opens every page, the drawn logo mark, the serif section heading over its + * accent rule, the bullet, and the stacked-line box both the wordmark and + * the title are set in. + */ +final class EditorialWidgets { + + private EditorialWidgets() { + } + + /** + * The masthead: the drawn mark, the stacked wordmark, the tracked + * document label over its accent rule, and the full-width rule beneath. + * + *

    It is {@code keepWithNext}, which is what stops the page-two + * masthead from being orphaned at the foot of page one — the preset + * issues no explicit page break.

    + */ + static void renderMasthead(PageFlowBuilder page, ProposalBrand brand) { + page.addSection("Masthead", masthead -> { + masthead.spacing(0); + masthead.keepWithNext(); + masthead.addRow("MastheadRow", row -> { + row.verticalAlign(RowVerticalAlign.CENTER) + .gap(LOGO_GAP) + // Not auto(): a section's natural width is the width + // it is offered, so two auto columns would each claim + // the whole row. Both ends are measured strips. + .columns(DocumentRowColumn.fixed(LOGO_WIDTH), + DocumentRowColumn.fixed(WORDMARK_WIDTH), + DocumentRowColumn.weight(1), + DocumentRowColumn.fixed(DOC_LABEL_WIDTH)); + row.add(logoMark()); + // The wordmark stacks at 0.82 of its type size, tighter than + // the 1.21 Lato asks for, which is what makes it read as one + // mark rather than two words — and a section cannot carry the + // negative spacing that pitch would need. + row.add(stackedLines("Wordmark", WORDMARK_WIDTH, + WORDMARK_SIZE * WORDMARK_LINE, WORDMARK_SIZE, + List.of(brand.nameLine1(), brand.nameLine2()), + List.of(WORDMARK, WORDMARK_SUB))); + // A zero-width spacer: the row distributes by columns, and the + // weight(1) column above already holds the ends apart. + row.addSpacer(0); + row.addSection("DocumentLabel", label -> { + label.spacing(0); + label.padding(0f, (float) DOC_LABEL_INSET, 0f, 0f); + label.addParagraph(p -> p + .text(TextOrnaments.upper(brand.documentLabel())) + .textStyle(DOC_LABEL) + .align(TextAlign.RIGHT)); + // addAligned, not addLine: a section stacks children at the + // content width with no child alignment, and this rule sits + // flush RIGHT under the label. + label.addAligned(HorizontalAlign.RIGHT, new LineBuilder() + .name("DocumentLabelRule") + .horizontal(DOC_LABEL_RULE_WIDTH) + .thickness(HEADING_RULE_THICKNESS) + .color(ACCENT) + .margin(DocumentInsets.top(5)) + .build()); + }); + }); + masthead.addLine(line -> line + .fill() + .thickness(MASTHEAD_RULE) + .color(INK) + .margin(DocumentInsets.top(GAP_MASTHEAD_RULE))); + }); + } + + /** + * The brand mark, drawn as four stacked vector paths rather than loaded: + * an accent stem, an accent diagonal, a page-coloured slash that cuts it, + * and an ink stem. + */ + private static DocumentNode logoMark() { + return new ShapeContainerBuilder() + .name("LogoMark") + .rectangle(LOGO_WIDTH, LOGO_HEIGHT) + .clipPolicy(ClipPolicy.OVERFLOW_VISIBLE) + .position(markPath("MarkStem", "M0,0 L14,0 L14,50 L0,50 Z", ACCENT), + 0, 0, LayerAlign.TOP_LEFT, 0) + .position(markPath("MarkDiagonal", "M13,0 L28,0 L48,50 L33,50 Z", ACCENT), + 0, 0, LayerAlign.TOP_LEFT, 1) + .position(markPath("MarkSlash", "M3,0 L8,0 L45,50 L40,50 Z", PAGE_BACKGROUND), + 0, 0, LayerAlign.TOP_LEFT, 2) + .position(markPath("MarkRightStem", "M40,0 L58,0 L58,50 L40,50 Z", INK), + 0, 0, LayerAlign.TOP_LEFT, 3) + .build(); + } + + private static DocumentNode markPath(String name, String d, DocumentColor fill) { + return new PathBuilder() + .name(name) + .size(LOGO_WIDTH, LOGO_HEIGHT) + .svg(SvgPath.parse(d, 0, 0, 58, 50)) + .fillColor(fill) + .build(); + } + + /** + * The heading every section opens with: the serif title over a short + * accent rule. Unlike its teal sibling there is no badge, so the data's + * section icon tokens are not read here. + * + * @param title the heading text + * @return the heading node, kept with whatever follows it + */ + static DocumentNode sectionHeading(String title) { + SectionBuilder heading = new SectionBuilder(); + heading.name("SectionHeading"); + heading.spacing(0); + heading.keepWithNext(); + heading.addParagraph(p -> p + .name("SectionHeadingTitle") + .text(title) + .textStyle(SECTION_HEADING)); + heading.addLine(line -> line + .name("SectionHeadingRule") + .horizontal(HEADING_RULE_WIDTH) + .thickness(HEADING_RULE_THICKNESS) + .color(ACCENT) + .margin(DocumentInsets.top(GAP_HEADING))); + return heading.build(); + } + + /** + * A bullet item: an inline accent dot, then the text, with a hanging + * indent so wrapped lines clear the dot. + */ + static void renderBullet(SectionBuilder host, String text) { + host.addParagraph(p -> p + .dot(3.6, ACCENT) + .inlineText(" " + text, BODY) + .bulletOffset(" ") + .indentStrategy(DocumentTextIndent.FROM_SECOND_LINE) + .lineSpacing(LIST_LEADING)); + } + + /** + * Lines stacked on an authored pitch: each line is anchored inside one + * container, because leading is additive and the engine rejects the + * negative value a tighter-than-natural pitch would need. + * + * @param name node name of the box and its lines + * @param width the box width + * @param pitch distance between line tops + * @param lastLineHeight height reserved for the final line + * @param texts the lines, in order + * @param styles one style per line + * @return the stacked box + */ + static DocumentNode stackedLines(String name, + double width, + double pitch, + double lastLineHeight, + List texts, + List styles) { + ShapeContainerBuilder box = new ShapeContainerBuilder() + .name(name) + .rectangle(width, (texts.size() - 1) * pitch + lastLineHeight) + .clipPolicy(ClipPolicy.OVERFLOW_VISIBLE); + for (int i = 0; i < texts.size(); i++) { + DocumentNode line = new ParagraphBuilder() + .name(name + "Line" + (i + 1)) + .text(texts.get(i)) + .textStyle(styles.get(i)) + .build(); + box.position(line, 0, i * pitch, LayerAlign.TOP_LEFT); + } + return box.build(); + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/proposal/presets/IndigoBand.java b/templates/src/main/java/com/demcha/compose/document/templates/proposal/presets/IndigoBand.java new file mode 100644 index 000000000..ae149d936 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/proposal/presets/IndigoBand.java @@ -0,0 +1,186 @@ +package com.demcha.compose.document.templates.proposal.presets; + +import com.demcha.compose.document.dsl.PageFlowBuilder; +import com.demcha.compose.document.dsl.SectionBuilder; +import com.demcha.compose.document.style.DocumentInsets; +import com.demcha.compose.document.style.DocumentRowColumn; +import com.demcha.compose.document.templates.data.proposal.ProposalGlance; + +import java.util.List; + +import static com.demcha.compose.document.templates.proposal.presets.IndigoFlow.layeredRow; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.ABOUT_BODY_CAP; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.ABOUT_BODY_PITCH_PX; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.ABOUT_BODY_SIZE; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.ABOUT_HEADING_CAP; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.ABOUT_MEASURE_PX; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.ACCENT; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.BAND_ARTWORK_W; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.BAND_H_PX; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.BAND_LEFT_W; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.BAND_TOP_PX; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.BODY; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.FEATURE_GLYPH; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.FEATURE_LINE1_CAP; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.FEATURE_LINE2_CAP; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.FEATURE_PITCH_PX; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.FEATURE_RADIUS; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.FEATURE_SIZE; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.FEATURE_TILE; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.FEATURE_TILE_AT; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.FEATURE_TILE_PX; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.INK; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.LABEL_SIZE; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.MARGIN_L; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.MARGIN_R; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.TILE_DARK; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.TINT; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.bold; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.boxBottomPx; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.plain; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.px; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.toPx; + +/** + * The tinted band across the middle of the sheet: a paragraph about the issuer, + * a row of marked tiles under it, and the place the design fills with artwork. + * + *

    What this port leaves out

    + * + *

    The design fills the band's right with a product photograph carrying its + * own brand's mark. That is a brand asset, and the templates artifact carries + * none, so the place it occupies is kept — the copy keeps the measure the design + * solved it against, and the tiles keep their pitch — and left as flat tint.

    + */ +final class IndigoBand { + + private IndigoBand() { + } + + /** + * The band. + * + *

    {@code keepTogether} is a section's property and the band needs it: its + * fill is painted behind its own content, so a band that split across a page + * would leave its fill behind.

    + * + * @param page the page flow + * @param glance the paragraph and the marked facts the band carries + * @param flow the sheet's cursor + */ + static void render(PageFlowBuilder page, ProposalGlance glance, IndigoFlow flow) { + if (glance.heading().isBlank() && glance.intro().isBlank() + && glance.facts().isEmpty()) { + // A band of tint three hundred pixels tall with nothing in it is not + // a design decision; a document that says nothing about its issuer + // simply has no band. + return; + } + double top = flow.boxAt(BAND_TOP_PX, BAND_H_PX); + page.addSection("AboutBand", band -> { + band.spacing(0) + .keepTogether() + .fillColor(TINT) + // Negative on both sides, so the fill reaches both paper + // edges; the padding puts the content back inside the left + // margin, and the right stays open for the artwork the design + // runs off the page. + .margin(new DocumentInsets(top, -MARGIN_R, 0, -MARGIN_L)) + .padding(new DocumentInsets(0, 0, 0, MARGIN_L)); + band.addRow("AboutRow", row -> { + row.spacing(0) + .columns(DocumentRowColumn.weight(1), + DocumentRowColumn.fixed(BAND_ARTWORK_W)); + row.addSection("AboutLeft", left -> { + left.spacing(0); + IndigoFlow cell = new IndigoFlow(BAND_TOP_PX); + renderCopy(left, glance, cell); + renderFeatureTiles(left, glance.facts(), cell); + }); + // The artwork's place, kept so the copy beside it keeps its + // measure. The design's own picture is its brand's, but the + // height is the band's: the artwork is what makes the band as + // tall as it is, and without it the tint would close on the + // copy and everything below would ride up. + row.addSection("AboutArtwork", holder -> holder + .spacing(0) + .addSpacer(space -> space.name("AboutArtworkSpace") + .height(px(BAND_H_PX)))); + }); + }); + } + + private static void renderCopy(SectionBuilder left, ProposalGlance glance, IndigoFlow cell) { + if (!glance.heading().isBlank()) { + left.addParagraph(p -> p + .name("AboutHeading") + .text(glance.heading()) + .textStyle(bold(LABEL_SIZE, ACCENT)) + .margin(new DocumentInsets( + cell.capAt(ABOUT_HEADING_CAP, LABEL_SIZE, true), 0, 0, 0))); + } + if (glance.intro().isBlank()) { + return; + } + double measureInset = Math.max(0, BAND_LEFT_W - px(ABOUT_MEASURE_PX)); + double gap = cell.capAt(ABOUT_BODY_CAP, ABOUT_BODY_SIZE, false); + left.addParagraph(p -> p + .name("AboutBody") + .text(glance.intro()) + .textStyle(plain(ABOUT_BODY_SIZE, BODY)) + .lineSpacing(px(ABOUT_BODY_PITCH_PX) - ABOUT_BODY_SIZE) + .margin(new DocumentInsets(gap, measureInset, 0, 0))); + // The design sets it in four lines at this measure; the map states that + // rather than asking the engine afterwards. + cell.advanceTo(ABOUT_BODY_CAP + 3 * ABOUT_BODY_PITCH_PX + toPx(ABOUT_BODY_SIZE)); + } + + /** + * The row of marked tiles. + * + *

    The pitch is fixed for every tile but the last, which takes what is + * left: four full pitches are wider than the band's left column, and the last + * tile only ever needed its own width and its label's.

    + */ + private static void renderFeatureTiles(SectionBuilder left, List facts, + IndigoFlow cell) { + if (facts.isEmpty()) { + return; + } + double top = cell.boxAt(FEATURE_TILE_AT, + boxBottomPx(FEATURE_LINE2_CAP, FEATURE_SIZE, false) - FEATURE_TILE_AT); + layeredRow(left, "FeatureTiles", row -> { + row.spacing(0).margin(new DocumentInsets(top, 0, 0, 0)); + DocumentRowColumn[] columns = new DocumentRowColumn[facts.size()]; + for (int i = 0; i < facts.size(); i++) { + columns[i] = i == facts.size() - 1 + ? DocumentRowColumn.weight(1) + : DocumentRowColumn.fixed(px(FEATURE_PITCH_PX)); + } + row.columns(columns); + for (int i = 0; i < facts.size(); i++) { + ProposalGlance.Fact fact = facts.get(i); + int index = i; + row.addSection("FeatureCell_" + index, group -> { + group.spacing(0); + IndigoFlow stack = new IndigoFlow(FEATURE_TILE_AT); + group.add(IndigoWidgets.tile("FeatureTile_" + index, TILE_DARK, + fact.icon(), FEATURE_TILE, FEATURE_RADIUS, FEATURE_GLYPH)); + stack.boxAt(FEATURE_TILE_AT, FEATURE_TILE_PX); + group.addParagraph(p -> p + .name("FeatureLine1_" + index) + .text(fact.label()) + .textStyle(plain(FEATURE_SIZE, INK)) + .margin(new DocumentInsets( + stack.capAt(FEATURE_LINE1_CAP, FEATURE_SIZE, false), 0, 0, 0))); + group.addParagraph(p -> p + .name("FeatureLine2_" + index) + .text(fact.value()) + .textStyle(plain(FEATURE_SIZE, INK)) + .margin(new DocumentInsets( + stack.capAt(FEATURE_LINE2_CAP, FEATURE_SIZE, false), 0, 0, 0))); + }); + } + }); + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/proposal/presets/IndigoClosing.java b/templates/src/main/java/com/demcha/compose/document/templates/proposal/presets/IndigoClosing.java new file mode 100644 index 000000000..3481b773c --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/proposal/presets/IndigoClosing.java @@ -0,0 +1,186 @@ +package com.demcha.compose.document.templates.proposal.presets; + +import com.demcha.compose.document.dsl.PageFlowBuilder; +import com.demcha.compose.document.dsl.ParagraphBuilder; +import com.demcha.compose.document.dsl.SectionBuilder; +import com.demcha.compose.document.node.DocumentLinkOptions; +import com.demcha.compose.document.style.DocumentInsets; +import com.demcha.compose.document.style.DocumentRowColumn; +import com.demcha.compose.document.style.DocumentTextStyle; +import com.demcha.compose.document.templates.core.identity.ContactUri; +import com.demcha.compose.document.templates.data.proposal.ProposalBrand; +import com.demcha.compose.document.templates.data.proposal.ProposalFooter; + +import java.util.List; + +import static com.demcha.compose.document.templates.proposal.presets.IndigoFlow.rule; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.BODY; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.CONTENT_W; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.FOOTER_ADDRESS_CAP; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.FOOTER_BODY_SIZE; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.FOOTER_CONTACT_CAP; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.FOOTER_MARK_AT; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.FOOTER_MARK_SIZE; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.FOOTER_NAME_CAP; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.FOOTER_NAME_SIZE; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.FOOTER_RULE_AT; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.FOOTER_TEXT_COL_W; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.INK; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.RULE_STRONG; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.bold; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.boxBottomPx; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.plain; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.toPx; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.topBearing; + +/** + * What closes the sheet: a rule the width of the ink, then the issuer's mark, + * legal name, registered address and the channels a reader can follow. + * + *

    What this port leaves out

    + * + *

    The design closes with its own logotype. That is a brand asset and the + * templates artifact carries none, so the mark's place is kept and set with the + * monogram the document names, at the size the design's glyph reads at beside + * the name. A document naming no monogram leaves the place empty, and the name + * beside it does not move.

    + */ +final class IndigoClosing { + + /** + * The separator between two channels, spaced away from both. It is a run of + * its own so that only the channel either side of it is clickable, and so + * that it keeps the lighter colour the design gives it. + */ + private static final String SEPARATOR = " | "; + + private IndigoClosing() { + } + + /** The full-width rule the foot opens with. */ + static void renderFooterRule(PageFlowBuilder page, IndigoFlow flow) { + double top = flow.boxAt(FOOTER_RULE_AT, 1.5); + page.addLine(line -> rule(line, "FooterRule", CONTENT_W, RULE_STRONG) + .margin(new DocumentInsets(top, 0, 0, 0))); + } + + /** + * The foot itself. + * + * @param page the page flow + * @param brand the issuer's mark + * @param footer the issuer's identity as the foot states it + * @param flow the sheet's cursor + */ + static void renderFooter(PageFlowBuilder page, ProposalBrand brand, ProposalFooter footer, + IndigoFlow flow) { + if (!footer.isPresent() && brand.monogram().isBlank()) { + return; + } + // The row opens on the name's own box, not on the mark's: the name's + // box starts above its cap and above the mark, and a row opened on the + // mark would owe the name a negative margin. + double rowTopPx = FOOTER_NAME_CAP - toPx(topBearing(FOOTER_NAME_SIZE, true)); + double top = flow.boxAt(rowTopPx, + boxBottomPx(FOOTER_CONTACT_CAP, FOOTER_BODY_SIZE, false) - rowTopPx); + page.addRow("Footer", row -> { + row.spacing(0) + .margin(new DocumentInsets(top, 0, 0, 0)) + .columns(DocumentRowColumn.fixed(FOOTER_TEXT_COL_W), + DocumentRowColumn.weight(1)); + row.addSection("FooterMark", mark -> { + mark.spacing(0); + if (brand.monogram().isBlank()) { + return; + } + mark.addParagraph(p -> p + .name("FooterMonogram") + .text(brand.monogram()) + .textStyle(bold(FOOTER_MARK_SIZE, INK)) + .margin(new DocumentInsets(new IndigoFlow(rowTopPx) + .boxAt(FOOTER_MARK_AT, toPx(FOOTER_MARK_SIZE)), 0, 0, 0))); + }); + row.addSection("FooterIdentity", identity -> { + identity.spacing(0); + IndigoFlow cell = new IndigoFlow(rowTopPx); + renderName(identity, footer, cell); + renderAddress(identity, footer, cell); + renderChannels(identity, footer, cell); + }); + }); + } + + private static void renderName(SectionBuilder identity, ProposalFooter footer, + IndigoFlow cell) { + if (footer.name().isBlank()) { + return; + } + identity.addParagraph(p -> p + .name("FooterName") + .text(footer.name()) + .textStyle(bold(FOOTER_NAME_SIZE, INK)) + .margin(new DocumentInsets( + cell.capAt(FOOTER_NAME_CAP, FOOTER_NAME_SIZE, true), 0, 0, 0))); + } + + /** The registered address, which the design runs as one line however it is authored. */ + private static void renderAddress(SectionBuilder identity, ProposalFooter footer, + IndigoFlow cell) { + if (footer.addressLines().isEmpty()) { + return; + } + identity.addParagraph(p -> p + .name("FooterAddress") + .text(String.join(", ", footer.addressLines())) + .textStyle(plain(FOOTER_BODY_SIZE, BODY)) + .margin(new DocumentInsets( + cell.capAt(FOOTER_ADDRESS_CAP, FOOTER_BODY_SIZE, false), 0, 0, 0))); + } + + /** + * The channels, each linked, divided by the design's own separator. + * + *

    A confidentiality line closes the same line rather than opening one of + * its own: the design's last line already ends against the bottom margin, so + * a fourth line in the foot would carry the whole block onto a second page. + * It is set as prose and not as a channel — a notice is a sentence, and a + * sentence is not somewhere a reader can be sent.

    + */ + private static void renderChannels(SectionBuilder identity, ProposalFooter footer, + IndigoFlow cell) { + List contacts = footer.contacts(); + String notice = footer.confidentiality(); + if (contacts.isEmpty() && notice.isBlank()) { + return; + } + double top = cell.capAt(FOOTER_CONTACT_CAP, FOOTER_BODY_SIZE, false); + identity.addParagraph(p -> { + p.name("FooterChannels").margin(new DocumentInsets(top, 0, 0, 0)); + DocumentTextStyle body = plain(FOOTER_BODY_SIZE, BODY); + DocumentTextStyle divider = plain(FOOTER_BODY_SIZE, RULE_STRONG); + for (int i = 0; i < contacts.size(); i++) { + if (i > 0) { + p.inlineText(SEPARATOR, divider); + } + appendChannel(p, contacts.get(i), body); + } + if (!notice.isBlank()) { + if (!contacts.isEmpty()) { + p.inlineText(SEPARATOR, divider); + } + p.inlineText(notice, body); + } + }); + } + + /** A channel, made followable when its printed shape says what it is. */ + private static void appendChannel(ParagraphBuilder p, String contact, + DocumentTextStyle body) { + DocumentLinkOptions link = ContactUri.channelLink(contact); + if (link == null) { + p.inlineText(contact, body); + } else { + p.inlineText(contact, body, link); + } + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/proposal/presets/IndigoFlow.java b/templates/src/main/java/com/demcha/compose/document/templates/proposal/presets/IndigoFlow.java new file mode 100644 index 000000000..51032af3e --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/proposal/presets/IndigoFlow.java @@ -0,0 +1,165 @@ +package com.demcha.compose.document.templates.proposal.presets; + +import com.demcha.compose.document.dsl.LineBuilder; +import com.demcha.compose.document.dsl.RowBuilder; +import com.demcha.compose.document.dsl.SectionBuilder; +import com.demcha.compose.document.node.DocumentNode; +import com.demcha.compose.document.node.HorizontalAlign; +import com.demcha.compose.document.node.LayerAlign; +import com.demcha.compose.document.style.DocumentColor; +import com.demcha.compose.document.style.DocumentInsets; + +import java.util.List; +import java.util.function.Consumer; + +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.RULE_SOFT; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.RULE_THIN; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.px; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.toPx; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.topBearing; + +/** + * A running cursor down a column, and the small shapes the sheet repeats. + * + *

    The design is a map of cap tops rather than a stack of gaps. A cursor + * remembers how far down the column the layout has reached, and each block asks + * for the margin that puts its own cap top where the map says — so a block moves + * by changing one number in {@link IndigoStyles}, and the blocks around it do + * not have to be re-derived.

    + * + *

    A block whose content ran past where the map expected it leaves the cursor + * below the next block's stated position, and the margin that would put that + * block back where the map says is a negative one — which would draw it over + * what overran. Every margin is therefore floored at nothing: heavier content + * pushes what follows down, onto a second page if it comes to that, instead of + * printing on top of it.

    + */ +final class IndigoFlow { + + private double bottomPx; + + /** + * A cursor starting at a design y. + * + * @param originPx where the column begins, in design pixels + */ + IndigoFlow(double originPx) { + this.bottomPx = originPx; + } + + /** + * The top margin that puts a text box's caps at a design y. + * + *

    A text box starts above its own first cap by the face's top bearing, + * which is why a cap top and a box top are not the same number.

    + * + * @param capTopPx where the design puts the cap top + * @param size the type size + * @param bold whether the type is bold, which has its own bearing + * @return the margin to set on the block, never less than nothing + */ + double capAt(double capTopPx, double size, boolean bold) { + double boxTopPx = capTopPx - toPx(topBearing(size, bold)); + double margin = px(boxTopPx - bottomPx); + bottomPx = boxTopPx + toPx(size); + return Math.max(0, margin); + } + + /** + * The top margin for a box with no bearing: a rule, a tile, an image, or a + * nested block that measures its own height. + * + * @param topPx where the design puts the box's top + * @param heightPx how tall the box is, in design pixels + * @return the margin to set on the block, never less than nothing + */ + double boxAt(double topPx, double heightPx) { + double margin = px(topPx - bottomPx); + bottomPx = topPx + heightPx; + return Math.max(0, margin); + } + + /** + * Declares where a block that measured its own height has reached — a + * wrapped paragraph, whose line count the map states rather than the engine. + * + * @param reachedPx the design y the block ends at + */ + void advanceTo(double reachedPx) { + this.bottomPx = reachedPx; + } + + // ------------------------------------------------------------------ + // The shapes the sheet repeats + // ------------------------------------------------------------------ + + /** + * A row wrapped in a layer, so it can sit inside a row cell. + * + *

    A row nested in a row cell is refused by the layout compiler, and + * through a section too. A row wrapped in a LayerStack layer lays out + * horizontally there.

    + * + * @param parent the section the row belongs to + * @param name the row's name, which its holder and layer extend + * @param spec the row itself + */ + static void layeredRow(SectionBuilder parent, String name, Consumer spec) { + SectionBuilder holder = new SectionBuilder(); + holder.name(name + "Holder"); + holder.addRow(name, spec); + DocumentNode node = holder.build(); + parent.addLayerStack(stack -> stack + .name(name + "Layer") + .layer(node, LayerAlign.TOP_LEFT, 0)); + } + + /** + * A horizontal rule of the sheet's own weight. + * + * @param line the line being built + * @param name the rule's name + * @param width how wide it runs + * @param color its colour + * @return the line, for chaining + */ + static LineBuilder rule(LineBuilder line, String name, double width, DocumentColor color) { + return line.name(name).horizontal(width).thickness(RULE_THIN).color(color); + } + + /** + * One of the two hairlines that split the sheet's halves. + * + * @param row the row the hairline is a cell of + * @param name the hairline's name + * @param offsetFromRowTopPx how far below the row's top it starts + * @param heightPx how far it runs + */ + static void columnDivider(RowBuilder row, String name, double offsetFromRowTopPx, + double heightPx) { + row.addSection(name, cell -> { + cell.spacing(0); + cell.addLine(line -> line + .name(name + "Rule") + .vertical(px(heightPx)) + .thickness(RULE_THIN) + .color(RULE_SOFT) + .margin(new DocumentInsets(px(offsetFromRowTopPx), 0, 0, 0))); + }); + } + + /** + * A packaged mark, boxed so it can be centred inside a disc or a tile. + * + * @param token the mark's token + * @param allowed the set the block draws from + * @param size how large to draw it + * @return the boxed mark + */ + static DocumentNode glyph(String token, List allowed, double size) { + SectionBuilder holder = new SectionBuilder(); + holder.name(token + "Glyph").spacing(0); + holder.addSvgIcon(IndigoIcons.icon(token, allowed), size, HorizontalAlign.CENTER); + return holder.build(); + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/proposal/presets/IndigoIcons.java b/templates/src/main/java/com/demcha/compose/document/templates/proposal/presets/IndigoIcons.java new file mode 100644 index 000000000..4c65c8154 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/proposal/presets/IndigoIcons.java @@ -0,0 +1,96 @@ +package com.demcha.compose.document.templates.proposal.presets; + +import com.demcha.compose.document.svg.SvgIcon; + +import java.io.IOException; +import java.io.InputStream; +import java.io.UncheckedIOException; +import java.nio.charset.StandardCharsets; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Stream; + +/** + * The marks the Indigo proposal packages. + * + *

    Two sets of four, and they are not interchangeable: the header's marks are + * drawn in the accent for the pale disc they sit on, and the band's in white for + * the near-black tile. Naming a band mark on a header disc draws white on nearly + * white, so each set belongs to the block it was cut for. A tile that names no + * mark at all is still drawn — the disc and the tile are the design's shapes + * rather than containers for a particular drawing.

    + */ +final class IndigoIcons { + + /** The marks a document may name on a header disc. */ + static final List HEADER_TOKENS = List.of( + "date", "identifier", "valid", "prepared-by"); + + /** The marks a document may name on a band tile. */ + static final List BAND_TOKENS = List.of( + "global", "infrastructure", "secure", "apis"); + + /** Every mark this preset packages. */ + static final List TOKENS = + Stream.concat(HEADER_TOKENS.stream(), BAND_TOKENS.stream()).toList(); + + private static final String ICON_ROOT = "/templates/proposal/indigo/icons/"; + + private static final Map CACHE = new ConcurrentHashMap<>(); + + private IndigoIcons() { + } + + /** + * The packaged mark a token names, if the block it was named on can carry + * it. + * + *

    The wrong set is refused rather than drawn: a band mark on a header + * disc is white on nearly white, which renders as nothing at all and reads + * as a bug in the preset rather than a token in the document.

    + * + * @param token one of this preset's tokens + * @param allowed the set the block draws from + * @return the parsed icon + * @throws IllegalArgumentException when the token names no mark this block + * can draw, listing the ones it can — an icon token is data, so a + * wrong one is a data error and not a missing resource + */ + static SvgIcon icon(String token, List allowed) { + if (!allowed.contains(token)) { + throw new IllegalArgumentException( + "Unknown indigo proposal icon token '" + token + + "'. This block draws " + allowed + "."); + } + return CACHE.computeIfAbsent(token, IndigoIcons::read); + } + + /** + * Whether the document named a mark at all, for a tile that may carry none. + * + *

    It asks whether one was named, not whether this preset packages it: a + * token naming nothing here is a data error, and + * {@link #icon(String, List)} reports it by name — which a silently empty + * tile would hide.

    + * + * @param token the token a document states + * @return {@code true} when the document named one + */ + static boolean has(String token) { + return token != null && !token.isBlank(); + } + + private static SvgIcon read(String token) { + String resourcePath = ICON_ROOT + token + ".svg"; + try (InputStream input = IndigoIcons.class.getResourceAsStream(resourcePath)) { + if (input == null) { + throw new IllegalStateException("Missing indigo proposal icon: " + resourcePath); + } + return SvgIcon.parse(new String(input.readAllBytes(), StandardCharsets.UTF_8)); + } catch (IOException e) { + throw new UncheckedIOException( + "Failed to read indigo proposal icon: " + resourcePath, e); + } + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/proposal/presets/IndigoLower.java b/templates/src/main/java/com/demcha/compose/document/templates/proposal/presets/IndigoLower.java new file mode 100644 index 000000000..a70d28636 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/proposal/presets/IndigoLower.java @@ -0,0 +1,386 @@ +package com.demcha.compose.document.templates.proposal.presets; + +import com.demcha.compose.document.dsl.PageFlowBuilder; +import com.demcha.compose.document.dsl.ParagraphBuilder; +import com.demcha.compose.document.dsl.SectionBuilder; +import com.demcha.compose.document.dsl.ShapeContainerBuilder; +import com.demcha.compose.document.node.DocumentNode; +import com.demcha.compose.document.node.RowVerticalAlign; +import com.demcha.compose.document.node.TextAlign; +import com.demcha.compose.document.style.DocumentInsets; +import com.demcha.compose.document.style.DocumentRowColumn; +import com.demcha.compose.document.style.DocumentStroke; +import com.demcha.compose.document.table.DocumentTableCell; +import com.demcha.compose.document.table.DocumentTableColumn; +import com.demcha.compose.document.table.DocumentTableStyle; +import com.demcha.compose.document.templates.data.proposal.ProposalInvestment; +import com.demcha.compose.document.templates.data.proposal.ProposalScope; +import com.demcha.compose.document.templates.data.proposal.ProposalTermsBlock; + +import java.util.List; + +import static com.demcha.compose.document.templates.proposal.presets.IndigoFlow.columnDivider; +import static com.demcha.compose.document.templates.proposal.presets.IndigoFlow.layeredRow; +import static com.demcha.compose.document.templates.proposal.presets.IndigoFlow.rule; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.ACCENT; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.BODY; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.DIVIDER_LOWER_H_PX; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.DIVIDER_LOWER_TOP_PX; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.HALF; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.INK; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.INVEST_CELL_PAD_R; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.INVEST_FIRST_ROW_CAP; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.INVEST_HEADING_CAP; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.INVEST_ROW_PITCH_PX; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.INVEST_RULE_OFFSET_PX; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.INVEST_SIZE; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.LABEL_SIZE; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.LOWER_GAP_W; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.LOWER_LEFT_W; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.LOWER_RIGHT_INSET; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.LOWER_RIGHT_W; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.NOTES_FIRST_CAP; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.NOTES_HEADING_CAP; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.NOTES_PITCH_PX; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.NOTE_SIZE; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.OVERVIEW_HEADING_CAP; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.OVERVIEW_INTRO_CAP; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.OVERVIEW_INTRO_PITCH_PX; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.OVERVIEW_MEASURE_PX; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.OVERVIEW_SIZE; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.RULE_SOFT; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.RULE_THIN; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.STEP_CIRCLE; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.STEP_CIRCLE_PX; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.STEP_COL_W; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.STEP_CONNECTOR_W; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.STEP_FIRST_CIRCLE_AT; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.STEP_NUM_SIZE; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.STEP_PITCH_PX; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.STEP_SUB_OFFSET_PX; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.STEP_SUB_SIZE; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.STEP_TITLE_OFFSET_PX; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.STEP_TITLE_SIZE; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.TINT; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.TOTAL_CARD_AT; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.TOTAL_CARD_H_PX; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.TOTAL_CARD_RADIUS; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.TOTAL_FIGURE_CAP; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.TOTAL_FIGURE_SIZE; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.TOTAL_LABEL_SIZE; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.TOTAL_PAD_L; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.TOTAL_PAD_R; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.bold; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.boxBottomPx; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.plain; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.px; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.toPx; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.topBearing; + +/** + * The lower half of the sheet: the numbered plan at the left, the money at the + * right. + */ +final class IndigoLower { + + /** Every cell's stroke is white and weightless; the rules are lines inside them. */ + private static final DocumentTableStyle CELL_STYLE = DocumentTableStyle.builder() + .padding(new DocumentInsets(0, INVEST_CELL_PAD_R, 0, 0)) + .stroke(DocumentStroke.of(INK, 0)) + .build(); + + private IndigoLower() { + } + + /** + * The lower row. + * + * @param page the page flow + * @param scope the plan: a heading, an opening paragraph and numbered steps + * @param investment the money: a heading, its rows and the total + * @param terms the notes under the total + * @param flow the sheet's cursor + */ + static void render(PageFlowBuilder page, ProposalScope scope, ProposalInvestment investment, + ProposalTermsBlock terms, IndigoFlow flow) { + double rowTopPx = OVERVIEW_HEADING_CAP - toPx(topBearing(LABEL_SIZE, true)); + // How far the row reaches is the last step's, and a plan with no steps + // is measured as one: a count of nothing would put the row's foot above + // where its first step would have started. + int steps = Math.max(1, scope.items().size()); + double bottomPx = boxBottomPx(STEP_FIRST_CIRCLE_AT + (steps - 1) * STEP_PITCH_PX + + STEP_SUB_OFFSET_PX, STEP_SUB_SIZE, false); + double top = flow.boxAt(rowTopPx, bottomPx - rowTopPx); + page.addRow("LowerRow", row -> { + row.spacing(0) + .margin(new DocumentInsets(top, 0, 0, 0)) + .columns(DocumentRowColumn.fixed(LOWER_LEFT_W), + DocumentRowColumn.fixed(LOWER_GAP_W), + DocumentRowColumn.weight(1)); + row.addSection("LowerLeft", left -> { + left.spacing(0); + renderPlan(left, scope, new IndigoFlow(rowTopPx)); + }); + columnDivider(row, "ColumnDividerLower", + DIVIDER_LOWER_TOP_PX - rowTopPx, DIVIDER_LOWER_H_PX); + row.addSection("LowerRight", right -> { + right.spacing(0).padding(new DocumentInsets(0, LOWER_RIGHT_INSET, 0, 0)); + IndigoFlow cell = new IndigoFlow(rowTopPx); + renderInvestment(right, investment, cell); + renderTotal(right, investment, cell); + renderNotes(right, terms, cell); + }); + }); + } + + private static void renderPlan(SectionBuilder left, ProposalScope scope, IndigoFlow cell) { + if (!scope.heading().isBlank()) { + left.addParagraph(p -> p + .name("PlanHeading") + .text(scope.heading()) + .textStyle(bold(LABEL_SIZE, ACCENT)) + .margin(new DocumentInsets( + cell.capAt(OVERVIEW_HEADING_CAP, LABEL_SIZE, true), 0, 0, 0))); + } + if (!scope.intro().isBlank()) { + double measureInset = Math.max(0, LOWER_LEFT_W - px(OVERVIEW_MEASURE_PX)); + double gap = cell.capAt(OVERVIEW_INTRO_CAP, OVERVIEW_SIZE, false); + left.addParagraph(p -> p + .name("PlanIntro") + .text(scope.intro()) + .textStyle(plain(OVERVIEW_SIZE, BODY)) + .lineSpacing(px(OVERVIEW_INTRO_PITCH_PX) - OVERVIEW_SIZE) + .margin(new DocumentInsets(gap, measureInset, 0, 0))); + // Three lines at this measure, which the map states rather than the + // engine reporting afterwards. + cell.advanceTo(OVERVIEW_INTRO_CAP + 2 * OVERVIEW_INTRO_PITCH_PX + + toPx(OVERVIEW_SIZE)); + } + renderSteps(left, scope.items(), cell); + } + + private static void renderSteps(SectionBuilder left, List items, + IndigoFlow cell) { + for (int i = 0; i < items.size(); i++) { + ProposalScope.Item item = items.get(i); + int index = i; + boolean last = i == items.size() - 1; + double circleAt = STEP_FIRST_CIRCLE_AT + i * STEP_PITCH_PX; + double top = cell.boxAt(circleAt, last ? STEP_CIRCLE_PX : STEP_PITCH_PX); + layeredRow(left, "Step_" + index, row -> { + row.spacing(0) + .margin(new DocumentInsets(top, 0, 0, 0)) + .columns(DocumentRowColumn.fixed(STEP_COL_W), + DocumentRowColumn.weight(1)); + row.addSection("StepMark_" + index, mark -> { + mark.spacing(0); + mark.add(numberedDisc(index, item.number())); + if (!last) { + // The tick that joins this circle to the next. It is drawn + // per step rather than as one rail behind them all, + // because the design's gap is the shape's, not a rule's. + double gap = px(STEP_PITCH_PX - STEP_CIRCLE_PX); + double indent = (STEP_CIRCLE - STEP_CONNECTOR_W) * HALF; + mark.addLine(line -> line + .name("StepConnector_" + index) + .vertical(gap) + .thickness(STEP_CONNECTOR_W) + .color(TINT) + .margin(new DocumentInsets(0, 0, 0, indent))); + } + }); + row.addSection("StepText_" + index, text -> { + text.spacing(0); + IndigoFlow stack = new IndigoFlow(circleAt); + text.addParagraph(p -> p + .name("StepTitle_" + index) + .text(item.title()) + .textStyle(bold(STEP_TITLE_SIZE, INK)) + .margin(new DocumentInsets(stack.capAt( + circleAt + STEP_TITLE_OFFSET_PX, STEP_TITLE_SIZE, true), + 0, 0, 0))); + text.addParagraph(p -> p + .name("StepSubtitle_" + index) + .text(item.description()) + .textStyle(plain(STEP_SUB_SIZE, BODY)) + .margin(new DocumentInsets(stack.capAt( + circleAt + STEP_SUB_OFFSET_PX, STEP_SUB_SIZE, false), + 0, 0, 0))); + }); + }); + } + } + + /** + * A step's numbered disc. + * + *

    The number is the document's: a plan that continues one from elsewhere + * says so, and one that states none is numbered by its position.

    + */ + private static DocumentNode numberedDisc(int index, String stated) { + String number = stated.isBlank() ? String.format("%02d", index + 1) : stated; + return new ShapeContainerBuilder() + .name("StepCircle_" + index) + .circle(STEP_CIRCLE) + .fillColor(TINT) + .center(new ParagraphBuilder() + .name("StepNumber_" + index) + .text(number) + .textStyle(bold(STEP_NUM_SIZE, ACCENT)) + .align(TextAlign.CENTER) + .build()) + .build(); + } + + /** + * The money table. + * + *

    The heading travels with it. {@code keepWithNext} is a section's and a + * line's property and not a paragraph's, so rather than ask the label to + * hold on to what follows it, the label and the table are one kept-together + * section — a heading left alone at the foot of a page is what that + * prevents.

    + */ + private static void renderInvestment(SectionBuilder right, ProposalInvestment investment, + IndigoFlow cell) { + List rows = investment.rows(); + double headingTopPx = INVEST_HEADING_CAP - toPx(topBearing(LABEL_SIZE, true)); + double tableTopPx = INVEST_FIRST_ROW_CAP - toPx(topBearing(INVEST_SIZE, false)); + double tableBottomPx = boxBottomPx(INVEST_FIRST_ROW_CAP + + Math.max(0, rows.size() - 1) * INVEST_ROW_PITCH_PX, INVEST_SIZE, false); + double top = cell.boxAt(headingTopPx, tableBottomPx - headingTopPx); + right.addSection("Investment", group -> { + group.spacing(0) + .keepTogether() + .margin(new DocumentInsets(top, 0, 0, 0)); + IndigoFlow inner = new IndigoFlow(headingTopPx); + if (!investment.heading().isBlank()) { + group.addParagraph(p -> p + .name("InvestmentHeading") + .text(investment.heading()) + .textStyle(bold(LABEL_SIZE, ACCENT)) + .margin(new DocumentInsets( + inner.capAt(INVEST_HEADING_CAP, LABEL_SIZE, true), 0, 0, 0))); + } + if (rows.isEmpty()) { + return; + } + double tableTop = inner.boxAt(tableTopPx, tableBottomPx - tableTopPx); + group.addTable(table -> { + table.name("InvestmentTable") + .width(LOWER_RIGHT_W) + .columns(DocumentTableColumn.fixed(LOWER_RIGHT_W)) + // A cell's stroke draws all four edges and there is no + // per-edge control, so the default put a grid box round + // every row. The rules the design does draw are lines + // inside the cells. + .defaultCellStyle(CELL_STYLE) + .margin(new DocumentInsets(tableTop, 0, 0, 0)); + for (int i = 0; i < rows.size(); i++) { + table.rowCells(DocumentTableCell + .node(investmentRow(rows.get(i), i, i == rows.size() - 1))); + } + }); + }); + } + + private static DocumentNode investmentRow(ProposalInvestment.Row entry, int index, + boolean last) { + SectionBuilder cell = new SectionBuilder(); + cell.name("InvestmentCell_" + index).spacing(0); + cell.addRow("InvestmentRow_" + index, row -> { + row.spacing(0).weights(HALF, HALF); + row.addParagraph(p -> p + .name("InvestmentLabel_" + index) + .text(entry.label()) + .textStyle(plain(INVEST_SIZE, INK))); + row.addParagraph(p -> p + .name("InvestmentAmount_" + index) + .text(entry.amount()) + .textStyle(plain(INVEST_SIZE, INK)) + .align(TextAlign.RIGHT)); + }); + if (!last) { + double gap = px(INVEST_RULE_OFFSET_PX) - INVEST_SIZE + + topBearing(INVEST_SIZE, false); + cell.addLine(line -> rule(line, "InvestmentRule_" + index, LOWER_RIGHT_W, RULE_SOFT) + .margin(new DocumentInsets(Math.max(0, gap), 0, 0, 0))); + cell.addSpacer(spacer -> spacer.height(Math.max(0, + px(INVEST_ROW_PITCH_PX) - INVEST_SIZE - Math.max(0, gap) - RULE_THIN))); + } + return cell.build(); + } + + /** + * The total's card. + * + *

    The design's card is a fixed band, but its content is only the one row: + * without a bottom pad it closes early, and everything under it rides up by + * that much. The pad is the difference, computed rather than tuned.

    + */ + private static void renderTotal(SectionBuilder right, ProposalInvestment investment, + IndigoFlow cell) { + if (investment.totalLabel().isBlank() && investment.totalAmount().isBlank()) { + return; + } + double top = cell.boxAt(TOTAL_CARD_AT, TOTAL_CARD_H_PX); + double figureBoxTopPx = TOTAL_FIGURE_CAP - toPx(topBearing(TOTAL_FIGURE_SIZE, true)); + double contentBottomPx = figureBoxTopPx + toPx(TOTAL_FIGURE_SIZE); + double padBottom = Math.max(0, px(TOTAL_CARD_AT + TOTAL_CARD_H_PX - contentBottomPx)); + right.addSection("TotalCard", card -> { + card.spacing(0) + .keepTogether() + .fillColor(TINT) + .cornerRadius(TOTAL_CARD_RADIUS) + .padding(new DocumentInsets(0, TOTAL_PAD_R, padBottom, TOTAL_PAD_L)) + .margin(new DocumentInsets(top, 0, 0, 0)); + IndigoFlow inner = new IndigoFlow(TOTAL_CARD_AT); + double rowTop = inner.boxAt(figureBoxTopPx, TOTAL_CARD_H_PX); + layeredRow(card, "TotalRow", row -> { + row.spacing(0) + // The label's ink band and the larger figure's share a + // centre line, not a baseline. + .verticalAlign(RowVerticalAlign.CENTER) + .margin(new DocumentInsets(rowTop, 0, 0, 0)) + .weights(HALF, HALF); + row.addParagraph(p -> p + .name("TotalLabel") + .text(investment.totalLabel()) + .textStyle(plain(TOTAL_LABEL_SIZE, ACCENT))); + row.addParagraph(p -> p + .name("TotalFigure") + .text(investment.totalAmount()) + .textStyle(bold(TOTAL_FIGURE_SIZE, ACCENT)) + .align(TextAlign.RIGHT)); + }); + }); + } + + private static void renderNotes(SectionBuilder right, ProposalTermsBlock terms, + IndigoFlow cell) { + if (terms.heading().isBlank() && terms.items().isEmpty()) { + return; + } + if (!terms.heading().isBlank()) { + right.addParagraph(p -> p + .name("NotesHeading") + .text(terms.heading()) + .textStyle(bold(LABEL_SIZE, ACCENT)) + .margin(new DocumentInsets( + cell.capAt(NOTES_HEADING_CAP, LABEL_SIZE, true), 0, 0, 0))); + } + if (terms.items().isEmpty()) { + return; + } + double top = cell.capAt(NOTES_FIRST_CAP, NOTE_SIZE, false); + right.addList(list -> list + .name("NotesList") + .items(terms.items()) + .bullet() + .textStyle(plain(NOTE_SIZE, BODY)) + .itemSpacing(Math.max(0, px(NOTES_PITCH_PX) - NOTE_SIZE)) + .margin(new DocumentInsets(top, 0, 0, 0))); + cell.advanceTo(NOTES_FIRST_CAP + (terms.items().size() - 1) * NOTES_PITCH_PX + + toPx(NOTE_SIZE)); + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/proposal/presets/IndigoMasthead.java b/templates/src/main/java/com/demcha/compose/document/templates/proposal/presets/IndigoMasthead.java new file mode 100644 index 000000000..5b5f01b47 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/proposal/presets/IndigoMasthead.java @@ -0,0 +1,346 @@ +package com.demcha.compose.document.templates.proposal.presets; + +import com.demcha.compose.document.dsl.PageFlowBuilder; +import com.demcha.compose.document.dsl.SectionBuilder; +import com.demcha.compose.document.node.DocumentLinkOptions; +import com.demcha.compose.document.node.TextAlign; +import com.demcha.compose.document.style.DocumentInsets; +import com.demcha.compose.document.style.DocumentRowColumn; +import com.demcha.compose.document.templates.core.identity.ContactUri; +import com.demcha.compose.document.templates.data.proposal.ProposalAttention; +import com.demcha.compose.document.templates.data.proposal.ProposalBrand; +import com.demcha.compose.document.templates.data.proposal.ProposalMetaLine; +import com.demcha.compose.document.templates.data.proposal.ProposalRecipient; +import com.demcha.compose.document.templates.data.proposal.ProposalTitleLines; + +import java.util.List; + +import static com.demcha.compose.document.templates.proposal.presets.IndigoFlow.columnDivider; +import static com.demcha.compose.document.templates.proposal.presets.IndigoFlow.layeredRow; +import static com.demcha.compose.document.templates.proposal.presets.IndigoFlow.rule; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.ACCENT; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.ACCENT_RULE_AT; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.ACCENT_RULE_W; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.ADDRESS_CAP; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.ADDRESS_EXTRA_PITCH_PX; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.ATTN_EMAIL_CAP; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.ATTN_LABEL_CAP; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.ATTN_NAME_CAP; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.ATTN_NAME_SIZE; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.ATTN_PHONE_CAP; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.ATTN_ROLE_CAP; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.ATTN_ROLE_SIZE; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.BODY; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.BODY_SIZE; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.COMPANY_CAP; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.COMPANY_SIZE; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.CONTACT_SIZE; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.CONTENT_W; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.DIVIDER_UPPER_H_PX; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.DIVIDER_UPPER_TOP_PX; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.HALF; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.HEADLINE_CAP; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.HEADLINE_EXTRA_PITCH_PX; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.HEADLINE_SIZE; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.HERO_W; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.INK; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.INTRO_GAP_W; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.INTRO_LEFT_W; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.KICKER_CAP; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.KICKER_RULE_AT; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.KICKER_RULE_W; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.KICKER_SIZE; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.LABEL_SIZE; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.LEFT_DIVIDER_AT; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.LEFT_DIVIDER_W; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.MASTHEAD_RULE_AT; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.META_CIRCLE; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.META_CIRCLE_AT; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.META_CIRCLE_PX; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.META_GLYPH; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.META_LABEL_CAP; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.META_LABEL_SIZE; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.META_PITCH_PX; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.META_VALUE_CAP; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.META_VALUE_SIZE; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.PREPARED_LABEL_CAP; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.RULE_ACCENT; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.RULE_SOFT; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.RULE_STRONG; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.SUBTITLE_CAP; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.SUBTITLE_MEASURE_PX; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.SUBTITLE_PITCH_PX; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.TINT; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.WORDMARK_CAP; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.WORDMARK_SIZE; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.bold; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.boxBottomPx; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.plain; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.px; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.toPx; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.topBearing; + +/** + * The head of the sheet: the wordmark against the document's label, a rule under + * both, then the addressed party at the left and the headline at the right. + */ +final class IndigoMasthead { + + private IndigoMasthead() { + } + + /** The wordmark on the left, the document's label and its accent rule on the right. */ + static void renderMasthead(PageFlowBuilder page, ProposalBrand brand, IndigoFlow flow) { + double top = flow.boxAt(WORDMARK_CAP - toPx(topBearing(WORDMARK_SIZE, true)), + toPx(WORDMARK_SIZE)); + page.addRow("Masthead", row -> { + row.spacing(0) + .margin(new DocumentInsets(top, 0, 0, 0)) + .columns(DocumentRowColumn.weight(1), DocumentRowColumn.weight(1)); + row.addParagraph(p -> p + .name("Wordmark") + .text(brand.nameLine1()) + .textStyle(bold(WORDMARK_SIZE, INK))); + row.addSection("DocumentLabel", cell -> { + cell.spacing(0); + IndigoFlow inner = new IndigoFlow( + KICKER_CAP - toPx(topBearing(KICKER_SIZE, true))); + cell.addParagraph(p -> p + .name("DocumentLabelText") + .text(brand.documentLabel()) + .textStyle(bold(KICKER_SIZE, INK)) + .align(TextAlign.RIGHT) + .margin(new DocumentInsets( + inner.capAt(KICKER_CAP, KICKER_SIZE, true), 0, 0, 0))); + // A line carries no alignment, so the rule is pushed right by the + // width its cell does not use — derived, not tuned. + double indent = (CONTENT_W * HALF) - KICKER_RULE_W; + cell.addLine(line -> rule(line, "DocumentLabelRule", KICKER_RULE_W, ACCENT) + .thickness(RULE_ACCENT) + .margin(new DocumentInsets( + inner.boxAt(KICKER_RULE_AT, 3), 0, 0, indent))); + }); + }); + } + + /** The full-width rule under the masthead. */ + static void renderMastheadRule(PageFlowBuilder page, IndigoFlow flow) { + double top = flow.boxAt(MASTHEAD_RULE_AT, 1.5); + page.addLine(line -> rule(line, "MastheadRule", CONTENT_W, RULE_STRONG) + .margin(new DocumentInsets(top, 0, 0, 0))); + } + + /** + * The upper half: the addressee and the person at it on the left, the + * headline and the header tiles on the right. + * + *

    Three columns, not two: the middle one is the design's own hairline + * between the two blocks.

    + */ + static void renderIntroRow(PageFlowBuilder page, ProposalRecipient recipient, + ProposalAttention attention, ProposalTitleLines title, + ProposalMetaLine meta, IndigoFlow flow) { + double rowTopPx = PREPARED_LABEL_CAP - toPx(topBearing(LABEL_SIZE, true)); + double top = flow.boxAt(rowTopPx, + boxBottomPx(META_VALUE_CAP, META_VALUE_SIZE, false) - rowTopPx); + page.addRow("IntroRow", row -> { + row.spacing(0) + .margin(new DocumentInsets(top, 0, 0, 0)) + .columns(DocumentRowColumn.fixed(INTRO_LEFT_W), + DocumentRowColumn.fixed(INTRO_GAP_W), + DocumentRowColumn.weight(1)); + row.addSection("IntroLeft", left -> { + left.spacing(0); + IndigoFlow cell = new IndigoFlow(rowTopPx); + renderRecipient(left, recipient, cell); + renderAttention(left, attention, cell); + }); + columnDivider(row, "ColumnDividerUpper", + DIVIDER_UPPER_TOP_PX - rowTopPx, DIVIDER_UPPER_H_PX); + row.addSection("IntroRight", right -> { + right.spacing(0); + IndigoFlow cell = new IndigoFlow(rowTopPx); + renderHeadline(right, title, cell); + renderMetaTiles(right, meta, cell); + }); + }); + } + + private static void renderRecipient(SectionBuilder left, ProposalRecipient recipient, + IndigoFlow cell) { + left.addParagraph(p -> p + .name("RecipientLabel") + .text(recipient.label()) + .textStyle(bold(LABEL_SIZE, ACCENT)) + .margin(new DocumentInsets( + cell.capAt(PREPARED_LABEL_CAP, LABEL_SIZE, true), 0, 0, 0))); + left.addParagraph(p -> p + .name("RecipientName") + .text(recipient.name()) + .textStyle(bold(COMPANY_SIZE, INK)) + .margin(new DocumentInsets( + cell.capAt(COMPANY_CAP, COMPANY_SIZE, true), 0, 0, 0))); + + List lines = recipient.addressLines(); + for (int i = 0; i < lines.size(); i++) { + String line = lines.get(i); + int index = i; + // The design maps three address lines; a fourth and beyond continue + // on the pitch the mapped ones establish. + double capPx = index < ADDRESS_CAP.length + ? ADDRESS_CAP[index] + : ADDRESS_CAP[ADDRESS_CAP.length - 1] + + (index - ADDRESS_CAP.length + 1) * ADDRESS_EXTRA_PITCH_PX; + double gap = cell.capAt(capPx, CONTACT_SIZE, false); + left.addParagraph(p -> p + .name("RecipientAddress_" + index) + .text(line) + .textStyle(plain(CONTACT_SIZE, BODY)) + .margin(new DocumentInsets(gap, 0, 0, 0))); + } + + left.addLine(line -> rule(line, "RecipientDivider", LEFT_DIVIDER_W, RULE_SOFT) + .margin(new DocumentInsets(cell.boxAt(LEFT_DIVIDER_AT, 1.5), 0, 0, 0))); + } + + private static void renderAttention(SectionBuilder left, ProposalAttention attention, + IndigoFlow cell) { + left.addParagraph(p -> p + .name("AttentionLabel") + .text(attention.label()) + .textStyle(bold(LABEL_SIZE, ACCENT)) + .margin(new DocumentInsets( + cell.capAt(ATTN_LABEL_CAP, LABEL_SIZE, true), 0, 0, 0))); + left.addParagraph(p -> p + .name("AttentionName") + .text(attention.name()) + .textStyle(bold(ATTN_NAME_SIZE, INK)) + .margin(new DocumentInsets( + cell.capAt(ATTN_NAME_CAP, ATTN_NAME_SIZE, true), 0, 0, 0))); + left.addParagraph(p -> p + .name("AttentionRole") + .text(attention.role()) + .textStyle(plain(ATTN_ROLE_SIZE, BODY)) + .margin(new DocumentInsets( + cell.capAt(ATTN_ROLE_CAP, ATTN_ROLE_SIZE, false), 0, 0, 0))); + reachable(left, "AttentionEmail", attention.email(), + ContactUri.mailLink(attention.email()), cell, ATTN_EMAIL_CAP); + reachable(left, "AttentionPhone", attention.phone(), + ContactUri.telLink(attention.phone()), cell, ATTN_PHONE_CAP); + } + + /** + * A contact line, made followable, at the design y the map gives it. + * + *

    The cursor is asked for its margin inside the guard rather than outside + * it. A line the document does not carry was never laid out, and a cursor + * advanced past it would place the next line from where the missing one + * would have ended instead of where the map puts it.

    + */ + private static void reachable(SectionBuilder parent, String name, String value, + DocumentLinkOptions link, IndigoFlow cell, double capPx) { + if (value.isBlank()) { + return; + } + double gap = cell.capAt(capPx, CONTACT_SIZE, false); + parent.addParagraph(p -> { + p.name(name).margin(new DocumentInsets(gap, 0, 0, 0)); + if (link == null) { + p.text(value).textStyle(plain(CONTACT_SIZE, BODY)); + } else { + p.inlineText(value, plain(CONTACT_SIZE, BODY), link); + } + }); + } + + /** + * The headline, its accent rule and the paragraph under it. + * + *

    The rag is a composition decision, so each designed line is its own + * paragraph. Wrapping one string would re-break it at the engine's measure + * rather than where the design breaks it.

    + */ + private static void renderHeadline(SectionBuilder right, ProposalTitleLines title, + IndigoFlow cell) { + List lines = title.lines(); + for (int i = 0; i < lines.size(); i++) { + String line = lines.get(i); + int index = i; + double capPx = index < HEADLINE_CAP.length + ? HEADLINE_CAP[index] + : HEADLINE_CAP[HEADLINE_CAP.length - 1] + + (index - HEADLINE_CAP.length + 1) * HEADLINE_EXTRA_PITCH_PX; + double gap = cell.capAt(capPx, HEADLINE_SIZE, true); + right.addParagraph(p -> p + .name("HeadlineLine_" + index) + .text(line) + .textStyle(bold(HEADLINE_SIZE, INK)) + .margin(new DocumentInsets(gap, 0, 0, 0))); + } + + right.addLine(line -> rule(line, "HeadlineRule", ACCENT_RULE_W, ACCENT) + .thickness(RULE_ACCENT) + .margin(new DocumentInsets(cell.boxAt(ACCENT_RULE_AT, 3), 0, 0, 0))); + + List standfirst = title.standfirst(); + if (standfirst.isEmpty()) { + return; + } + double measureInset = Math.max(0, HERO_W - px(SUBTITLE_MEASURE_PX)); + double gap = cell.capAt(SUBTITLE_CAP, BODY_SIZE, false); + right.addParagraph(p -> p + .name("HeadlineStandfirst") + .text(String.join(" ", standfirst)) + .textStyle(plain(BODY_SIZE, BODY)) + // Line spacing is additive points between lines, not a multiple. + .lineSpacing(px(SUBTITLE_PITCH_PX) - BODY_SIZE) + .margin(new DocumentInsets(gap, measureInset, 0, 0))); + // The design sets it in three lines at this measure; the map states that + // rather than asking the engine afterwards. + cell.advanceTo(SUBTITLE_CAP + 2 * SUBTITLE_PITCH_PX + toPx(BODY_SIZE)); + } + + /** The row of marked tiles under the headline. */ + private static void renderMetaTiles(SectionBuilder right, ProposalMetaLine meta, + IndigoFlow cell) { + List entries = meta.entries(); + if (entries.isEmpty()) { + return; + } + double top = cell.boxAt(META_CIRCLE_AT, + boxBottomPx(META_VALUE_CAP, META_VALUE_SIZE, false) - META_CIRCLE_AT); + layeredRow(right, "MetaTiles", row -> { + row.spacing(0).margin(new DocumentInsets(top, 0, 0, 0)); + DocumentRowColumn[] columns = new DocumentRowColumn[entries.size()]; + for (int i = 0; i < entries.size(); i++) { + columns[i] = i < META_PITCH_PX.length + ? DocumentRowColumn.fixed(px(META_PITCH_PX[i])) + : DocumentRowColumn.weight(1); + } + row.columns(columns); + for (int i = 0; i < entries.size(); i++) { + ProposalMetaLine.Entry entry = entries.get(i); + int index = i; + row.addSection("MetaTile_" + index, group -> { + group.spacing(0); + IndigoFlow stack = new IndigoFlow(META_CIRCLE_AT); + group.add(IndigoWidgets.disc("MetaCircle_" + index, TINT, + entry.icon(), META_CIRCLE, META_GLYPH)); + stack.boxAt(META_CIRCLE_AT, META_CIRCLE_PX); + group.addParagraph(p -> p + .name("MetaLabel_" + index) + .text(entry.label()) + .textStyle(plain(META_LABEL_SIZE, BODY)) + .margin(new DocumentInsets( + stack.capAt(META_LABEL_CAP, META_LABEL_SIZE, false), 0, 0, 0))); + group.addParagraph(p -> p + .name("MetaValue_" + index) + .text(entry.value()) + .textStyle(plain(META_VALUE_SIZE, INK)) + .margin(new DocumentInsets( + stack.capAt(META_VALUE_CAP, META_VALUE_SIZE, false), 0, 0, 0))); + }); + } + }); + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/proposal/presets/IndigoProposal.java b/templates/src/main/java/com/demcha/compose/document/templates/proposal/presets/IndigoProposal.java new file mode 100644 index 000000000..897bd60f6 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/proposal/presets/IndigoProposal.java @@ -0,0 +1,123 @@ +package com.demcha.compose.document.templates.proposal.presets; + +import com.demcha.compose.document.api.DocumentSession; +import com.demcha.compose.document.templates.api.DocumentTemplate; +import com.demcha.compose.document.templates.data.proposal.StructuredProposalData; +import com.demcha.compose.document.templates.data.proposal.StructuredProposalDocumentSpec; + +import java.util.Objects; + +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.FLOW_ORIGIN_PX; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.PAGE; +import static com.demcha.compose.document.templates.proposal.presets.IndigoStyles.PAGE_MARGIN; + +/** + * Indigo Proposal — a one-page sales proposal: an addressed head, a tinted band + * about the issuer, a numbered plan beside its price, and a foot that says who + * sent it. + * + *

    The sheet is a single page by design and not by accident: every block's + * position is a cap top in one vertical map ({@link IndigoStyles}), and the + * cursor that turns those into margins ({@link IndigoFlow}) walks the page once, + * top to bottom. Content heavier than the design's own — a sixth step, a fifth + * priced row, an address that wraps — pushes the blocks below it down and can + * carry the foot onto a second page. The preset does not cap what it is given: + * a document that overruns paginates rather than being truncated, and never + * prints one block over another.

    + * + *

    Two places the design fills with its own brand assets are kept as places + * rather than filled: the band's artwork column (see {@link IndigoBand}) and the + * foot's logotype (see {@link IndigoClosing}). The templates artifact carries no + * brand assets, so the copy keeps the measure it was solved against and the foot + * sets whatever monogram the document names.

    + * + *

    The preset consumes the structured proposal model + * ({@link StructuredProposalDocumentSpec}); the icon tokens the data carries + * resolve against the packaged Indigo set ({@link IndigoIcons}). It owns its + * page geometry — A4 and the design's own ink margins — so a session margin set + * by the caller is overwritten; see {@link #RECOMMENDED_MARGIN}.

    + * + *

    Fonts

    + * + *

    The sheet is set in Gothic A1, the family every size was solved against. + * The templates artifact carries no fonts: it arrives with + * {@code graph-compose-fonts} on the classpath, or a caller registers a family + * of that name on the session itself. With neither, the engine substitutes and + * every size is solved for type that is not there.

    + * + *

    Usage:

    + *
    {@code
    + * DocumentTemplate template = IndigoProposal.create();
    + * template.compose(session, spec);
    + * }
    + * + * @since 2.4.0 + */ +public final class IndigoProposal { + + /** + * Stable template identifier. + */ + public static final String ID = "proposal-indigo"; + + /** + * Human-readable display name. + */ + public static final String DISPLAY_NAME = "Indigo Proposal"; + + /** + * Recommended session margin (in points). The preset sets the design's own + * ink margins inside {@code compose}, so callers leave the session margin at + * zero and let the preset own the page frame. + */ + public static final double RECOMMENDED_MARGIN = 0.0; + + private IndigoProposal() { + } + + /** + * Builds the preset. + * + * @return ready-to-use template + */ + public static DocumentTemplate create() { + return new Template(); + } + + private record Template() implements DocumentTemplate { + + @Override + public String id() { + return ID; + } + + @Override + public String displayName() { + return DISPLAY_NAME; + } + + @Override + public void compose(DocumentSession document, StructuredProposalDocumentSpec spec) { + Objects.requireNonNull(document, "document"); + StructuredProposalData data = Objects.requireNonNull(spec, "spec").proposal(); + + document.pageSize(PAGE).margin(PAGE_MARGIN); + + document.pageFlow(page -> { + // spacing(0): every gap on this sheet is a margin the map + // solved, so a flow spacing would be added on top of all of + // them. + page.name("Proposal").spacing(0); + IndigoFlow flow = new IndigoFlow(FLOW_ORIGIN_PX); + IndigoMasthead.renderMasthead(page, data.brand(), flow); + IndigoMasthead.renderMastheadRule(page, flow); + IndigoMasthead.renderIntroRow(page, data.recipient(), data.attention(), + data.title(), data.meta(), flow); + IndigoBand.render(page, data.glance(), flow); + IndigoLower.render(page, data.scope(), data.investment(), data.terms(), flow); + IndigoClosing.renderFooterRule(page, flow); + IndigoClosing.renderFooter(page, data.brand(), data.footer(), flow); + }); + } + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/proposal/presets/IndigoStyles.java b/templates/src/main/java/com/demcha/compose/document/templates/proposal/presets/IndigoStyles.java new file mode 100644 index 000000000..e431f9e84 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/proposal/presets/IndigoStyles.java @@ -0,0 +1,290 @@ +package com.demcha.compose.document.templates.proposal.presets; + +import com.demcha.compose.document.api.DocumentPageSize; +import com.demcha.compose.document.style.DocumentColor; +import com.demcha.compose.document.style.DocumentInsets; +import com.demcha.compose.document.style.DocumentTextDecoration; +import com.demcha.compose.document.style.DocumentTextStyle; +import com.demcha.compose.font.FontName; + +/** + * Every measurement, colour and type size of the Indigo proposal. + * + *

    Sizes are solved by width, not by cap height

    + * + *

    The design's own face is not bundled. Gothic A1 matches it closely at + * regular weight and sets 10–20% wide at bold, so a cap-height solve alone put + * every bold block over its measure. Each size below is instead solved from the + * measured ink width of the string it sets.

    + * + *

    Positions are a map of cap tops

    + * + *

    The whole sheet is one vertical map: every text block's cap top in design + * pixels, in one place. {@link IndigoFlow} turns a cap top into the margin that + * puts it there, given where the flow has already reached — so a block moves by + * changing one number here rather than by re-deriving the gaps around it.

    + */ +final class IndigoStyles { + + private IndigoStyles() { + } + + // ------------------------------------------------------------------ + // The page, and the one scale everything derives from + // ------------------------------------------------------------------ + + static final DocumentPageSize PAGE = DocumentPageSize.A4; + + private static final double PAGE_W = PAGE.width(); + private static final double DESIGN_PX_WIDTH = 1054.0; + private static final double PX = PAGE_W / DESIGN_PX_WIDTH; + + static double px(double designPixels) { + return designPixels * PX; + } + + /** Points back into design pixels, for the map's own arithmetic. */ + static double toPx(double points) { + return points / PX; + } + + /** Ink margins: the masthead and footer rules both run the same span. */ + private static final double MARGIN_T = px(46); + static final double MARGIN_R = px(41); + private static final double MARGIN_B = px(24); + static final double MARGIN_L = px(49); + + static final DocumentInsets PAGE_MARGIN = + new DocumentInsets(MARGIN_T, MARGIN_R, MARGIN_B, MARGIN_L); + + static final double CONTENT_W = PAGE_W - MARGIN_L - MARGIN_R; + + /** Where the flow's cursor starts, in design pixels. */ + static final double FLOW_ORIGIN_PX = 46; + + static final double HALF = 0.5; + + // ------------------------------------------------------------------ + // Columns + // ------------------------------------------------------------------ + + /** + * The two halves of the sheet divide at different x, which is a fact about + * the design and not a mistake: the upper hairline sits left of the lower + * one. + */ + private static final double DIVIDER_UPPER_X_PX = 402; + static final double DIVIDER_UPPER_TOP_PX = 172; + static final double DIVIDER_UPPER_H_PX = 646 - 172; + private static final double DIVIDER_LOWER_X_PX = 503; + static final double DIVIDER_LOWER_TOP_PX = 1020; + static final double DIVIDER_LOWER_H_PX = 1361 - 1020; + + private static final double INTRO_SPLIT_PX = 441; + private static final double LOWER_SPLIT_PX = 538; + private static final double CONTENT_RIGHT_PX = 1013; + + static final double INTRO_LEFT_W = px(DIVIDER_UPPER_X_PX - 49); + static final double INTRO_GAP_W = px(INTRO_SPLIT_PX - DIVIDER_UPPER_X_PX); + static final double LOWER_LEFT_W = px(DIVIDER_LOWER_X_PX - 49); + static final double LOWER_GAP_W = px(LOWER_SPLIT_PX - DIVIDER_LOWER_X_PX); + static final double LOWER_RIGHT_INSET = px(CONTENT_RIGHT_PX - 996); + static final double LOWER_RIGHT_W = px(996 - LOWER_SPLIT_PX); + + /** The hero's own measure, narrower than the column it sits in. */ + static final double HERO_W = CONTENT_W - px(INTRO_SPLIT_PX - 49); + static final double SUBTITLE_MEASURE_PX = 410; + static final double ABOUT_MEASURE_PX = 382; + static final double OVERVIEW_MEASURE_PX = 420; + + // ------------------------------------------------------------------ + // The band + // ------------------------------------------------------------------ + + static final double BAND_TOP_PX = 675; + static final double BAND_H_PX = 307; + + /** + * The design fills the band's right with a product photograph. That is a + * brand asset, and the templates artifact carries none, so the place it + * occupies is kept — the copy keeps the measure the design solved it against + * — and left as flat tint. + */ + static final double BAND_ARTWORK_W = px(584); + private static final double BAND_BLEED = MARGIN_R; + static final double BAND_LEFT_W = CONTENT_W + BAND_BLEED - BAND_ARTWORK_W; + + // ------------------------------------------------------------------ + // Colour, sampled from the design + // ------------------------------------------------------------------ + + static final DocumentColor INK = DocumentColor.rgb(0, 0, 0); + static final DocumentColor BODY = DocumentColor.rgb(124, 128, 138); + static final DocumentColor ACCENT = DocumentColor.rgb(61, 37, 173); + static final DocumentColor TINT = DocumentColor.rgb(241, 238, 251); + static final DocumentColor TILE_DARK = DocumentColor.rgb(20, 26, 37); + static final DocumentColor RULE_STRONG = DocumentColor.rgb(213, 216, 224); + static final DocumentColor RULE_SOFT = DocumentColor.rgb(231, 233, 235); + + // ------------------------------------------------------------------ + // Type + // ------------------------------------------------------------------ + + /** + * The family every size below was solved against. The templates artifact + * carries no fonts: Gothic A1 arrives with {@code graph-compose-fonts} on + * the classpath, or a caller registers a family of that name on the session + * itself. With neither, the engine substitutes and every size is solved for + * type that is not there. + */ + static final FontName FACE = FontName.GOTHIC_A1; + + private static final double FACE_ASCENT = 0.7979; + private static final double CAP_REGULAR = 0.7314; + private static final double CAP_BOLD = 0.7725; + private static final double TOP_BEARING_REGULAR = FACE_ASCENT - CAP_REGULAR; + private static final double TOP_BEARING_BOLD = FACE_ASCENT - CAP_BOLD; + + static double topBearing(double size, boolean bold) { + return (bold ? TOP_BEARING_BOLD : TOP_BEARING_REGULAR) * size; + } + + /** The design y a box of this size and cap top reaches down to. */ + static double boxBottomPx(double capTopPx, double size, boolean bold) { + return capTopPx - toPx(topBearing(size, bold)) + toPx(size); + } + + static final double WORDMARK_SIZE = 28.3; + static final double KICKER_SIZE = 13.9; + static final double LABEL_SIZE = 8.1; + static final double COMPANY_SIZE = 13.2; + static final double BODY_SIZE = 9.7; + static final double CONTACT_SIZE = 8.8; + static final double ATTN_NAME_SIZE = 9.6; + static final double ATTN_ROLE_SIZE = 8.9; + static final double HEADLINE_SIZE = 25.5; + static final double META_LABEL_SIZE = 6.8; + static final double META_VALUE_SIZE = 7.8; + static final double ABOUT_BODY_SIZE = 7.8; + static final double FEATURE_SIZE = 7.9; + static final double OVERVIEW_SIZE = 7.5; + static final double STEP_TITLE_SIZE = 7.6; + static final double STEP_SUB_SIZE = 7.4; + static final double STEP_NUM_SIZE = 8.0; + static final double INVEST_SIZE = 7.6; + static final double TOTAL_LABEL_SIZE = 7.1; + static final double TOTAL_FIGURE_SIZE = 13.2; + static final double NOTE_SIZE = 7.4; + static final double FOOTER_NAME_SIZE = 7.1; + static final double FOOTER_BODY_SIZE = 7.0; + + /** + * The foot's mark, solved from the design glyph's ink height rather than + * from a measured string: what stands in its place is a monogram, and what + * has to match is how large the mark reads beside the name. + */ + static final double FOOTER_MARK_SIZE = 19.9; + + static DocumentTextStyle style(double size, DocumentColor color, + DocumentTextDecoration decoration) { + return new DocumentTextStyle(FACE, size, decoration, color); + } + + static DocumentTextStyle plain(double size, DocumentColor color) { + return style(size, color, DocumentTextDecoration.DEFAULT); + } + + static DocumentTextStyle bold(double size, DocumentColor color) { + return style(size, color, DocumentTextDecoration.BOLD); + } + + // ------------------------------------------------------------------ + // Rules and shapes + // ------------------------------------------------------------------ + + static final double RULE_THIN = px(1.5); + static final double RULE_ACCENT = px(3); + static final double LEFT_DIVIDER_W = px(322 - 49); + static final double ACCENT_RULE_W = px(58); + static final double KICKER_RULE_W = px(57); + static final double META_CIRCLE = px(44); + static final double META_GLYPH = px(20); + static final double FEATURE_TILE = px(48); + static final double FEATURE_RADIUS = px(12); + static final double FEATURE_GLYPH = px(24); + static final double FEATURE_PITCH_PX = 110.7; + static final double STEP_CIRCLE = px(38); + static final double STEP_CIRCLE_PX = 38; + static final double STEP_PITCH_PX = 49.5; + static final double STEP_COL_W = px(110 - 49); + static final double STEP_CONNECTOR_W = px(2); + static final double TOTAL_CARD_RADIUS = px(14); + static final double TOTAL_CARD_H_PX = 60; + static final double TOTAL_PAD_R = px(996 - 975); + static final double TOTAL_PAD_L = px(557 - 538); + static final double INVEST_CELL_PAD_R = px(996 - 979); + static final double FOOTER_TEXT_COL_W = px(108 - 49); + + // ------------------------------------------------------------------ + // The vertical map, in design pixels: the cap top of every text block + // ------------------------------------------------------------------ + + static final double WORDMARK_CAP = 51; + static final double KICKER_CAP = 50; + static final double KICKER_RULE_AT = 90; + static final double MASTHEAD_RULE_AT = 122; + + static final double PREPARED_LABEL_CAP = 174; + static final double COMPANY_CAP = 204; + static final double[] ADDRESS_CAP = {248, 270, 293}; + static final double ADDRESS_EXTRA_PITCH_PX = 23; + static final double LEFT_DIVIDER_AT = 357; + static final double ATTN_LABEL_CAP = 388; + static final double ATTN_NAME_CAP = 419; + static final double ATTN_ROLE_CAP = 448; + static final double ATTN_EMAIL_CAP = 472; + static final double ATTN_PHONE_CAP = 495; + + static final double[] HEADLINE_CAP = {179, 233, 286, 340}; + static final double HEADLINE_EXTRA_PITCH_PX = 53.7; + static final double ACCENT_RULE_AT = 403; + static final double SUBTITLE_CAP = 436; + static final double SUBTITLE_PITCH_PX = 24; + + static final double[] META_PITCH_PX = {130, 141, 150}; + static final double META_CIRCLE_AT = 549; + static final double META_CIRCLE_PX = 44; + static final double META_LABEL_CAP = 614; + static final double META_VALUE_CAP = 636; + + static final double ABOUT_HEADING_CAP = 711; + static final double ABOUT_BODY_CAP = 743; + static final double ABOUT_BODY_PITCH_PX = 20; + static final double FEATURE_TILE_AT = 852; + static final double FEATURE_TILE_PX = 48; + static final double FEATURE_LINE1_CAP = 916; + static final double FEATURE_LINE2_CAP = 935; + + static final double OVERVIEW_HEADING_CAP = 1020; + static final double OVERVIEW_INTRO_CAP = 1052; + static final double OVERVIEW_INTRO_PITCH_PX = 20; + static final double STEP_FIRST_CIRCLE_AT = 1124; + static final double STEP_TITLE_OFFSET_PX = 1133 - 1124; + static final double STEP_SUB_OFFSET_PX = 1153 - 1124; + + static final double INVEST_HEADING_CAP = 1020; + static final double INVEST_FIRST_ROW_CAP = 1061; + static final double INVEST_ROW_PITCH_PX = 34.5; + static final double INVEST_RULE_OFFSET_PX = 1084 - 1061; + static final double TOTAL_CARD_AT = 1196; + static final double TOTAL_FIGURE_CAP = 1218; + static final double NOTES_HEADING_CAP = 1285; + static final double NOTES_FIRST_CAP = 1312; + static final double NOTES_PITCH_PX = 18.5; + + static final double FOOTER_RULE_AT = 1395; + static final double FOOTER_MARK_AT = 1416; + static final double FOOTER_NAME_CAP = 1416; + static final double FOOTER_ADDRESS_CAP = 1435; + static final double FOOTER_CONTACT_CAP = 1455; +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/proposal/presets/IndigoWidgets.java b/templates/src/main/java/com/demcha/compose/document/templates/proposal/presets/IndigoWidgets.java new file mode 100644 index 000000000..b2aa60302 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/proposal/presets/IndigoWidgets.java @@ -0,0 +1,81 @@ +package com.demcha.compose.document.templates.proposal.presets; + +import com.demcha.compose.document.dsl.ShapeContainerBuilder; +import com.demcha.compose.document.dsl.SpacerBuilder; +import com.demcha.compose.document.node.DocumentNode; +import com.demcha.compose.document.style.DocumentColor; + +import java.util.List; + +import static com.demcha.compose.document.templates.proposal.presets.IndigoFlow.glyph; + +/** + * The two filled shapes the Indigo proposal repeats: the disc a header tile + * opens with and the rounded tile a feature opens with. + * + *

    Both are drawn whether or not the document names a mark for them. The shape + * is the design's; the mark inside it is the document's, and a tile that names + * none is still the tile the design draws.

    + */ +final class IndigoWidgets { + + private IndigoWidgets() { + } + + /** + * A filled disc carrying one of the header's marks. + * + * @param name the disc's name + * @param fill the disc's colour + * @param token the mark's token, or blank for a disc that carries none + * @param diameter the disc's diameter + * @param markSize how large to draw the mark + * @return the disc + */ + static DocumentNode disc(String name, DocumentColor fill, String token, + double diameter, double markSize) { + return new ShapeContainerBuilder() + .name(name) + .circle(diameter) + .fillColor(fill) + .center(mark(name, token, IndigoIcons.HEADER_TOKENS, markSize)) + .build(); + } + + /** + * A filled rounded tile carrying one of the band's marks. + * + * @param name the tile's name + * @param fill the tile's colour + * @param token the mark's token, or blank for a tile that carries none + * @param size the tile's side + * @param radius the tile's corner radius + * @param markSize how large to draw the mark + * @return the tile + */ + static DocumentNode tile(String name, DocumentColor fill, String token, + double size, double radius, double markSize) { + return new ShapeContainerBuilder() + .name(name) + .roundedRect(size, size, radius) + .fillColor(fill) + .center(mark(name, token, IndigoIcons.BAND_TOKENS, markSize)) + .build(); + } + + /** + * What goes inside the shape: the mark the document names, or the space it + * would have taken. + * + *

    A shape with no layer at all is refused, so a shape that carries no + * mark is centred on the mark's own space instead — the design's shape is + * drawn either way, and a token this block cannot draw is still a data error + * reported by name rather than an empty tile.

    + */ + private static DocumentNode mark(String name, String token, List allowed, + double markSize) { + return IndigoIcons.has(token) + ? glyph(token, allowed, markSize) + : new SpacerBuilder().name(name + "Space").size(markSize, markSize).build(); + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/proposal/presets/NorthlineIcons.java b/templates/src/main/java/com/demcha/compose/document/templates/proposal/presets/NorthlineIcons.java new file mode 100644 index 000000000..268f4f05b --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/proposal/presets/NorthlineIcons.java @@ -0,0 +1,55 @@ +package com.demcha.compose.document.templates.proposal.presets; + +import com.demcha.compose.document.image.DocumentImageData; + +import java.io.IOException; +import java.io.InputStream; +import java.io.UncheckedIOException; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Classpath loader for the Northline icon set. + * + *

    The icons ship inside the templates artifact under + * {@code templates/proposal/northline/icons/}, keyed by the icon tokens the + * structured proposal data carries (e.g. {@code badge-summary}, + * {@code fact-start}, {@code goal-brand}). Loaded bytes are cached per + * token, so a two-page proposal reads each icon file once per JVM.

    + */ +final class NorthlineIcons { + + private static final String ICON_ROOT = "/templates/proposal/northline/icons/"; + private static final Map CACHE = new ConcurrentHashMap<>(); + + private NorthlineIcons() { + } + + /** + * Loads (and caches) the icon for a data-supplied token. + * + * @param token the icon token, without extension + * @return the icon image data + * @throws IllegalArgumentException when the token names no packaged icon + * — an icon token is data, so a wrong one is a data error, the + * same class the phase-grid header contract throws + */ + static DocumentImageData image(String token) { + return CACHE.computeIfAbsent(token, NorthlineIcons::read); + } + + private static DocumentImageData read(String token) { + String resourcePath = ICON_ROOT + token + ".png"; + try (InputStream input = NorthlineIcons.class.getResourceAsStream(resourcePath)) { + if (input == null) { + throw new IllegalArgumentException( + "Unknown northline proposal icon token '" + token + + "' — no packaged icon at " + resourcePath + "."); + } + return DocumentImageData.fromBytes(input.readAllBytes()); + } catch (IOException e) { + throw new UncheckedIOException( + "Failed to read northline proposal icon: " + resourcePath, e); + } + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/proposal/presets/NorthlinePageOne.java b/templates/src/main/java/com/demcha/compose/document/templates/proposal/presets/NorthlinePageOne.java new file mode 100644 index 000000000..0bbfbd603 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/proposal/presets/NorthlinePageOne.java @@ -0,0 +1,350 @@ +package com.demcha.compose.document.templates.proposal.presets; + +import com.demcha.compose.document.dsl.PageFlowBuilder; +import com.demcha.compose.document.dsl.ParagraphBuilder; +import com.demcha.compose.document.dsl.SectionBuilder; +import com.demcha.compose.document.dsl.ShapeContainerBuilder; +import com.demcha.compose.document.node.DocumentNode; +import com.demcha.compose.document.node.LayerAlign; +import com.demcha.compose.document.node.RowVerticalAlign; +import com.demcha.compose.document.node.TextAlign; +import com.demcha.compose.document.style.ClipPolicy; +import com.demcha.compose.document.style.DocumentInsets; +import com.demcha.compose.document.style.DocumentRowColumn; +import com.demcha.compose.document.style.DocumentTextStyle; +import com.demcha.compose.document.templates.data.proposal.ProposalGlance; +import com.demcha.compose.document.templates.data.proposal.ProposalGoals; +import com.demcha.compose.document.templates.data.proposal.ProposalScope; +import com.demcha.compose.document.templates.data.proposal.ProposalSummaryBlock; +import com.demcha.compose.document.templates.data.proposal.ProposalTitleLines; +import com.demcha.compose.document.templates.data.proposal.StructuredProposalData; + +import java.util.List; + +import static com.demcha.compose.document.templates.proposal.presets.NorthlineStyles.ACCENT; +import static com.demcha.compose.document.templates.proposal.presets.NorthlineStyles.BAND_TOP; +import static com.demcha.compose.document.templates.proposal.presets.NorthlineStyles.BODY; +import static com.demcha.compose.document.templates.proposal.presets.NorthlineStyles.BODY_LEADING; +import static com.demcha.compose.document.templates.proposal.presets.NorthlineStyles.CONTENT_WIDTH; +import static com.demcha.compose.document.templates.proposal.presets.NorthlineStyles.FACT_DIVIDER_GAP; +import static com.demcha.compose.document.templates.proposal.presets.NorthlineStyles.FACT_GAP; +import static com.demcha.compose.document.templates.proposal.presets.NorthlineStyles.FACT_ICON; +import static com.demcha.compose.document.templates.proposal.presets.NorthlineStyles.FACT_LABEL; +import static com.demcha.compose.document.templates.proposal.presets.NorthlineStyles.FACT_LINE; +import static com.demcha.compose.document.templates.proposal.presets.NorthlineStyles.FACT_NOTE; +import static com.demcha.compose.document.templates.proposal.presets.NorthlineStyles.FACT_VALUE; +import static com.demcha.compose.document.templates.proposal.presets.NorthlineStyles.FACT_VALUE_LINE; +import static com.demcha.compose.document.templates.proposal.presets.NorthlineStyles.FACT_VALUE_SIZE; +import static com.demcha.compose.document.templates.proposal.presets.NorthlineStyles.GAP_HEADING; +import static com.demcha.compose.document.templates.proposal.presets.NorthlineStyles.GAP_SECTION; +import static com.demcha.compose.document.templates.proposal.presets.NorthlineStyles.GLANCE_WEIGHT; +import static com.demcha.compose.document.templates.proposal.presets.NorthlineStyles.GLANCE_WIDTH; +import static com.demcha.compose.document.templates.proposal.presets.NorthlineStyles.GOAL_GAP; +import static com.demcha.compose.document.templates.proposal.presets.NorthlineStyles.GOAL_ICON; +import static com.demcha.compose.document.templates.proposal.presets.NorthlineStyles.GOAL_TEXT; +import static com.demcha.compose.document.templates.proposal.presets.NorthlineStyles.GOAL_TEXT_LINES; +import static com.demcha.compose.document.templates.proposal.presets.NorthlineStyles.HAIRLINE; +import static com.demcha.compose.document.templates.proposal.presets.NorthlineStyles.LABEL_SIZE; +import static com.demcha.compose.document.templates.proposal.presets.NorthlineStyles.LATO_LINE; +import static com.demcha.compose.document.templates.proposal.presets.NorthlineStyles.META; +import static com.demcha.compose.document.templates.proposal.presets.NorthlineStyles.META_SEPARATOR; +import static com.demcha.compose.document.templates.proposal.presets.NorthlineStyles.PANEL_BACKGROUND; +import static com.demcha.compose.document.templates.proposal.presets.NorthlineStyles.PANEL_HEADING; +import static com.demcha.compose.document.templates.proposal.presets.NorthlineStyles.PANEL_PADDING; +import static com.demcha.compose.document.templates.proposal.presets.NorthlineStyles.PANEL_RADIUS; +import static com.demcha.compose.document.templates.proposal.presets.NorthlineStyles.RULE; +import static com.demcha.compose.document.templates.proposal.presets.NorthlineStyles.RULE_QUIET; +import static com.demcha.compose.document.templates.proposal.presets.NorthlineStyles.SCOPE_BADGE_H; +import static com.demcha.compose.document.templates.proposal.presets.NorthlineStyles.SCOPE_BADGE_W; +import static com.demcha.compose.document.templates.proposal.presets.NorthlineStyles.SCOPE_BODY; +import static com.demcha.compose.document.templates.proposal.presets.NorthlineStyles.SCOPE_GAP; +import static com.demcha.compose.document.templates.proposal.presets.NorthlineStyles.SCOPE_NUMBER; +import static com.demcha.compose.document.templates.proposal.presets.NorthlineStyles.SCOPE_ROW_PAD; +import static com.demcha.compose.document.templates.proposal.presets.NorthlineStyles.SCOPE_TITLE; +import static com.demcha.compose.document.templates.proposal.presets.NorthlineStyles.SCOPE_TITLE_SPLIT; +import static com.demcha.compose.document.templates.proposal.presets.NorthlineStyles.SUMMARY_GAP; +import static com.demcha.compose.document.templates.proposal.presets.NorthlineStyles.SUMMARY_WEIGHT; +import static com.demcha.compose.document.templates.proposal.presets.NorthlineStyles.SUMMARY_WIDTH; +import static com.demcha.compose.document.templates.proposal.presets.NorthlineStyles.TABLE_SIZE; +import static com.demcha.compose.document.templates.proposal.presets.NorthlineStyles.TITLE_ACCENT; +import static com.demcha.compose.document.templates.proposal.presets.NorthlineStyles.TITLE_INK; +import static com.demcha.compose.document.templates.proposal.presets.NorthlineStyles.TITLE_LAST_LINE_HEIGHT; +import static com.demcha.compose.document.templates.proposal.presets.NorthlineStyles.TITLE_PITCH; +import static com.demcha.compose.document.templates.proposal.presets.NorthlineStyles.TITLE_RULE_WIDTH; +import static com.demcha.compose.document.templates.proposal.presets.NorthlineWidgets.sectionHeading; + +/** + * Page one of the Northline proposal: the title block, the summary band + * with the glance card, the goal cells, and the numbered scope list. + * + *

    Layout notes carried over from the ported template: the title lines + * stack as anchored children of one container because the reference sets + * the title at a pitch tighter than the font's own line height, which text + * leading (additive, non-negative) cannot reach; the goal cells flatten + * every icon/text pair into one row because a row is refused inside a row + * cell; the scope list is rows plus hairlines rather than a table because + * the reference has horizontal separators and no vertical rules.

    + */ +final class NorthlinePageOne { + + private NorthlinePageOne() { + } + + static void compose(PageFlowBuilder page, StructuredProposalData data) { + renderTitleBlock(page, data.title()); + renderTitleMeta(page, data); + renderSummaryBand(page, data); + renderGoalCells(page, data.goals()); + renderScopeOfWork(page, data.scope()); + } + + /** Accent rule, then the three title lines — ink lead, accent remainder. */ + private static void renderTitleBlock(PageFlowBuilder page, ProposalTitleLines title) { + page.addSection("TitleBlock", block -> { + block.margin(BAND_TOP, 0f, 0f, 0f); + block.spacing(0); + block.addLine(line -> line + .horizontal(TITLE_RULE_WIDTH) + .thickness(2.6) + .color(ACCENT) + .margin(DocumentInsets.bottom(2))); + block.add(renderTitleLines(title)); + }); + } + + /** + * The three title lines, stacked on the reference's own pitch: each + * line is a child of one container placed {@code TITLE_PITCH} apart — + * this stacks what the data already declares as separate lines. + */ + private static DocumentNode renderTitleLines(ProposalTitleLines title) { + return new ShapeContainerBuilder() + .name("TitleLines") + .rectangle(CONTENT_WIDTH, 2 * TITLE_PITCH + TITLE_LAST_LINE_HEIGHT) + .clipPolicy(ClipPolicy.OVERFLOW_VISIBLE) + .position(titleLine("TitleLead", title.lead(), TITLE_INK), + 0, 0, LayerAlign.TOP_LEFT) + .position(titleLine("TitleLine1", title.second(), TITLE_ACCENT), + 0, TITLE_PITCH, LayerAlign.TOP_LEFT) + .position(titleLine("TitleLine2", title.third(), TITLE_ACCENT), + 0, 2 * TITLE_PITCH, LayerAlign.TOP_LEFT) + .build(); + } + + private static DocumentNode titleLine(String name, String text, DocumentTextStyle style) { + return new ParagraphBuilder() + .name(name).text(text).textStyle(style).build(); + } + + /** Prepared-for, prepared-by and date, joined by a quieter separator. */ + private static void renderTitleMeta(PageFlowBuilder page, StructuredProposalData data) { + page.addSection("TitleMeta", section -> { + section.margin(BAND_TOP, 0f, 0f, 0f); + section.addParagraph(p -> p + .name("MetaLine") + .inlineText(data.meta().preparedFor(), META) + .inlineText(" | ", META_SEPARATOR) + .inlineText(data.meta().preparedBy(), META) + .inlineText(" | ", META_SEPARATOR) + .inlineText(data.meta().date(), META)); + }); + } + + /** The two-column band: summary column left, glance card right. */ + private static void renderSummaryBand(PageFlowBuilder page, StructuredProposalData data) { + page.addRow("SummaryBand", row -> { + row.margin(DocumentInsets.top(BAND_TOP)); + row.verticalAlign(RowVerticalAlign.TOP) + .gap(SUMMARY_GAP) + .weights(SUMMARY_WEIGHT, GLANCE_WEIGHT); + row.addSection("SummaryColumn", column -> { + renderExecutiveSummary(column, data.executiveSummary()); + // The goals heading sits in this column, level with the + // bottom of the glance card — it belongs to the band. + column.add(sectionHeading( + data.goals().heading(), data.goals().icon(), SUMMARY_WIDTH)); + }); + row.addSection("GlanceColumn", column -> renderGlancePanel(column, data.glance())); + }); + } + + /** Heading plus the summary paragraphs. */ + private static void renderExecutiveSummary(SectionBuilder column, + ProposalSummaryBlock summary) { + column.spacing(0); + column.add(sectionHeading(summary.heading(), summary.icon(), SUMMARY_WIDTH)); + List paragraphs = summary.paragraphs(); + for (int i = 0; i < paragraphs.size(); i++) { + String text = paragraphs.get(i); + float top = i == 0 ? (float) GAP_HEADING : (float) GAP_SECTION * 0.6f; + column.addParagraph(p -> p + .text(text) + .textStyle(BODY) + .lineSpacing(BODY_LEADING) + .margin(top, 0f, 0f, 0f)); + } + // Clears the last paragraph before the goals heading. + column.addSpacer(spacer -> spacer.height(GAP_SECTION)); + } + + /** + * The quiet card of project facts. {@code softPanel} colours the + * section that already owns the facts, so the card is their parent + * rather than a rectangle drawn behind them. + */ + private static void renderGlancePanel(SectionBuilder column, ProposalGlance glance) { + double inner = GLANCE_WIDTH - 2 * PANEL_PADDING; + column.addSection("GlancePanel", panel -> { + panel.spacing(0).softPanel(PANEL_BACKGROUND, PANEL_RADIUS, PANEL_PADDING); + panel.addParagraph(p -> p + .text(glance.heading()) + .textStyle(PANEL_HEADING) + .margin(0f, 0f, (float) GAP_HEADING, 0f)); + List facts = glance.facts(); + for (int i = 0; i < facts.size(); i++) { + panel.add(renderGlanceFact(facts.get(i), inner)); + if (i < facts.size() - 1) { + panel.addLine(line -> line + .fill() + .thickness(HAIRLINE) + .color(RULE_QUIET) + .margin(DocumentInsets.symmetric(FACT_DIVIDER_GAP, 0))); + } + } + }); + } + + /** + * One fact: the icon anchored at the left of a label-over-value stack. + * The container's height derives from the number of text lines the fact + * actually has, so the optional note line makes its own room. + */ + private static DocumentNode renderGlanceFact(ProposalGlance.Fact fact, double width) { + boolean hasNote = !fact.note().isBlank(); + double height = LABEL_SIZE * FACT_LINE + FACT_VALUE_SIZE * FACT_VALUE_LINE + + (hasNote ? (LABEL_SIZE + 0.5) * FACT_LINE : 0); + + SectionBuilder text = new SectionBuilder(); + text.name("FactText"); + text.spacing(0); + text.addParagraph(p -> p.text(fact.label()).textStyle(FACT_LABEL)); + text.addParagraph(p -> p.text(fact.value()).textStyle(FACT_VALUE)); + if (hasNote) { + text.addParagraph(p -> p.text(fact.note()).textStyle(FACT_NOTE)); + } + + ShapeContainerBuilder container = new ShapeContainerBuilder() + .name("GlanceFact") + .rectangle(width, height) + .clipPolicy(ClipPolicy.OVERFLOW_VISIBLE); + if (!fact.icon().isBlank()) { + container.position(NorthlineWidgets.icon(fact.icon(), FACT_ICON), + 0, 0, LayerAlign.CENTER_LEFT); + } + return container + .position(text.build(), FACT_ICON + FACT_GAP, 0, LayerAlign.CENTER_LEFT) + .build(); + } + + /** + * The goal cells, flattened deliberately: the icon/text pairs and the + * separators are all direct children of one row, and the column widths + * carry the structure. Skipped entirely when the data has no goals — + * the flattened column spec cannot describe zero cells. + */ + private static void renderGoalCells(PageFlowBuilder page, ProposalGoals goals) { + List items = goals.items(); + if (items.isEmpty()) { + return; + } + // Three goal-text lines set the separator height; the pitch is the + // font line plus the additive leading. + double separatorHeight = GOAL_TEXT_LINES * (TABLE_SIZE * LATO_LINE + BODY_LEADING); + page.addRow("GoalCells", row -> { + row.margin(DocumentInsets.top(BAND_TOP)); + row.verticalAlign(RowVerticalAlign.TOP).gap(GOAL_GAP); + row.columns(goalColumns(items.size())); + for (int i = 0; i < items.size(); i++) { + if (i > 0) { + row.addLine(line -> line + .vertical(separatorHeight) + .thickness(HAIRLINE) + .color(RULE)); + } + ProposalGoals.Goal goal = items.get(i); + if (goal.icon().isBlank()) { + // Keep the child-per-column mapping of the flattened row. + row.addSpacer(GOAL_ICON); + } else { + row.addImage(image -> NorthlineWidgets.configureIcon( + image, goal.icon(), GOAL_ICON)); + } + row.addParagraph(p -> p + .text(goal.text()) + .textStyle(GOAL_TEXT) + .lineSpacing(BODY_LEADING)); + } + }); + } + + /** Column spec for the flattened goal row. */ + private static DocumentRowColumn[] goalColumns(int goalCount) { + DocumentRowColumn[] columns = new DocumentRowColumn[goalCount * 2 + (goalCount - 1)]; + int at = 0; + for (int i = 0; i < goalCount; i++) { + if (i > 0) { + columns[at++] = DocumentRowColumn.fixed(HAIRLINE); + } + columns[at++] = DocumentRowColumn.fixed(GOAL_ICON); + columns[at++] = DocumentRowColumn.weight(1); + } + return columns; + } + + /** The numbered scope list: rows plus hairlines at the flow's top level. */ + private static void renderScopeOfWork(PageFlowBuilder page, ProposalScope scope) { + double titleWidth = CONTENT_WIDTH * SCOPE_TITLE_SPLIT - SCOPE_BADGE_W - 2 * SCOPE_GAP; + page.addSection("ScopeOfWork", section -> { + section.margin(BAND_TOP, 0f, 0f, 0f); + section.spacing(0); + section.add(sectionHeading(scope.heading(), scope.icon(), CONTENT_WIDTH)); + section.addLine(line -> line + .fill() + .thickness(HAIRLINE) + .color(RULE) + .margin(DocumentInsets.top(GAP_HEADING))); + List items = scope.items(); + for (int i = 0; i < items.size(); i++) { + ProposalScope.Item item = items.get(i); + section.addRow("ScopeRow" + item.number(), row -> { + row.verticalAlign(RowVerticalAlign.TOP) + .gap(SCOPE_GAP) + .padding(DocumentInsets.symmetric(SCOPE_ROW_PAD, 0)) + .columns(DocumentRowColumn.fixed(SCOPE_BADGE_W), + DocumentRowColumn.fixed(titleWidth), + DocumentRowColumn.weight(1)); + row.add(renderScopeBadge(item.number())); + row.addParagraph(p -> p.text(item.title()).textStyle(SCOPE_TITLE)); + row.addParagraph(p -> p + .text(item.description()) + .textStyle(SCOPE_BODY) + .lineSpacing(BODY_LEADING)); + }); + if (i < items.size() - 1) { + section.addLine(line -> line.fill().thickness(HAIRLINE).color(RULE)); + } + } + }); + } + + /** The teal step badge and the number it owns. */ + private static DocumentNode renderScopeBadge(String number) { + return new ShapeContainerBuilder() + .name("ScopeBadge") + .roundedRect(SCOPE_BADGE_W, SCOPE_BADGE_H, 2.0) + .fillColor(ACCENT) + .center(NorthlineWidgets.paragraph("ScopeNumber", number, SCOPE_NUMBER, + TextAlign.CENTER)) + .build(); + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/proposal/presets/NorthlinePageTwo.java b/templates/src/main/java/com/demcha/compose/document/templates/proposal/presets/NorthlinePageTwo.java new file mode 100644 index 000000000..17db60deb --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/proposal/presets/NorthlinePageTwo.java @@ -0,0 +1,333 @@ +package com.demcha.compose.document.templates.proposal.presets; + +import com.demcha.compose.document.dsl.PageFlowBuilder; +import com.demcha.compose.document.dsl.ParagraphBuilder; +import com.demcha.compose.document.dsl.SectionBuilder; +import com.demcha.compose.document.node.DocumentNode; +import com.demcha.compose.document.node.RowVerticalAlign; +import com.demcha.compose.document.style.DocumentColor; +import com.demcha.compose.document.style.DocumentInsets; +import com.demcha.compose.document.style.DocumentRowColumn; +import com.demcha.compose.document.style.DocumentStroke; +import com.demcha.compose.document.style.DocumentTextStyle; +import com.demcha.compose.document.table.DocumentTableCell; +import com.demcha.compose.document.table.DocumentTableColumn; +import com.demcha.compose.document.table.DocumentTableStyle; +import com.demcha.compose.document.table.DocumentTableTextAnchor; +import com.demcha.compose.document.templates.data.proposal.ProposalAcceptance; +import com.demcha.compose.document.templates.data.proposal.ProposalDeliverables; +import com.demcha.compose.document.templates.data.proposal.ProposalInvestment; +import com.demcha.compose.document.templates.data.proposal.ProposalPhaseGrid; +import com.demcha.compose.document.templates.data.proposal.ProposalTermsBlock; +import com.demcha.compose.document.templates.data.proposal.StructuredProposalData; + +import java.util.List; + +import static com.demcha.compose.document.templates.proposal.presets.NorthlineStyles.ACCEPTANCE_FIELD_GAP; +import static com.demcha.compose.document.templates.proposal.presets.NorthlineStyles.ACCEPTANCE_PADDING; +import static com.demcha.compose.document.templates.proposal.presets.NorthlineStyles.ACCENT; +import static com.demcha.compose.document.templates.proposal.presets.NorthlineStyles.BAND_TOP; +import static com.demcha.compose.document.templates.proposal.presets.NorthlineStyles.BODY; +import static com.demcha.compose.document.templates.proposal.presets.NorthlineStyles.BODY_LEADING; +import static com.demcha.compose.document.templates.proposal.presets.NorthlineStyles.CONTENT_WIDTH; +import static com.demcha.compose.document.templates.proposal.presets.NorthlineStyles.DELIVERABLES_LEFT_WEIGHT; +import static com.demcha.compose.document.templates.proposal.presets.NorthlineStyles.DELIVERABLES_RIGHT_WEIGHT; +import static com.demcha.compose.document.templates.proposal.presets.NorthlineStyles.GAP_HEADING; +import static com.demcha.compose.document.templates.proposal.presets.NorthlineStyles.HAIRLINE; +import static com.demcha.compose.document.templates.proposal.presets.NorthlineStyles.INK; +import static com.demcha.compose.document.templates.proposal.presets.NorthlineStyles.INVESTMENT_ROW_PAD; +import static com.demcha.compose.document.templates.proposal.presets.NorthlineStyles.INVESTMENT_WEIGHT; +import static com.demcha.compose.document.templates.proposal.presets.NorthlineStyles.INVESTMENT_WIDTH; +import static com.demcha.compose.document.templates.proposal.presets.NorthlineStyles.MONEY_GAP; +import static com.demcha.compose.document.templates.proposal.presets.NorthlineStyles.PANEL_BACKGROUND; +import static com.demcha.compose.document.templates.proposal.presets.NorthlineStyles.PANEL_RADIUS; +import static com.demcha.compose.document.templates.proposal.presets.NorthlineStyles.RULE; +import static com.demcha.compose.document.templates.proposal.presets.NorthlineStyles.RULE_QUIET; +import static com.demcha.compose.document.templates.proposal.presets.NorthlineStyles.SIGNATURE_LABEL; +import static com.demcha.compose.document.templates.proposal.presets.NorthlineStyles.SIGNATURE_LABEL_WIDTH; +import static com.demcha.compose.document.templates.proposal.presets.NorthlineStyles.TABLE_BODY; +import static com.demcha.compose.document.templates.proposal.presets.NorthlineStyles.TABLE_BODY_BOLD; +import static com.demcha.compose.document.templates.proposal.presets.NorthlineStyles.TABLE_HEADER; +import static com.demcha.compose.document.templates.proposal.presets.NorthlineStyles.TERMS_WEIGHT; +import static com.demcha.compose.document.templates.proposal.presets.NorthlineStyles.TERMS_WIDTH; +import static com.demcha.compose.document.templates.proposal.presets.NorthlineStyles.TIMELINE_WEIGHTS; +import static com.demcha.compose.document.templates.proposal.presets.NorthlineStyles.TOTAL_TEXT; +import static com.demcha.compose.document.templates.proposal.presets.NorthlineWidgets.renderBullet; +import static com.demcha.compose.document.templates.proposal.presets.NorthlineWidgets.sectionHeading; + +/** + * Page two of the Northline proposal: the deliverable columns, the phase + * grid, the investment / terms band, and the signing card. + * + *

    Layout notes carried over from the ported template: the phase grid is + * the one region with real vertical rules and therefore the one real + * table; the investment block sits inside a row cell where a nested row is + * refused, so it is a table too — with the recorded cost that its cell + * strokes also draw a rule down the ITEM/AMOUNT boundary. Prose cells in + * the phase grid are composed paragraphs (a plain-text cell reports its + * whole string as the column's natural width and a narrower fixed column is + * rejected rather than wrapped), while the phase cell stays a plain-text + * cell — {@code DocumentTableTextAnchor} centres a text cell's value but + * not a composed one's.

    + */ +final class NorthlinePageTwo { + + private NorthlinePageTwo() { + } + + static void compose(PageFlowBuilder page, StructuredProposalData data) { + renderDeliverables(page, data.deliverables()); + renderTimelineTable(page, data.timeline()); + renderMoneyBand(page, data); + renderAcceptance(page, data.acceptance()); + } + + /** Heading plus two bullet columns. */ + private static void renderDeliverables(PageFlowBuilder page, + ProposalDeliverables deliverables) { + page.addSection("Deliverables", section -> { + section.margin(BAND_TOP, 0f, 0f, 0f); + section.spacing(0); + section.add(sectionHeading( + deliverables.heading(), deliverables.icon(), CONTENT_WIDTH)); + section.addRow("DeliverableColumns", row -> { + row.verticalAlign(RowVerticalAlign.TOP) + .gap(0) + .padding(DocumentInsets.top(GAP_HEADING)) + .weights(DELIVERABLES_LEFT_WEIGHT, DELIVERABLES_RIGHT_WEIGHT); + row.addSection("DeliverablesLeft", column -> { + column.spacing(0); + deliverables.leftColumn().forEach(item -> renderBullet(column, item)); + }); + row.addSection("DeliverablesRight", column -> { + column.spacing(0); + deliverables.rightColumn().forEach(item -> renderBullet(column, item)); + }); + }); + }); + } + + /** + * The phase grid. Requires one authored header per column: the grid + * draws four columns, so four headers — anything else is a data error + * reported here rather than an index error deep in the table. Skipped + * entirely when the data has no phases, including that header-count + * validation — a grid that never renders constrains nothing. + */ + private static void renderTimelineTable(PageFlowBuilder page, ProposalPhaseGrid timeline) { + if (timeline.phases().isEmpty()) { + return; + } + List headers = timeline.columnHeaders(); + if (headers.size() != TIMELINE_WEIGHTS.length) { + throw new IllegalArgumentException( + "The phase grid renders " + TIMELINE_WEIGHTS.length + + " columns and needs exactly one header per column; " + + "columnHeaders carries " + headers.size() + "."); + } + DocumentTableStyle headerStyle = DocumentTableStyle.builder() + .fillColor(INK) + .stroke(DocumentStroke.of(INK, HAIRLINE)) + .padding(DocumentInsets.symmetric(7, 10)) + .textStyle(TABLE_HEADER) + .textAnchor(DocumentTableTextAnchor.CENTER_LEFT) + .build(); + DocumentTableStyle cellStyle = DocumentTableStyle.builder() + .stroke(DocumentStroke.of(RULE, HAIRLINE)) + .padding(DocumentInsets.symmetric(9, 10)) + .textStyle(TABLE_BODY) + .textAnchor(DocumentTableTextAnchor.TOP_LEFT) + .lineSpacing(BODY_LEADING) + .build(); + DocumentTableStyle centredCellStyle = DocumentTableStyle.builder() + .stroke(DocumentStroke.of(RULE, HAIRLINE)) + .padding(DocumentInsets.symmetric(9, 10)) + .textStyle(TABLE_BODY) + .textAnchor(DocumentTableTextAnchor.CENTER_LEFT) + .lineSpacing(BODY_LEADING) + .build(); + + page.addSection("Timeline", section -> { + section.margin(BAND_TOP, 0f, 0f, 0f); + section.spacing(0); + section.add(sectionHeading(timeline.heading(), timeline.icon(), CONTENT_WIDTH)); + section.addTable(table -> { + table.name("TimelineTable") + .width(CONTENT_WIDTH) + .margin(DocumentInsets.top(GAP_HEADING)) + .columns(DocumentTableColumn.fixed(CONTENT_WIDTH * TIMELINE_WEIGHTS[0]), + DocumentTableColumn.fixed(CONTENT_WIDTH * TIMELINE_WEIGHTS[1]), + DocumentTableColumn.fixed(CONTENT_WIDTH * TIMELINE_WEIGHTS[2]), + DocumentTableColumn.fixed(CONTENT_WIDTH * TIMELINE_WEIGHTS[3])) + .defaultCellStyle(cellStyle); + table.headerCells( + DocumentTableCell.text(headers.get(0)).withStyle(headerStyle), + DocumentTableCell.text(headers.get(1)).withStyle(headerStyle), + DocumentTableCell.text(headers.get(2)).withStyle(headerStyle), + DocumentTableCell.text(headers.get(3)).withStyle(headerStyle)); + for (ProposalPhaseGrid.Phase phase : timeline.phases()) { + table.rowCells( + DocumentTableCell.text(phase.number() + " " + phase.name()) + .withStyle(centredCellStyle), + DocumentTableCell.node(wrappingCell(phase.focus())) + .withStyle(cellStyle), + DocumentTableCell.text(phase.duration()) + .withStyle(centredCellStyle), + DocumentTableCell.node(wrappingCell(phase.output())) + .withStyle(cellStyle)); + } + table.repeatHeader(); + }); + }); + } + + /** A table cell that has to wrap: a composed paragraph wraps to its column. */ + private static DocumentNode wrappingCell(String text) { + return new ParagraphBuilder() + .name("Cell") + .text(text) + .textStyle(TABLE_BODY) + .lineSpacing(BODY_LEADING) + .build(); + } + + /** The two-column money band: the priced table left, terms right. */ + private static void renderMoneyBand(PageFlowBuilder page, StructuredProposalData data) { + page.addRow("MoneyBand", row -> { + row.margin(DocumentInsets.top(BAND_TOP)); + row.verticalAlign(RowVerticalAlign.TOP) + .gap(MONEY_GAP) + .weights(INVESTMENT_WEIGHT, TERMS_WEIGHT); + row.addSection("InvestmentColumn", + column -> renderInvestmentTable(column, data.investment())); + row.addSection("TermsColumn", column -> renderTerms(column, data.terms())); + }); + } + + /** The priced block, with the last band always the labelled total. */ + private static void renderInvestmentTable(SectionBuilder column, + ProposalInvestment investment) { + double labelWidth = INVESTMENT_WIDTH * 0.62; + double amountWidth = INVESTMENT_WIDTH - labelWidth; + + DocumentTableStyle headerLabel = investmentStyle(INK, TABLE_HEADER, + DocumentTableTextAnchor.CENTER_LEFT); + DocumentTableStyle headerAmount = investmentStyle(INK, TABLE_HEADER, + DocumentTableTextAnchor.CENTER_RIGHT); + DocumentTableStyle rowLabel = investmentStyle(null, TABLE_BODY, + DocumentTableTextAnchor.CENTER_LEFT); + DocumentTableStyle rowAmount = investmentStyle(null, TABLE_BODY, + DocumentTableTextAnchor.CENTER_RIGHT); + DocumentTableStyle subtotalLabel = investmentStyle(PANEL_BACKGROUND, TABLE_BODY_BOLD, + DocumentTableTextAnchor.CENTER_LEFT); + DocumentTableStyle subtotalAmount = investmentStyle(PANEL_BACKGROUND, TABLE_BODY_BOLD, + DocumentTableTextAnchor.CENTER_RIGHT); + DocumentTableStyle optionalAmount = investmentStyle(null, TABLE_BODY_BOLD, + DocumentTableTextAnchor.CENTER_RIGHT); + DocumentTableStyle totalLabel = investmentStyle(ACCENT, TOTAL_TEXT, + DocumentTableTextAnchor.CENTER_LEFT); + DocumentTableStyle totalAmount = investmentStyle(ACCENT, TOTAL_TEXT, + DocumentTableTextAnchor.CENTER_RIGHT); + + column.spacing(0); + column.add(sectionHeading( + investment.heading(), investment.icon(), INVESTMENT_WIDTH)); + column.addTable(table -> { + table.name("InvestmentTable") + .width(INVESTMENT_WIDTH) + .margin(DocumentInsets.top(GAP_HEADING)) + .columns(DocumentTableColumn.fixed(labelWidth), + DocumentTableColumn.fixed(amountWidth)) + .defaultCellStyle(rowLabel); + table.headerCells( + DocumentTableCell.text(investment.itemHeader()).withStyle(headerLabel), + DocumentTableCell.text(investment.amountHeader()).withStyle(headerAmount)); + for (ProposalInvestment.Row line : investment.rows()) { + switch (line.role()) { + case SUBTOTAL -> table.rowCells( + DocumentTableCell.text(line.label()).withStyle(subtotalLabel), + DocumentTableCell.text(line.amount()).withStyle(subtotalAmount)); + case OPTIONAL -> table.rowCells( + DocumentTableCell.text(line.label()).withStyle(rowLabel), + DocumentTableCell.text(line.amount()).withStyle(optionalAmount)); + default -> table.rowCells( + DocumentTableCell.text(line.label()).withStyle(rowLabel), + DocumentTableCell.text(line.amount()).withStyle(rowAmount)); + } + } + table.rowCells( + DocumentTableCell.text(investment.totalLabel()).withStyle(totalLabel), + DocumentTableCell.text(investment.totalAmount()).withStyle(totalAmount)); + }); + } + + /** One investment cell style; {@code fill} may be null for the plain rows. */ + private static DocumentTableStyle investmentStyle(DocumentColor fill, + DocumentTextStyle text, + DocumentTableTextAnchor anchor) { + DocumentTableStyle.Builder builder = DocumentTableStyle.builder() + .stroke(DocumentStroke.of(RULE_QUIET, HAIRLINE)) + .padding(DocumentInsets.symmetric(INVESTMENT_ROW_PAD, 10)) + .textStyle(text) + .textAnchor(anchor); + if (fill != null) { + builder.fillColor(fill); + } + return builder.build(); + } + + /** Heading plus the bulleted terms, reusing the deliverables bullet. */ + private static void renderTerms(SectionBuilder column, ProposalTermsBlock terms) { + column.spacing(0); + column.add(sectionHeading(terms.heading(), terms.icon(), TERMS_WIDTH)); + column.addSection("TermsList", list -> { + list.spacing(0).padding((float) GAP_HEADING, 0f, 0f, 0f); + terms.items().forEach(item -> renderBullet(list, item)); + }); + } + + /** + * The signing card. The signature fields are flat row columns — label, + * rule per field — with the column spec derived from the field count, + * so no pair nests inside a cell; the row is skipped when the data has + * no fields. + */ + private static void renderAcceptance(PageFlowBuilder page, ProposalAcceptance acceptance) { + double inner = CONTENT_WIDTH - 2 * ACCEPTANCE_PADDING; + page.addSection("Acceptance", card -> { + card.margin(BAND_TOP, 0f, 0f, 0f); + card.spacing(0).softPanel(PANEL_BACKGROUND, PANEL_RADIUS, ACCEPTANCE_PADDING, + DocumentStroke.of(RULE, HAIRLINE)); + card.add(sectionHeading(acceptance.heading(), acceptance.icon(), inner)); + card.addParagraph(p -> p + .text(acceptance.statement()) + .textStyle(BODY) + .lineSpacing(BODY_LEADING) + .margin((float) GAP_HEADING, 0f, 0f, 0f)); + List fields = acceptance.fields(); + if (fields.isEmpty()) { + return; + } + card.addRow("SignatureFields", row -> { + row.verticalAlign(RowVerticalAlign.BOTTOM) + .gap(6) + .padding(DocumentInsets.top(ACCEPTANCE_FIELD_GAP)) + .columns(signatureColumns(fields.size())); + for (String field : fields) { + row.addParagraph(p -> p.text(field).textStyle(SIGNATURE_LABEL)); + row.addLine(line -> line.fill().thickness(HAIRLINE).color(INK)); + } + }); + }); + } + + /** Label-strip + growing-rule column pair per signature field. */ + private static DocumentRowColumn[] signatureColumns(int fieldCount) { + DocumentRowColumn[] columns = new DocumentRowColumn[fieldCount * 2]; + for (int i = 0; i < fieldCount; i++) { + columns[i * 2] = DocumentRowColumn.fixed(SIGNATURE_LABEL_WIDTH); + columns[i * 2 + 1] = DocumentRowColumn.weight(1); + } + return columns; + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/proposal/presets/NorthlineProposal.java b/templates/src/main/java/com/demcha/compose/document/templates/proposal/presets/NorthlineProposal.java new file mode 100644 index 000000000..6936a0dab --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/proposal/presets/NorthlineProposal.java @@ -0,0 +1,164 @@ +package com.demcha.compose.document.templates.proposal.presets; + +import com.demcha.compose.document.api.DocumentSession; +import com.demcha.compose.document.api.PageBackgroundFill; +import com.demcha.compose.document.output.DocumentHeaderFooter; +import com.demcha.compose.document.output.DocumentHeaderFooterZone; +import com.demcha.compose.document.style.DocumentInsets; +import com.demcha.compose.document.templates.api.DocumentTemplate; +import com.demcha.compose.document.templates.data.proposal.ProposalBrand; +import com.demcha.compose.document.templates.data.proposal.StructuredProposalData; +import com.demcha.compose.document.templates.data.proposal.StructuredProposalDocumentSpec; + +import java.util.List; +import java.util.Objects; + +import static com.demcha.compose.document.templates.proposal.presets.NorthlineStyles.ACCENT; +import static com.demcha.compose.document.templates.proposal.presets.NorthlineStyles.FOOTER_HEIGHT; +import static com.demcha.compose.document.templates.proposal.presets.NorthlineStyles.FOOTER_TEXT_SIZE; +import static com.demcha.compose.document.templates.proposal.presets.NorthlineStyles.FOOTER_ZONE_HEIGHT; +import static com.demcha.compose.document.templates.proposal.presets.NorthlineStyles.INK; +import static com.demcha.compose.document.templates.proposal.presets.NorthlineStyles.MARGIN_SIDE; +import static com.demcha.compose.document.templates.proposal.presets.NorthlineStyles.MARGIN_TOP; +import static com.demcha.compose.document.templates.proposal.presets.NorthlineStyles.ON_DARK; +import static com.demcha.compose.document.templates.proposal.presets.NorthlineStyles.PAGE_BACKGROUND; +import static com.demcha.compose.document.templates.proposal.presets.NorthlineStyles.PAGE_BLOCK_WIDTH; +import static com.demcha.compose.document.templates.proposal.presets.NorthlineStyles.PAGE_HEIGHT; +import static com.demcha.compose.document.templates.proposal.presets.NorthlineStyles.PAGE_WIDTH; + +/** + * Northline Proposal — the first structured proposal preset: a two-page + * teal-and-navy business proposal on the Spectral/Lato pair. + * + *

    Page one carries the brand header, the three stacked title lines, the + * meta line, the executive summary beside the at-a-glance card, the goal + * cells, and the numbered scope list; page two repeats the header and + * carries the deliverable columns, the phase grid, the investment / terms + * band, and the signing card. Like the ported template, the preset issues + * no explicit page break: page one's bands are sized so reference-volume + * content ends just short of the content box and the page-two header opens + * the second page (a break issued after that natural advance would leave a + * blank page between them). The two-page distribution is therefore a + * property of the content volume — noticeably lighter content lets the + * page-two bands climb onto page one. A section whose data is empty keeps + * its badge disc and heading; only structures that cannot be composed + * empty — the goal cells, the phase grid, the signature row — are + * skipped.

    + * + *

    The page chrome is not flow content: the navy footer band and the + * teal page-number block are {@link PageBackgroundFill}s (their geometry + * comes from the page and they repeat on every page), the brand line and + * the {@code 0{page}} number are a {@link DocumentHeaderFooter} in the + * FOOTER zone, and the bottom page margin equals the band height so + * content stops above the chrome. The preset therefore owns its session + * geometry — page size, margins, page background — and a session margin + * set by the caller would be overwritten; see {@link #RECOMMENDED_MARGIN}.

    + * + *

    The preset consumes the structured proposal model + * ({@link StructuredProposalDocumentSpec}); the icon tokens the data + * carries resolve against the packaged Northline icon set. The colours and + * the measured geometry are preset-local ({@link NorthlineStyles}) — the + * look is not shared with any other family today.

    + */ +public final class NorthlineProposal { + + /** + * Stable template identifier. + */ + public static final String ID = "proposal-northline"; + + /** + * Human-readable display name. + */ + public static final String DISPLAY_NAME = "Northline Proposal"; + + /** + * Recommended session margin (in points). The preset sets its own page + * geometry — side margins, the top margin, and a bottom margin equal to + * the footer band — inside {@code compose}, so callers leave the + * session margin at zero and let the preset own the page frame. + */ + public static final double RECOMMENDED_MARGIN = 0.0; + + private NorthlineProposal() { + } + + /** + * Builds the preset. + * + * @return ready-to-use template + */ + public static DocumentTemplate create() { + return new Template(); + } + + private record Template() implements DocumentTemplate { + + @Override + public String id() { + return ID; + } + + @Override + public String displayName() { + return DISPLAY_NAME; + } + + @Override + public void compose(DocumentSession document, StructuredProposalDocumentSpec spec) { + Objects.requireNonNull(document, "document"); + StructuredProposalData data = Objects.requireNonNull(spec, "spec").proposal(); + + document.pageSize(PAGE_WIDTH, PAGE_HEIGHT) + // The bottom margin is the footer band: content stops + // above it, so nothing runs underneath the chrome. + .margin(new DocumentInsets(MARGIN_TOP, MARGIN_SIDE, + FOOTER_HEIGHT, MARGIN_SIDE)) + .pageBackground(PAGE_BACKGROUND); + + renderChrome(document, data.brand()); + + document.pageFlow(page -> { + // spacing(0): flow spacing survives a page break and would + // indent the page-two header; the rhythm lives on the bands. + page.name("Proposal").spacing(0); + page.addSection("Header", + header -> NorthlineWidgets.renderHeader(header, data.brand())); + NorthlinePageOne.compose(page, data); + page.addSection("Header", + header -> NorthlineWidgets.renderHeader(header, data.brand())); + NorthlinePageTwo.compose(page, data); + }); + } + + /** + * The band that closes every page: the navy band and the teal + * corner block as page-ratio background fills, and the brand line + + * page number as FOOTER-zone chrome with {@code {page}} resolved + * per page ({@code 0{page}} because the reference numbers its pages + * 01 and 02 and there is no zero-padded page-number style). + */ + private static void renderChrome(DocumentSession document, ProposalBrand brand) { + document.pageBackgrounds(List.of( + PageBackgroundFill.bottomBand(FOOTER_HEIGHT / PAGE_HEIGHT, INK), + new PageBackgroundFill( + 1.0 - PAGE_BLOCK_WIDTH / PAGE_WIDTH, + 1.0 - FOOTER_HEIGHT / PAGE_HEIGHT, + PAGE_BLOCK_WIDTH / PAGE_WIDTH, + FOOTER_HEIGHT / PAGE_HEIGHT, + ACCENT))); + + document.footer(DocumentHeaderFooter.builder() + .zone(DocumentHeaderFooterZone.FOOTER) + // The backend places the baseline at (height - fontSize) + // above the page edge; this height centres the cap band + // inside the footer band. + .height((float) FOOTER_ZONE_HEIGHT) + .leftText(brand.footerName() + " | " + brand.website()) + .rightText("0{page}") + .fontSize((float) FOOTER_TEXT_SIZE) + .textColor(ON_DARK) + .build()); + } + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/proposal/presets/NorthlineStyles.java b/templates/src/main/java/com/demcha/compose/document/templates/proposal/presets/NorthlineStyles.java new file mode 100644 index 000000000..048f98ec3 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/proposal/presets/NorthlineStyles.java @@ -0,0 +1,207 @@ +package com.demcha.compose.document.templates.proposal.presets; + +import com.demcha.compose.document.style.DocumentColor; +import com.demcha.compose.document.style.DocumentTextDecoration; +import com.demcha.compose.document.style.DocumentTextStyle; +import com.demcha.compose.document.templates.core.text.TextOrnaments; +import com.demcha.compose.font.FontName; + +/** + * The Northline look: colour roles, the Spectral/Lato type scale, and the + * measured geometry constants shared by the preset's bands. + * + *

    Preset-local rather than a {@code BrandTheme}: the teal-and-navy + * proposal look is not shared with any other family today, and every value + * below was measured against the ported template's reference render — a + * theme slot would add indirection without a second consumer. The Northline + * orange variant parameterises the colours when it lands.

    + * + *

    Geometry notes carried over from the ported template: every width is a + * fraction of {@link #CONTENT_WIDTH}; the only hardcoded points are + * dimensions nothing else depends on (icon sizes, badge diameter, logo + * square, footer band height, card padding), each named so a revision + * changes one constant. {@link #BODY_LEADING} is EXTRA leading in points, + * not a multiplier — the engine adds it to the font's own line height.

    + */ +final class NorthlineStyles { + + private NorthlineStyles() { + } + + // --- page --------------------------------------------------------------- + static final double PAGE_WIDTH = 595.276; // A4 portrait, points + static final double PAGE_HEIGHT = 841.890; + static final double MARGIN_SIDE = 36.0; + static final double MARGIN_TOP = 8.0; + /** The denominator of every derived width below. */ + static final double CONTENT_WIDTH = PAGE_WIDTH - 2 * MARGIN_SIDE; + + // --- band splits, measured from the reference --------------------------- + static final double SUMMARY_WEIGHT = 0.62; + static final double GLANCE_WEIGHT = 0.35; + static final double SUMMARY_GAP = CONTENT_WIDTH * 0.03; + static final double INVESTMENT_WEIGHT = 0.59; + static final double TERMS_WEIGHT = 0.35; + static final double MONEY_GAP = CONTENT_WIDTH * 0.06; + static final double DELIVERABLES_LEFT_WEIGHT = 0.48; + static final double DELIVERABLES_RIGHT_WEIGHT = 0.52; + /** PHASE / FOCUS / DURATION / OUTPUT, from the four measured vertical rules. */ + static final double[] TIMELINE_WEIGHTS = {0.232, 0.298, 0.179, 0.291}; + /** Where the scope description column starts. */ + static final double SCOPE_TITLE_SPLIT = 0.448; + + static final double SUMMARY_WIDTH = CONTENT_WIDTH * SUMMARY_WEIGHT; + static final double GLANCE_WIDTH = CONTENT_WIDTH * GLANCE_WEIGHT; + static final double INVESTMENT_WIDTH = CONTENT_WIDTH * INVESTMENT_WEIGHT; + static final double TERMS_WIDTH = CONTENT_WIDTH * TERMS_WEIGHT; + + // --- independent dimensions --------------------------------------------- + static final double BADGE_DIAMETER = 27.0; + static final double BADGE_GLYPH = BADGE_DIAMETER * 0.52; + static final double BADGE_GAP = BADGE_DIAMETER * 0.42; + static final double GOAL_ICON = 20.0; + static final double GOAL_GAP = 7.0; + /** Lines a goal statement wraps to; sets the separator height. */ + static final double GOAL_TEXT_LINES = 3; + static final double FACT_ICON = 25.0; + static final double FACT_GAP = 12.0; + static final double LOGO_SIZE = 50.0; + static final double FOOTER_HEIGHT = 48.0; + static final double PANEL_PADDING = 16.0; + static final double PANEL_RADIUS = 7.0; + static final double SCOPE_BADGE_W = 26.0; + static final double SCOPE_BADGE_H = 15.0; + static final double SCOPE_GAP = 12.0; + static final double TITLE_RULE_WIDTH = CONTENT_WIDTH * 0.086; + static final double PAGE_BLOCK_WIDTH = 62.0; + static final double HAIRLINE = 0.7; + /** Helvetica cap height as a fraction of the type size. */ + static final double HELVETICA_CAP = 0.717; + /** Footer chrome text size (the backend draws footer chrome in Standard-14). */ + static final double FOOTER_TEXT_SIZE = 9.5; + /** + * Not the band height: the backend places the footer baseline at + * (height - fontSize) above the page edge, so this value seats the cap + * band in the middle of a {@link #FOOTER_HEIGHT} band. + */ + static final double FOOTER_ZONE_HEIGHT = + (FOOTER_HEIGHT - FOOTER_TEXT_SIZE * HELVETICA_CAP) / 2 + FOOTER_TEXT_SIZE; + static final double WORDMARK_WIDTH = CONTENT_WIDTH * 0.19; + static final double DOC_LABEL_WIDTH = CONTENT_WIDTH * 0.16; + static final double SIGNATURE_LABEL_WIDTH = CONTENT_WIDTH * 0.115; + + // --- vertical rhythm ----------------------------------------------------- + static final double GAP_SECTION = 18.0; + /** + * The gap above a band, carried by the band rather than by the page + * flow: flow spacing survives a page break and would indent the + * page-two header, so the flow spacing stays zero and the rhythm lives + * on the bands — a header carries no leading space on any page. + */ + static final float BAND_TOP = (float) GAP_SECTION; + static final double GAP_HEADING = 7.0; + static final double GAP_ROW = 5.0; + /** Inner padding of the acceptance card; the glance card keeps PANEL_PADDING. */ + static final double ACCEPTANCE_PADDING = 11.0; + /** Gap between the acceptance statement and the signature fields. */ + static final double ACCEPTANCE_FIELD_GAP = 13.0; + /** Vertical padding inside a scope row, each side (measured). */ + static final double SCOPE_ROW_PAD = 5.0; + /** Vertical padding in an investment cell, each side (measured). */ + static final double INVESTMENT_ROW_PAD = 4.0; + /** Gap above and below a divider between glance facts. */ + static final double FACT_DIVIDER_GAP = 7.0; + /** Glance label / value line heights, as multiples of the type size. */ + static final double FACT_LINE = 1.4; + static final double FACT_VALUE_LINE = 1.3; + + // --- type scale ---------------------------------------------------------- + static final double BODY_SIZE = 10.0; + /** EXTRA leading in points (added to the font's own line height). */ + static final double BODY_LEADING = 1.45; + /** Lato line height as a multiple of the type size (measured). */ + static final double LATO_LINE = 1.213; + static final double TITLE_SIZE = 46.0; + /** The reference's title pitch between stacked title lines. */ + static final double TITLE_PITCH = 48.0; + /** Height reserved for the last title line inside the stacked-title container. */ + static final double TITLE_LAST_LINE_HEIGHT = TITLE_SIZE * 0.95; + static final double HEADING_SIZE = 13.5; + static final double TABLE_SIZE = 9.0; + static final double LABEL_SIZE = 8.0; + static final double FACT_VALUE_SIZE = 14.0; + + // --- theme tokens: role names, not colour names --------------------------- + static final DocumentColor INK = DocumentColor.rgb(1, 25, 52); + static final DocumentColor ACCENT = DocumentColor.rgb(20, 111, 120); + static final DocumentColor PAGE_BACKGROUND = DocumentColor.rgb(252, 252, 252); + static final DocumentColor PANEL_BACKGROUND = DocumentColor.rgb(245, 246, 247); + static final DocumentColor RULE = DocumentColor.rgb(203, 208, 213); + static final DocumentColor RULE_QUIET = DocumentColor.rgb(228, 231, 234); + static final DocumentColor BODY_TEXT = DocumentColor.rgb(39, 51, 75); + static final DocumentColor MUTED_TEXT = DocumentColor.rgb(85, 93, 109); + static final DocumentColor ON_DARK = DocumentColor.rgb(255, 255, 255); + + static final FontName TITLE_FONT = FontName.SPECTRAL; + static final FontName BODY_FONT = FontName.LATO; + + // --- text roles ----------------------------------------------------------- + static final DocumentTextStyle TITLE_INK = + style(TITLE_FONT, TITLE_SIZE, DocumentTextDecoration.BOLD, INK); + static final DocumentTextStyle TITLE_ACCENT = + style(TITLE_FONT, TITLE_SIZE, DocumentTextDecoration.BOLD, ACCENT); + static final DocumentTextStyle SECTION_HEADING = + style(BODY_FONT, HEADING_SIZE, DocumentTextDecoration.BOLD, INK); + static final DocumentTextStyle PANEL_HEADING = + style(BODY_FONT, BODY_SIZE + 0.5, DocumentTextDecoration.BOLD, ACCENT); + static final DocumentTextStyle BODY = + style(BODY_FONT, BODY_SIZE, DocumentTextDecoration.DEFAULT, BODY_TEXT); + static final DocumentTextStyle META = + style(BODY_FONT, LABEL_SIZE, DocumentTextDecoration.BOLD, MUTED_TEXT); + static final DocumentTextStyle META_SEPARATOR = + style(BODY_FONT, LABEL_SIZE, DocumentTextDecoration.DEFAULT, RULE); + static final DocumentTextStyle FACT_LABEL = + style(BODY_FONT, LABEL_SIZE, DocumentTextDecoration.BOLD, MUTED_TEXT); + static final DocumentTextStyle FACT_VALUE = + style(BODY_FONT, FACT_VALUE_SIZE, DocumentTextDecoration.BOLD, INK); + static final DocumentTextStyle FACT_NOTE = + style(BODY_FONT, LABEL_SIZE + 0.5, DocumentTextDecoration.DEFAULT, BODY_TEXT); + static final DocumentTextStyle GOAL_TEXT = + style(BODY_FONT, TABLE_SIZE, DocumentTextDecoration.DEFAULT, BODY_TEXT); + static final DocumentTextStyle SCOPE_TITLE = + style(BODY_FONT, TABLE_SIZE + 0.5, DocumentTextDecoration.BOLD, INK); + static final DocumentTextStyle SCOPE_BODY = + style(BODY_FONT, TABLE_SIZE, DocumentTextDecoration.DEFAULT, MUTED_TEXT); + static final DocumentTextStyle SCOPE_NUMBER = + style(BODY_FONT, LABEL_SIZE + 0.5, DocumentTextDecoration.BOLD, ON_DARK); + static final DocumentTextStyle TABLE_HEADER = + style(BODY_FONT, TABLE_SIZE - 0.5, DocumentTextDecoration.BOLD, ON_DARK); + static final DocumentTextStyle TABLE_BODY = + style(BODY_FONT, TABLE_SIZE, DocumentTextDecoration.DEFAULT, BODY_TEXT); + static final DocumentTextStyle TABLE_BODY_BOLD = + style(BODY_FONT, TABLE_SIZE, DocumentTextDecoration.BOLD, INK); + static final DocumentTextStyle TOTAL_TEXT = + style(BODY_FONT, TABLE_SIZE + 1.5, DocumentTextDecoration.BOLD, ON_DARK); + static final DocumentTextStyle WORDMARK = + style(BODY_FONT, 16.0, DocumentTextDecoration.BOLD, INK); + static final DocumentTextStyle MONOGRAM = + style(BODY_FONT, 30.0, DocumentTextDecoration.BOLD, ON_DARK); + /** The document label, spaced on the style so its caller passes the words. */ + static final DocumentTextStyle DOC_LABEL = + style(BODY_FONT, LABEL_SIZE + 1, DocumentTextDecoration.BOLD, INK) + .withLetterSpacing(TextOrnaments.SPACED_CAPS); + static final DocumentTextStyle SIGNATURE_LABEL = + style(BODY_FONT, TABLE_SIZE, DocumentTextDecoration.DEFAULT, INK); + + private static DocumentTextStyle style(FontName font, + double size, + DocumentTextDecoration decoration, + DocumentColor color) { + return DocumentTextStyle.builder() + .fontName(font) + .size(size) + .decoration(decoration) + .color(color) + .build(); + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/proposal/presets/NorthlineWidgets.java b/templates/src/main/java/com/demcha/compose/document/templates/proposal/presets/NorthlineWidgets.java new file mode 100644 index 000000000..8443918aa --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/proposal/presets/NorthlineWidgets.java @@ -0,0 +1,208 @@ +package com.demcha.compose.document.templates.proposal.presets; + +import com.demcha.compose.document.dsl.ImageBuilder; +import com.demcha.compose.document.dsl.LineBuilder; +import com.demcha.compose.document.dsl.ParagraphBuilder; +import com.demcha.compose.document.dsl.SectionBuilder; +import com.demcha.compose.document.dsl.ShapeContainerBuilder; +import com.demcha.compose.document.node.DocumentNode; +import com.demcha.compose.document.node.HorizontalAlign; +import com.demcha.compose.document.node.LayerAlign; +import com.demcha.compose.document.node.RowVerticalAlign; +import com.demcha.compose.document.node.TextAlign; +import com.demcha.compose.document.node.TextVerticalAlign; +import com.demcha.compose.document.style.ClipPolicy; +import com.demcha.compose.document.style.DocumentInsets; +import com.demcha.compose.document.style.DocumentRowColumn; +import com.demcha.compose.document.style.DocumentTextIndent; +import com.demcha.compose.document.style.DocumentTextStyle; +import com.demcha.compose.document.templates.core.text.TextOrnaments; +import com.demcha.compose.document.templates.data.proposal.ProposalBrand; + +import static com.demcha.compose.document.templates.proposal.presets.NorthlineStyles.ACCENT; +import static com.demcha.compose.document.templates.proposal.presets.NorthlineStyles.BADGE_DIAMETER; +import static com.demcha.compose.document.templates.proposal.presets.NorthlineStyles.BADGE_GAP; +import static com.demcha.compose.document.templates.proposal.presets.NorthlineStyles.BADGE_GLYPH; +import static com.demcha.compose.document.templates.proposal.presets.NorthlineStyles.BODY; +import static com.demcha.compose.document.templates.proposal.presets.NorthlineStyles.BODY_LEADING; +import static com.demcha.compose.document.templates.proposal.presets.NorthlineStyles.DOC_LABEL; +import static com.demcha.compose.document.templates.proposal.presets.NorthlineStyles.DOC_LABEL_WIDTH; +import static com.demcha.compose.document.templates.proposal.presets.NorthlineStyles.GAP_HEADING; +import static com.demcha.compose.document.templates.proposal.presets.NorthlineStyles.GAP_ROW; +import static com.demcha.compose.document.templates.proposal.presets.NorthlineStyles.HAIRLINE; +import static com.demcha.compose.document.templates.proposal.presets.NorthlineStyles.INK; +import static com.demcha.compose.document.templates.proposal.presets.NorthlineStyles.LOGO_SIZE; +import static com.demcha.compose.document.templates.proposal.presets.NorthlineStyles.MONOGRAM; +import static com.demcha.compose.document.templates.proposal.presets.NorthlineStyles.RULE; +import static com.demcha.compose.document.templates.proposal.presets.NorthlineStyles.SECTION_HEADING; +import static com.demcha.compose.document.templates.proposal.presets.NorthlineStyles.WORDMARK; +import static com.demcha.compose.document.templates.proposal.presets.NorthlineStyles.WORDMARK_WIDTH; + +/** + * The building blocks the Northline bands share: the page header, the + * icon-badged section heading, the teal bullet, and the small node helpers. + * + *

    Constraint notes carried over from the ported template: the engine + * refuses a row nested inside another row's cell, so the header flattens + * its columns, and the section heading is a shape container hosting two + * anchored children rather than a row — half its uses sit inside a row + * cell. The heading container carries no fill and no margin (a margin + * would displace its paint); the gap above it comes from the enclosing + * flow's spacing.

    + */ +final class NorthlineWidgets { + + private NorthlineWidgets() { + } + + /** + * Logo square, wordmark, document label and the hairline that closes + * the header. One flat row: the logo is a shape container owning its + * monogram, the wordmark and the label are sections, and the weight(1) + * column between them holds the two ends apart. + */ + static void renderHeader(SectionBuilder header, ProposalBrand brand) { + header.spacing(0); + header.addRow("HeaderRow", row -> { + row.verticalAlign(RowVerticalAlign.CENTER) + .gap(LOGO_SIZE * 0.25) + // Not auto(): a section's natural width is the width it + // is offered, so two auto columns each claim the whole + // row. Both ends are measured strips instead. + .columns(DocumentRowColumn.fixed(LOGO_SIZE), + DocumentRowColumn.fixed(WORDMARK_WIDTH), + DocumentRowColumn.weight(1), + DocumentRowColumn.fixed(DOC_LABEL_WIDTH)); + row.add(logoMark(brand.monogram())); + row.addSection("Wordmark", mark -> { + mark.spacing(0); + mark.addParagraph(p -> p.text(brand.nameLine1()).textStyle(WORDMARK)); + mark.addParagraph(p -> p.text(brand.nameLine2()).textStyle(WORDMARK)); + }); + // A zero-width spacer: the row distributes by columns, and the + // weight(1) column above already holds the ends apart. + row.addSpacer(0); + row.addSection("DocumentLabel", label -> { + label.spacing(0); + label.addParagraph(p -> p + .text(TextOrnaments.upper(brand.documentLabel())) + .textStyle(DOC_LABEL) + .align(TextAlign.RIGHT)); + // addAligned, not addLine: a section stacks children at the + // content width with no child-alignment option, and the rule + // sits flush RIGHT under the label. + label.addAligned(HorizontalAlign.RIGHT, new LineBuilder() + .name("DocumentLabelRule") + .horizontal(BADGE_DIAMETER * 1.3) + .thickness(2.2) + .color(ACCENT) + .margin(DocumentInsets.top(6)) + .build()); + }); + }); + header.addLine(line -> line + .fill() + .thickness(HAIRLINE) + .color(RULE) + .margin(DocumentInsets.top(GAP_HEADING))); + } + + /** The navy square and the monogram it owns. */ + private static DocumentNode logoMark(String monogram) { + return new ShapeContainerBuilder() + .name("LogoMark") + .rectangle(LOGO_SIZE, LOGO_SIZE) + .fillColor(INK) + .center(paragraph("Monogram", monogram, MONOGRAM, TextAlign.CENTER)) + .build(); + } + + /** + * The heading every section opens with: a teal badge owning a white + * glyph, and the title vertically centred beside it. + * + * @param title the heading text, already uppercase in the data + * @param iconToken the icon token from the data + * @param width the width of the column the heading sits in + */ + static DocumentNode sectionHeading(String title, String iconToken, double width) { + return new ShapeContainerBuilder() + .name("SectionHeading") + .rectangle(width, BADGE_DIAMETER) + .clipPolicy(ClipPolicy.OVERFLOW_VISIBLE) + .position(badge(iconToken), 0, 0, LayerAlign.CENTER_LEFT) + .position(new ParagraphBuilder() + .name("SectionHeadingTitle") + .text(title) + .textStyle(SECTION_HEADING) + .verticalAlign(TextVerticalAlign.CENTER) + .build(), BADGE_DIAMETER + BADGE_GAP, 0, LayerAlign.CENTER_LEFT) + .build(); + } + + /** + * The teal disc and the glyph it owns. A blank token leaves the disc + * plain — the container still needs a layer, so an empty paragraph + * stands in for the glyph. + */ + private static DocumentNode badge(String iconToken) { + ShapeContainerBuilder badge = new ShapeContainerBuilder() + .name("SectionBadge") + .circle(BADGE_DIAMETER) + .fillColor(ACCENT); + if (iconToken.isBlank()) { + badge.center(new ParagraphBuilder() + .name("BadgeGlyph").text("").textStyle(SECTION_HEADING).build()); + } else { + badge.center(icon(iconToken, BADGE_GLYPH)); + } + return badge.build(); + } + + /** + * A bullet item: an inline teal dot followed by the text, with a + * hanging indent so wrapped lines clear the dot. The dot is an inline + * shape run rather than a bullet glyph, so it keeps its own colour and + * cannot be lost to font coverage. + */ + static void renderBullet(SectionBuilder host, String text) { + host.addParagraph(p -> p + .dot(4.2, ACCENT) + .inlineText(" " + text, BODY) + .bulletOffset(" ") + // Wrapped lines align under the first line text, not the dot. + .indentStrategy(DocumentTextIndent.FROM_SECOND_LINE) + .lineSpacing(BODY_LEADING) + .margin(0f, 0f, (float) GAP_ROW, 0f)); + } + + /** A named, vertically centred paragraph node. */ + static DocumentNode paragraph(String name, + String text, + DocumentTextStyle textStyle, + TextAlign align) { + return new ParagraphBuilder() + .name(name) + .text(text) + .textStyle(textStyle) + .align(align) + .verticalAlign(TextVerticalAlign.CENTER) + .build(); + } + + /** One packaged icon at a given size, as a node. */ + static DocumentNode icon(String token, double size) { + return new ImageBuilder() + .name("Icon-" + token) + .source(NorthlineIcons.image(token)) + .size(size, size) + .build(); + } + + /** The same icon, configured in place on a row's own image builder. */ + static void configureIcon(ImageBuilder image, String token, double size) { + image.name("Icon-" + token) + .source(NorthlineIcons.image(token)) + .size(size, size); + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/proposal/presets/package-info.java b/templates/src/main/java/com/demcha/compose/document/templates/proposal/presets/package-info.java index 655ef0ec1..afedd4563 100644 --- a/templates/src/main/java/com/demcha/compose/document/templates/proposal/presets/package-info.java +++ b/templates/src/main/java/com/demcha/compose/document/templates/proposal/presets/package-info.java @@ -1,16 +1,29 @@ /** - * Layered proposal presets — thin orchestrators that compose a proposal - * on a {@link com.demcha.compose.document.templates.core.theme.BrandTheme} - * plus the canonical DSL. + * Layered proposal presets — thin orchestrators that compose a proposal on + * the canonical DSL. * - *

    This package mirrors {@code invoice.presets}: each preset is a - * {@code final} class with a {@code create(BrandTheme)} factory returning - * a {@link com.demcha.compose.document.templates.api.DocumentTemplate} - * parameterised on - * {@link com.demcha.compose.document.templates.data.proposal.ProposalDocumentSpec}. - * The presets read every visual value from the theme and reuse the shared - * {@code templates.core.*} layer; the proposal family does not own a theme - * type of its own.

    + *

    Each preset is a {@code final} class whose {@code create} factory + * returns a + * {@link com.demcha.compose.document.templates.api.DocumentTemplate}, and + * the package holds two shapes of them:

    + * + *
      + *
    • Narrative presets take a {@code create(BrandTheme)} factory + * and are parameterised on + * {@link com.demcha.compose.document.templates.data.proposal.ProposalDocumentSpec}, + * reading every visual value from the theme + * ({@code ModernProposal}).
    • + *
    • Structured presets take a no-argument {@code create()} and + * are parameterised on + * {@link com.demcha.compose.document.templates.data.proposal.StructuredProposalDocumentSpec}, + * carrying their measured geometry and palette as preset-local tokens + * because each reproduces one designed look rather than a themeable + * family ({@code NorthlineProposal}, {@code EditorialProposal}, + * {@code IndigoProposal}).
    • + *
    + * + *

    Both shapes reuse the shared {@code templates.core.*} layer; the + * proposal family does not own a theme type of its own.

    * * @since 2.0.0 */ diff --git a/templates/src/main/java/com/demcha/compose/document/templates/receipt/AUTHORS.md b/templates/src/main/java/com/demcha/compose/document/templates/receipt/AUTHORS.md new file mode 100644 index 000000000..dd07ffa98 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/receipt/AUTHORS.md @@ -0,0 +1,173 @@ +# Receipt family — recipe cookbook + +Everything here builds on `ModernReceipt`, the reference preset. Each +recipe is a change you can make without forking a widget. + +Read [`../../../../../../../../../docs/templates/v2-layered/authoring-presets.md`](../../../../../../../../../docs/templates/v2-layered/authoring-presets.md) +first if you have not — it explains why the layers look the way they do. + +--- + +## 0. The shortest complete receipt + +```java +ReceiptDocumentSpec spec = ReceiptDocumentSpec.of(receipt -> receipt + .documentTitle("Transfer confirmation") + .issuerName("Northwind Pay") + .generatedOn("09 August 2026") + .amount("Amount sent", "£66.62") + .status(ReceiptStatus.settled("Completed")) + .payer("Paid from", p -> p.name("Alex Sample").field("Account", "•••• 4396")) + .beneficiary("Paid to", p -> p.name("Harbour Finance Ltd"))); + +BrandTheme theme = BrandTheme.receiptModern(); +float m = (float) ModernReceipt.RECOMMENDED_MARGIN; +try (DocumentSession document = GraphCompose.document(out) + .pageSize(DocumentPageSize.A4) + .pageBackground(theme.palette().mainFill()) + .margin(m, m, m, m) + .create()) { + ModernReceipt.create(theme).compose(document, spec); + document.buildPdf(); +} +``` + +Every block you did not fill — detail groups, timeline, notes, QR code — +drops out rather than rendering an empty heading. + +--- + +## 1. Brand it for a different issuer + +The theme carries **no** brand colour. An issuer arrives through +`Options`: + +```java +ModernReceipt.Options options = ModernReceipt.Options + .branded(SvgGlyph.fromResource("/brand/northwind-pay-mark.svg"), + DocumentColor.rgb(23, 92, 211)) + .withLogoWidth(14) // a symbol mark, sized to the name beside it + .withLogoColor(DocumentColor.rgb(23, 92, 211)); + +ModernReceipt.create(BrandTheme.receiptModern(), options); +``` + +The accent reaches the hero's left strip, the direction arrow between the +parties, the reached step of the status trail, and an in-progress status +chip. Nothing else is tinted — which is what keeps the numbers readable. + +**Wordmark instead of a symbol?** Leave `issuerName` blank in the data and +size the mark to its real width: + +```java +ModernReceipt.Options.branded(SvgGlyph.fromFile(Path.of("brand/acme.svg")), accent) + .withLogoWidth(78) + .withLogoColor(DocumentColor.rgb(13, 15, 20)); +``` + +`SvgGlyph` flattens the file's filled layers into one recolourable +silhouette, so a single logo file serves a dark mark on white and a light +one on a dark page. + +--- + +## 2. Add a block the template has never heard of + +A detail group is just a title and rows: + +```java +receipt.detailGroup("Fees and charges", group -> group + .field("Correspondent fee", "£3.50") + .field("FX margin", "0.35%") + .emphasized("Total charged", "£70.47")); +``` + +`emphasized` sets the value in the bold weight — use it for the one row per +group a reader looks for first. Groups render in the order you add them, +each keeping together across a page break. + +--- + +## 3. Say what your payment system says + +`ReceiptStatus` carries the issuer's own word plus the tone a template may +colour on: + +```java +receipt.status("Being processed", ReceiptStatusTone.IN_PROGRESS); +receipt.status("Returned by the beneficiary bank", ReceiptStatusTone.ATTENTION); +receipt.status("Rejected", ReceiptStatusTone.FAILED); +``` + +Settled is green, attention amber, failed red, in-progress the issuer's +accent — in every theme. That is deliberate: a reader checking whether +money arrived should not have to learn a colour scheme per bank. + +--- + +## 4. Show how the payment got there + +```java +receipt.event("Instructed", "07 Jul 2026, 08:12 BST", "Submitted to the scheme.") + .event("In clearing", "08 Jul 2026, 09:00 BST", "Three-working-day Bacs cycle.") + .event("Settled", "09 Jul 2026, 06:30 BST", "Confirmed by the receiving bank."); +``` + +Steps render oldest first; the last one takes the accent dot, so the eye +lands on where the payment got to without reading a timestamp. Rename the +heading with `Options.withTimelineTitle("Where it got to")`, or pass no +events at all and the block disappears. + +--- + +## 5. Make it verifiable + +```java +receipt.verification("https://pay.example.com/v/NWP-4821-0067", + "Scan to check this confirmation against our records.") + .supportLine("Support +44 20 0000 0000") + .supportLine("help@example.com") + .legalNote("…registered in England and Wales…"); +``` + +These three make up the footer, which the preset seats on the bottom +margin when the receipt owns the document. The QR code encodes the URL +verbatim — it is what makes a printed copy resolvable again. + +--- + +## 6. Change the look without touching a widget + +Every size, colour, and gap comes from the `BrandTheme`. To make a denser +receipt, build a theme from the shipped parts and swap the one you want: + +```java +BrandTheme dense = new BrandTheme( + Palette.receiptModern(), + Typography.receiptModern(), + Spacing.invoiceModern(), // tighter block rhythm + Decoration.classic()); +ModernReceipt.create(dense, options); +``` + +The slots carry receipt-specific readings worth knowing: + +| Token | On a receipt it is | +|---|---| +| `typography.sizeHeadline` | the hero **amount** — the largest thing on the page | +| `typography.sizeEntryTitle` | the document title and the party names | +| `typography.sizeEntryDate` | field values in the right-hand column | +| `typography.sizeBanner` | spaced-caps group headings and eyebrow labels | +| `spacing.accentRuleWidth` | the hairline rules, not a wide accent strip | +| `spacing.bannerCornerRadius` | the corner radius of the hero and party panels | + +--- + +## 7. Put a receipt inside a bigger document + +`compose` seats the footer on the bottom margin by re-composing once — and +that clears the session, so it only happens when the session was empty +when you called it. Compose a cover page first and the receipt still +renders correctly; its footer simply flows after the body instead of +pinning. The page-number chrome is installed either way, and a caller who +wants different chrome sets it after composing. diff --git a/templates/src/main/java/com/demcha/compose/document/templates/receipt/components/FieldRowRenderer.java b/templates/src/main/java/com/demcha/compose/document/templates/receipt/components/FieldRowRenderer.java new file mode 100644 index 000000000..06e5ccbae --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/receipt/components/FieldRowRenderer.java @@ -0,0 +1,156 @@ +package com.demcha.compose.document.templates.receipt.components; + +import com.demcha.compose.document.dsl.SectionBuilder; +import com.demcha.compose.document.node.RowVerticalAlign; +import com.demcha.compose.document.node.TextAlign; +import com.demcha.compose.document.style.DocumentInsets; +import com.demcha.compose.document.style.DocumentLineCap; +import com.demcha.compose.document.style.DocumentRowColumn; +import com.demcha.compose.document.style.DocumentStroke; +import com.demcha.compose.document.style.DocumentTextStyle; +import com.demcha.compose.document.templates.core.theme.BrandTheme; +import com.demcha.compose.document.templates.data.receipt.ReceiptField; + +import java.util.List; +import java.util.Objects; + +/** + * Field rows in the two forms a receipt needs. + * + *

    {@link #renderAll} is the full-width form: label on the left, value hard + * against the right edge, and a dotted leader stretched between them. The + * leader is the same construction the table-of-contents builder uses, and it + * is here for the same reason — across a full page width a bare label and a + * bare value read as two unrelated columns, and the reader has to track which + * value belongs to which row. Values right-align because that is what makes a + * column of them scannable: a reader comparing two account numbers compares + * their ends, not their beginnings.

    + * + *

    {@link #renderInline} is the narrow form, and it exists for a hard engine + * rule rather than for taste: a row cannot contain another row, so fields + * sitting inside a column of the hero panel or of the party panel cannot use + * the leader form. They render as one paragraph per field, label and value on + * the same line in their own weights — which is legible at that width without + * a leader.

    + */ +public final class FieldRowRenderer { + + /** Gap under each row, in points. */ + private static final double ROW_BOTTOM_PADDING = 4.0; + + /** Horizontal gap between the label, the leader, and the value. */ + private static final double LEADER_GAP = 6.0; + + /** + * A bottom-aligned line lands on the descender line; lifting it by roughly + * the font's descent seats it on the text baseline. Same approximation the + * table-of-contents builder makes, for the same reason: the exact descent + * is a font metric the DSL layer cannot reach. + */ + private static final double LEADER_BASELINE_LIFT_RATIO = 0.2; + + private static final double LEADER_THICKNESS = 1.0; + + /** Dash pattern that renders as dots once the caps are rounded. */ + private static final double LEADER_DOT = 0.1; + + private static final double LEADER_DOT_GAP = 4.0; + + private FieldRowRenderer() { + } + + /** + * Renders every field of a block as a leader row. + * + * @param host section the rows are appended to + * @param fields rows to render; an empty list renders nothing + * @param theme active theme + */ + public static void renderAll(SectionBuilder host, List fields, BrandTheme theme) { + Objects.requireNonNull(host, "host"); + Objects.requireNonNull(theme, "theme"); + if (fields == null || fields.isEmpty()) { + return; + } + for (ReceiptField field : fields) { + render(host, field, theme); + } + } + + /** + * Renders every field as a single line each — the form to use inside a + * column that is already part of a row. + * + * @param host section the lines are appended to + * @param fields rows to render; an empty list renders nothing + * @param align where each line sits in its column + * @param theme active theme + */ + public static void renderInline(SectionBuilder host, List fields, + TextAlign align, BrandTheme theme) { + Objects.requireNonNull(host, "host"); + Objects.requireNonNull(theme, "theme"); + if (fields == null || fields.isEmpty()) { + return; + } + DocumentTextStyle labelStyle = ReceiptStyles.label(theme); + TextAlign lineAlign = align == null ? TextAlign.LEFT : align; + for (ReceiptField field : fields) { + DocumentTextStyle valueStyle = field.emphasized() + ? ReceiptStyles.valueStrong(theme) + : ReceiptStyles.value(theme); + host.addParagraph(p -> p + .inlineText(field.label() + " ", labelStyle) + .inlineText(field.value(), valueStyle) + .align(lineAlign) + .margin(DocumentInsets.zero())); + } + } + + /** + * Renders one leader row. + * + * @param host section the row is appended to + * @param field the label/value pair + * @param theme active theme + */ + public static void render(SectionBuilder host, ReceiptField field, BrandTheme theme) { + Objects.requireNonNull(host, "host"); + Objects.requireNonNull(field, "field"); + Objects.requireNonNull(theme, "theme"); + + DocumentTextStyle labelStyle = ReceiptStyles.label(theme); + DocumentTextStyle valueStyle = field.emphasized() + ? ReceiptStyles.valueStrong(theme) + : ReceiptStyles.value(theme); + double baselineLift = labelStyle.size() * LEADER_BASELINE_LIFT_RATIO; + + host.addRow("ReceiptFieldRow", row -> row + .gap(LEADER_GAP) + // Bottom-aligned so the leader lands on the row's baseline rather + // than riding along the top of the line. + .verticalAlign(RowVerticalAlign.BOTTOM) + .columns(DocumentRowColumn.auto(), + DocumentRowColumn.weight(1), + DocumentRowColumn.auto()) + .padding(new DocumentInsets(0, 0, ROW_BOTTOM_PADDING, 0)) + .addParagraph(p -> p + .text(field.label()) + .textStyle(labelStyle) + .margin(DocumentInsets.zero())) + .addLine(line -> line + .fill() + .stroke(DocumentStroke.of(theme.palette().rule(), LEADER_THICKNESS)) + .dashed(LEADER_DOT, LEADER_DOT_GAP) + .lineCap(DocumentLineCap.ROUND) + .margin(DocumentInsets.bottom(baselineLift))) + // No explicit right alignment: the column is auto-sized to the + // value, so the text already ends at the row's right edge — and + // asking for RIGHT here would make the paragraph claim the full + // row width, which an auto column cannot grant. + .addParagraph(p -> p + .text(field.value()) + .textStyle(valueStyle) + .margin(DocumentInsets.zero()))); + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/receipt/components/ReceiptStyles.java b/templates/src/main/java/com/demcha/compose/document/templates/receipt/components/ReceiptStyles.java new file mode 100644 index 000000000..69cccd6da --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/receipt/components/ReceiptStyles.java @@ -0,0 +1,168 @@ +package com.demcha.compose.document.templates.receipt.components; + +import com.demcha.compose.document.style.DocumentColor; +import com.demcha.compose.document.style.DocumentTextDecoration; +import com.demcha.compose.document.style.DocumentTextStyle; +import com.demcha.compose.document.templates.core.text.TextOrnaments; +import com.demcha.compose.document.templates.core.text.TextStyles; +import com.demcha.compose.document.templates.core.theme.BrandTheme; + +/** + * The text styles a receipt is built from, each composed once from the + * {@link BrandTheme}. + * + *

    A receipt has few kinds of text but uses each of them many times: the + * same grey label appears on every field row, in both party cards, and on + * every timeline entry. Naming them once here is what keeps the widgets free + * of style plumbing, and what makes a change to the type scale a change to + * one file rather than to eight.

    + * + *

    Every value reads from the theme — the receipt themes' + * {@link com.demcha.compose.document.templates.core.theme.Typography} slots + * are documented against these names.

    + */ +public final class ReceiptStyles { + + private ReceiptStyles() { + } + + /** + * Small spaced-caps caption above a block — {@code AMOUNT SENT}. + * + *

    Spaced on the style, so a caller hands over the words rather than a + * padded-out copy of them.

    + * + * @param theme active theme + * @return the eyebrow style + */ + public static DocumentTextStyle eyebrow(BrandTheme theme) { + return TextStyles.of(theme.typography().bodyFont(), theme.typography().sizeBanner(), + DocumentTextDecoration.DEFAULT, theme.palette().muted()) + .withLetterSpacing(TextOrnaments.SPACED_CAPS); + } + + /** + * Spaced-caps heading over a field group — {@code TRANSFER DETAILS}. + * + *

    Spaced on the style, as {@link #eyebrow(BrandTheme)} is.

    + * + * @param theme active theme + * @return the group-heading style + */ + public static DocumentTextStyle groupTitle(BrandTheme theme) { + return TextStyles.of(theme.typography().bodyFont(), theme.typography().sizeBanner(), + DocumentTextDecoration.BOLD, theme.palette().ink()) + .withLetterSpacing(TextOrnaments.SPACED_CAPS); + } + + /** + * The document heading — {@code Transfer confirmation}. + * + * @param theme active theme + * @return the document-title style + */ + public static DocumentTextStyle title(BrandTheme theme) { + return TextStyles.of(theme.typography().headlineFont(), theme.typography().sizeEntryTitle(), + DocumentTextDecoration.BOLD, theme.palette().ink()); + } + + /** + * Quiet one-liner under a title or an amount. + * + * @param theme active theme + * @return the caption style + */ + public static DocumentTextStyle caption(BrandTheme theme) { + return TextStyles.of(theme.typography().bodyFont(), theme.typography().sizeEntrySubtitle(), + DocumentTextDecoration.DEFAULT, theme.palette().muted()); + } + + /** + * Left column of a field row. + * + * @param theme active theme + * @return the field-label style + */ + public static DocumentTextStyle label(BrandTheme theme) { + return TextStyles.of(theme.typography().bodyFont(), theme.typography().sizeEntryDate(), + DocumentTextDecoration.DEFAULT, theme.palette().muted()); + } + + /** + * Right column of a field row. + * + * @param theme active theme + * @return the field-value style + */ + public static DocumentTextStyle value(BrandTheme theme) { + return TextStyles.of(theme.typography().bodyFont(), theme.typography().sizeEntryDate(), + DocumentTextDecoration.DEFAULT, theme.palette().ink()); + } + + /** + * Right column of the one field row per group a reader looks for first. + * + * @param theme active theme + * @return the emphasised field-value style + */ + public static DocumentTextStyle valueStrong(BrandTheme theme) { + return TextStyles.of(theme.typography().bodyFont(), theme.typography().sizeEntryDate(), + DocumentTextDecoration.BOLD, theme.palette().ink()); + } + + /** + * The hero amount — the largest thing on the page. + * + * @param theme active theme + * @return the amount style + */ + public static DocumentTextStyle amount(BrandTheme theme) { + return TextStyles.of(theme.typography().headlineFont(), theme.typography().sizeHeadline(), + DocumentTextDecoration.BOLD, theme.palette().ink()); + } + + /** + * Account holder or beneficiary name inside a party card. + * + * @param theme active theme + * @return the party-name style + */ + public static DocumentTextStyle partyName(BrandTheme theme) { + return TextStyles.of(theme.typography().bodyFont(), theme.typography().sizeBody(), + DocumentTextDecoration.BOLD, theme.palette().ink()); + } + + /** + * Body prose — notes and address lines. + * + * @param theme active theme + * @return the body style + */ + public static DocumentTextStyle body(BrandTheme theme) { + return TextStyles.of(theme.typography().bodyFont(), theme.typography().sizeBody(), + DocumentTextDecoration.DEFAULT, theme.palette().ink()); + } + + /** + * Footer small print and support lines. + * + * @param theme active theme + * @return the small-print style + */ + public static DocumentTextStyle smallPrint(BrandTheme theme) { + return TextStyles.of(theme.typography().bodyFont(), theme.typography().sizeContact(), + DocumentTextDecoration.DEFAULT, theme.palette().muted()); + } + + /** + * Text inside the status pill, coloured by the caller. + * + * @param theme active theme + * @param color pill foreground, chosen from the status tone + * @return the status-pill text style + */ + public static DocumentTextStyle pill(BrandTheme theme, DocumentColor color) { + return TextStyles.of(theme.typography().bodyFont(), theme.typography().sizeBanner(), + DocumentTextDecoration.BOLD, color); + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/receipt/components/StatusPill.java b/templates/src/main/java/com/demcha/compose/document/templates/receipt/components/StatusPill.java new file mode 100644 index 000000000..02f027706 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/receipt/components/StatusPill.java @@ -0,0 +1,96 @@ +package com.demcha.compose.document.templates.receipt.components; + +import com.demcha.compose.document.dsl.SectionBuilder; +import com.demcha.compose.document.node.TextAlign; +import com.demcha.compose.document.style.DocumentColor; +import com.demcha.compose.document.style.DocumentInsets; +import com.demcha.compose.document.templates.core.theme.BrandTheme; +import com.demcha.compose.document.templates.data.receipt.ReceiptStatus; +import com.demcha.compose.document.templates.data.receipt.ReceiptStatusTone; + +import java.util.Objects; + +/** + * The rounded status chip — {@code Completed}, {@code Processing}, + * {@code Returned} — rendered as an inline highlight so the pill hugs its + * label instead of stretching across the column. + * + *

    Why the tone colours live here and not in the theme

    + * + *

    These four pairs are semantic, not cosmetic: a settled payment reads + * green and a failed one reads red in every brand's statement, and a reader + * checking whether money arrived should not have to learn a new colour per + * issuer. Putting them in {@code Palette} would invite a theme to make + * "failed" green, which is the one thing a receipt must never do.

    + * + *

    The issuer's own accent still reaches the pill: an in-progress payment + * has no universal colour, so it takes the brand accent the caller + * supplies.

    + */ +public final class StatusPill { + + /** Settled: money arrived. */ + private static final DocumentColor SETTLED_INK = DocumentColor.rgb(13, 110, 74); + private static final DocumentColor SETTLED_FILL = DocumentColor.rgb(226, 244, 235); + + /** Attention: held, returned, disputed — someone has to look at it. */ + private static final DocumentColor ATTENTION_INK = DocumentColor.rgb(146, 94, 4); + private static final DocumentColor ATTENTION_FILL = DocumentColor.rgb(253, 243, 222); + + /** Failed: the payment did not happen. */ + private static final DocumentColor FAILED_INK = DocumentColor.rgb(176, 42, 34); + private static final DocumentColor FAILED_FILL = DocumentColor.rgb(251, 234, 233); + + /** Corner radius of the chip, in points — a full round on an 8pt label. */ + private static final double PILL_RADIUS = 9.0; + + private static final DocumentInsets PILL_PADDING = new DocumentInsets(3.5, 9, 3.5, 9); + + private StatusPill() { + } + + /** + * Renders the chip as its own paragraph. + * + * @param host section the chip is appended to + * @param status status label and tone; a blank label renders nothing + * @param accent the issuer's brand accent, used for in-progress payments + * @param align where the chip sits in its column + * @param theme active theme + */ + public static void render(SectionBuilder host, ReceiptStatus status, + DocumentColor accent, TextAlign align, BrandTheme theme) { + Objects.requireNonNull(host, "host"); + Objects.requireNonNull(theme, "theme"); + if (status == null || !status.hasLabel()) { + return; + } + DocumentColor ink = inkFor(status.tone(), accent, theme); + DocumentColor fill = fillFor(status.tone(), theme); + host.addParagraph(p -> p + .inlineHighlight(status.label(), ReceiptStyles.pill(theme, ink), + fill, PILL_RADIUS, PILL_PADDING) + .align(align == null ? TextAlign.LEFT : align) + .margin(DocumentInsets.zero())); + } + + private static DocumentColor inkFor(ReceiptStatusTone tone, DocumentColor accent, BrandTheme theme) { + return switch (tone) { + case SETTLED -> SETTLED_INK; + case ATTENTION -> ATTENTION_INK; + case FAILED -> FAILED_INK; + case IN_PROGRESS -> accent == null ? theme.palette().ink() : accent; + }; + } + + private static DocumentColor fillFor(ReceiptStatusTone tone, BrandTheme theme) { + return switch (tone) { + case SETTLED -> SETTLED_FILL; + case ATTENTION -> ATTENTION_FILL; + case FAILED -> FAILED_FILL; + // No universal colour for "on its way": the brand accent supplies the + // ink, and the theme's own panel fill keeps the chip legible under it. + case IN_PROGRESS -> theme.palette().banner(); + }; + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/receipt/components/package-info.java b/templates/src/main/java/com/demcha/compose/document/templates/receipt/components/package-info.java new file mode 100644 index 000000000..3aa3f5840 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/receipt/components/package-info.java @@ -0,0 +1,17 @@ +/** + * Internal renderers behind the receipt widgets. + * + *

    {@link com.demcha.compose.document.templates.receipt.components.ReceiptStyles} + * composes the family's text styles from the + * {@link com.demcha.compose.document.templates.core.theme.BrandTheme}; + * {@link com.demcha.compose.document.templates.receipt.components.FieldRowRenderer} + * draws one label/value row; and + * {@link com.demcha.compose.document.templates.receipt.components.StatusPill} + * draws the rounded status chip.

    + * + *

    These take {@code (host, data, theme)} and hold no state. They are + * public because the widgets live in a sibling package, not because callers + * are expected to reach for them — compose a receipt through + * {@code receipt.presets} instead.

    + */ +package com.demcha.compose.document.templates.receipt.components; diff --git a/templates/src/main/java/com/demcha/compose/document/templates/receipt/package-info.java b/templates/src/main/java/com/demcha/compose/document/templates/receipt/package-info.java new file mode 100644 index 000000000..958f999a4 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/receipt/package-info.java @@ -0,0 +1,45 @@ +/** + * The layered receipt template family — payment confirmations, + * transfer advices, card receipts. + * + *

    Layers

    + * + *
    + * templates.data.receipt      records: amount, parties, field groups, timeline
    + *          │
    + * templates.core.theme        BrandTheme.receiptModern() — shared tokens
    + *          │
    + * receipt.components          ReceiptStyles · FieldRowRenderer · StatusPill
    + *          │
    + * receipt.widgets             ReceiptMasthead · AmountHero · PartyPair
    + *                             DetailGroup · StatusTrail · ReceiptFooter
    + *          │
    + * receipt.presets             ModernReceipt — sequences the blocks
    + * 
    + * + *

    Authoring a receipt in four steps

    + * + *
      + *
    1. Describe the payment. Build a + * {@link com.demcha.compose.document.templates.data.receipt.ReceiptDocumentSpec} + * — the amount and its caption, the status and its tone, the two + * parties, one titled group per block of details, and the steps the + * payment went through. Everything is display text; the template never + * formats a number or a date.
    2. + *
    3. Pick the theme. + * {@link com.demcha.compose.document.templates.core.theme.BrandTheme#receiptModern()} + * carries the statement look and deliberately no brand colour.
    4. + *
    5. Brand it. Load the issuer's mark with + * {@link com.demcha.compose.document.templates.core.identity.SvgGlyph#fromResource(String)} + * and pass it, with the accent, through + * {@code ModernReceipt.Options}.
    6. + *
    7. Compose and render. + * {@code ModernReceipt.create(theme, options).compose(session, spec)}, + * then {@code session.buildPdf()}.
    8. + *
    + * + *

    Blocks with no data drop out, so the same preset renders a five-row + * card receipt and a full transfer confirmation with a timeline and a + * verification code.

    + */ +package com.demcha.compose.document.templates.receipt; diff --git a/templates/src/main/java/com/demcha/compose/document/templates/receipt/presets/ModernReceipt.java b/templates/src/main/java/com/demcha/compose/document/templates/receipt/presets/ModernReceipt.java new file mode 100644 index 000000000..4e45e4a31 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/receipt/presets/ModernReceipt.java @@ -0,0 +1,384 @@ +package com.demcha.compose.document.templates.receipt.presets; + +import com.demcha.compose.document.api.DocumentSession; +import com.demcha.compose.document.dsl.PageFlowBuilder; +import com.demcha.compose.document.dsl.SectionBuilder; +import com.demcha.compose.document.output.DocumentHeaderFooter; +import com.demcha.compose.document.output.DocumentHeaderFooterZone; +import com.demcha.compose.document.snapshot.LayoutNodeSnapshot; +import com.demcha.compose.document.snapshot.LayoutSnapshot; +import com.demcha.compose.document.style.DocumentColor; +import com.demcha.compose.document.style.DocumentInsets; +import com.demcha.compose.document.templates.api.DocumentTemplate; +import com.demcha.compose.document.templates.core.identity.SvgGlyph; +import com.demcha.compose.document.templates.core.text.TextOrnaments; +import com.demcha.compose.document.templates.core.theme.BrandTheme; +import com.demcha.compose.document.templates.data.receipt.ReceiptData; +import com.demcha.compose.document.templates.data.receipt.ReceiptDocumentSpec; +import com.demcha.compose.document.templates.data.receipt.ReceiptFieldGroup; +import com.demcha.compose.document.templates.receipt.components.ReceiptStyles; +import com.demcha.compose.document.templates.receipt.widgets.AmountHero; +import com.demcha.compose.document.templates.receipt.widgets.DetailGroup; +import com.demcha.compose.document.templates.receipt.widgets.PartyPair; +import com.demcha.compose.document.templates.receipt.widgets.ReceiptFooter; +import com.demcha.compose.document.templates.receipt.widgets.ReceiptMasthead; +import com.demcha.compose.document.templates.receipt.widgets.StatusTrail; + +import java.util.Objects; + +/** + * Modern Receipt — the reference preset of the layered {@code receipt} + * family: a bank-grade transfer confirmation. + * + *

    The page reads top to bottom in the order a reader asks the questions: + * who issued this and what is it (masthead), how much and did it go through + * (hero), between whom (party pair), on what terms (detail groups), how it + * got there (status trail), and how to check it (footer QR + small + * print).

    + * + *

    What makes it a receipt rather than an invoice

    + * + *

    An invoice asks for money and is built around a table of line items. A + * receipt reports money that already moved and is built around one number + * and its provenance — so the amount is set larger than the title, the + * status carries a coloured chip, and the detail blocks are hairline + * label/value rows rather than a bordered grid. Nothing on the page is + * computed: every value arrives formatted from the issuing system.

    + * + *

    Branding

    + * + *

    The theme carries no brand colour. An issuer's mark and accent arrive + * per document through {@link Options}, so one preset and one theme serve + * every bank — swap the glyph and the accent and the same code renders a + * different institution's confirmation.

    + * + *

    Page chrome

    + * + *

    {@code compose} installs a footer carrying {@code Page n of m}. A + * receipt is a document people print, file, and photograph, and a page that + * cannot say whether it is the whole story is a support call — so the + * pagination marker is part of this preset rather than something each caller + * remembers to add. It goes through the session's chrome API, which means a + * caller who wants a different footer sets one after composing.

    + */ +public final class ModernReceipt { + + /** + * Stable template identifier. + */ + public static final String ID = "receipt-modern"; + + /** + * Human-readable display name. + */ + public static final String DISPLAY_NAME = "Modern Receipt"; + + /** + * Recommended page margin (in points). Wider than the invoice preset's: + * the layout leans on white space rather than on rules, and a tight + * margin would undo that. + */ + public static final double RECOMMENDED_MARGIN = 34.0; + + /** Height reserved for the page-number footer, in points. */ + private static final float FOOTER_HEIGHT = 24f; + + /** + * Slack left unspent when the footer is pinned, in points. The measurement + * is exact to the layout's own arithmetic; this keeps a rounding difference + * from spilling the footer onto a second page. + */ + private static final double PIN_EPSILON = 0.5; + + /** + * Below this much free space, pinning is not worth a second composition + * pass — the footer is already close enough to the bottom margin to read as + * one. + */ + private static final double MIN_PIN_HEIGHT = 8.0; + + private ModernReceipt() { + } + + /** + * Per-document branding and wording knobs. + * + *

    These are structural rather than cosmetic in the theme's sense: two + * receipts from two banks share every token in the + * {@link BrandTheme#receiptModern()} theme and differ only in their mark + * and their accent. Carrying them here keeps a per-issuer theme from + * being the way to render a second bank.

    + * + * @param logo the issuer's mark, or {@code null} to fall back to + * the issuer's name set as a spaced-caps wordmark + * @param logoWidth width of the mark in points; the height follows the + * glyph's aspect ratio. Non-positive values fall back + * to {@link #DEFAULT_LOGO_WIDTH} + * @param logoColor colour the mark is filled with, or {@code null} for + * the theme's ink + * @param accent the issuer's brand accent — the hero strip, the + * direction arrow, the reached timeline step. {@code + * null} falls back to the theme's ink, which renders + * an unbranded but complete receipt + * @param timelineTitle heading over the status trail; blank falls back to + * {@link #DEFAULT_TIMELINE_TITLE} + */ + public record Options(SvgGlyph logo, + double logoWidth, + DocumentColor logoColor, + DocumentColor accent, + String timelineTitle) { + + /** Width the issuer's mark renders at when the caller gives none. */ + public static final double DEFAULT_LOGO_WIDTH = 92.0; + + /** Heading over the status trail when the caller gives none. */ + public static final String DEFAULT_TIMELINE_TITLE = "Payment progress"; + + /** + * Normalizes the width and the heading. + */ + public Options { + logoWidth = logoWidth > 0 ? logoWidth : DEFAULT_LOGO_WIDTH; + timelineTitle = timelineTitle == null || timelineTitle.isBlank() + ? DEFAULT_TIMELINE_TITLE + : timelineTitle; + } + + /** + * Unbranded defaults — no mark, theme ink for the accent. + * + * @return default options + */ + public static Options defaults() { + return new Options(null, DEFAULT_LOGO_WIDTH, null, null, null); + } + + /** + * Defaults plus an issuer's mark and accent. + * + * @param logo the issuer's mark + * @param accent the issuer's brand accent + * @return branded options + */ + public static Options branded(SvgGlyph logo, DocumentColor accent) { + return new Options(logo, DEFAULT_LOGO_WIDTH, null, accent, null); + } + + /** + * Returns a copy carrying a different mark width. + * + * @param width width in points + * @return updated options + */ + public Options withLogoWidth(double width) { + return new Options(logo, width, logoColor, accent, timelineTitle); + } + + /** + * Returns a copy carrying a different mark colour. + * + * @param color fill colour for the mark + * @return updated options + */ + public Options withLogoColor(DocumentColor color) { + return new Options(logo, logoWidth, color, accent, timelineTitle); + } + + /** + * Returns a copy carrying a different status-trail heading. + * + * @param title heading text + * @return updated options + */ + public Options withTimelineTitle(String title) { + return new Options(logo, logoWidth, logoColor, accent, title); + } + } + + /** + * Builds the preset with the Modern Receipt theme + * ({@link BrandTheme#receiptModern()}) and unbranded options. + * + * @return ready-to-use template + */ + public static DocumentTemplate create() { + return create(BrandTheme.receiptModern(), Options.defaults()); + } + + /** + * Builds the preset with a caller-supplied theme and unbranded options. + * + * @param theme active theme + * @return ready-to-use template + */ + public static DocumentTemplate create(BrandTheme theme) { + return create(theme, Options.defaults()); + } + + /** + * Builds the preset with a caller-supplied theme and branding. + * + * @param theme active theme + * @param options issuer mark, accent, and wording + * @return ready-to-use template + */ + public static DocumentTemplate create(BrandTheme theme, Options options) { + Objects.requireNonNull(theme, "theme"); + return new Template(theme, options == null ? Options.defaults() : options); + } + + private record Template(BrandTheme theme, Options options) + implements DocumentTemplate { + + @Override + public String id() { + return ID; + } + + @Override + public String displayName() { + return DISPLAY_NAME; + } + + @Override + public void compose(DocumentSession document, ReceiptDocumentSpec spec) { + Objects.requireNonNull(document, "document"); + ReceiptData data = Objects.requireNonNull(spec, "spec").receipt(); + + DocumentColor accent = options.accent() == null + ? theme.palette().ink() + : options.accent(); + DocumentColor markColor = options.logoColor() == null + ? theme.palette().ink() + : options.logoColor(); + + // Whether this preset may pin its footer. The pin re-composes, which + // means clearing the session — safe only when the receipt is the whole + // document. A caller who composed a cover page first keeps it, and the + // footer flows after the body instead. There is also nothing to pin + // when the receipt carries no footer: the spacer would be a page-tall + // hole holding nothing down. + boolean ownsDocument = document.roots().isEmpty() && ReceiptFooter.hasContent(data); + + composeBody(document, data, accent, markColor, 0); + + if (ownsDocument) { + double pin = bottomSlack(document) - theme.spacing().pageFlowSpacing() - PIN_EPSILON; + if (pin > MIN_PIN_HEIGHT) { + document.clear(); + composeBody(document, data, accent, markColor, pin); + } + } + + document.chrome().footer(DocumentHeaderFooter.builder() + .zone(DocumentHeaderFooterZone.FOOTER) + .height(FOOTER_HEIGHT) + .rightText("Page {page} of {pages}") + .fontSize((float) theme.typography().sizeContact()) + .textColor(theme.palette().muted()) + .build()); + } + + /** + * Points of unused page left under the composed content on the last page. + * + *

    The engine has no vertical flex — a spacer grows inside a row, not + * down a page — so the only way to seat a block on the bottom margin is + * to measure what the body left and spend it. Layout runs on demand, so + * reading the snapshot here costs one pass over content that is about to + * be laid out anyway.

    + * + *

    Only nodes that both start and end on the last page are measured. A + * node's {@code placementY} is resolved on the page it starts on, so a + * container spanning a page break — the root flow of any receipt longer + * than a page always does — would report a coordinate from a page whose + * free space says nothing about the last one.

    + */ + private static double bottomSlack(DocumentSession document) { + LayoutSnapshot snapshot = document.layoutSnapshot(); + int lastPage = snapshot.totalPages() - 1; + double lowest = Double.MAX_VALUE; + for (LayoutNodeSnapshot node : snapshot.nodes()) { + if (node.startPage() == lastPage && node.endPage() == lastPage) { + lowest = Math.min(lowest, node.placementY()); + } + } + if (lowest == Double.MAX_VALUE) { + return 0.0; + } + // Placement is measured from the page bottom, so the lowest edge minus + // the bottom margin is exactly the gap the footer can fall through. + return lowest - snapshot.canvas().margin().bottom(); + } + + /** + * Sequences the blocks. Each {@code addSection} is guarded by whether + * its block has anything to draw: a widget that renders nothing still + * leaves a section behind, and an empty section still takes its turn in + * the flow's spacing — which is how a receipt with no timeline ends up + * with a timeline-sized hole in it. + */ + private void composeBody(DocumentSession document, ReceiptData data, + DocumentColor accent, DocumentColor markColor, + double footerPin) { + PageFlowBuilder flow = document.dsl().pageFlow() + .name("ReceiptModernRoot") + .spacing(theme.spacing().pageFlowSpacing()) + .addSection("ReceiptMasthead", section -> ReceiptMasthead.render( + section, data, options.logo(), options.logoWidth(), markColor, theme)) + .addSection("ReceiptHero", section -> + AmountHero.render(section, data, accent, theme)); + + if (data.hasPayer() || data.hasBeneficiary()) { + flow.addSection("ReceiptParties", section -> + PartyPair.render(section, data, accent, theme)); + } + + for (ReceiptFieldGroup group : data.detailGroups()) { + if (group.fields().isEmpty()) { + continue; + } + flow.addSection("ReceiptDetails", section -> + DetailGroup.render(section, group, theme)); + } + + if (!data.timeline().isEmpty()) { + flow.addSection("ReceiptTrail", section -> StatusTrail.render( + section, options.timelineTitle(), data.timeline(), accent, theme)); + } + if (!data.notes().isEmpty()) { + flow.addSection("ReceiptNotes", section -> renderNotes(section, data)); + } + + if (footerPin > 0) { + flow.addSpacer(spacer -> spacer.name("ReceiptFooterPin").height(footerPin)); + } + + if (ReceiptFooter.hasContent(data)) { + flow.addSection("ReceiptFooter", section -> + ReceiptFooter.render(section, data, theme)); + } + flow.build(); + } + + /** + * Notes render inline rather than through {@code DetailGroup}: they are + * prose, not label/value rows, so they take the body style and no + * hairlines. + */ + private void renderNotes(SectionBuilder host, ReceiptData data) { + host.keepTogether() + .spacing(theme.spacing().sectionBodySpacing()) + .addParagraph(p -> p + .text(TextOrnaments.upper("Notes")) + .textStyle(ReceiptStyles.groupTitle(theme)) + .margin(new DocumentInsets(0, 0, 6, 0))) + .addParagraph(p -> p + .text(String.join("\n", data.notes())) + .textStyle(ReceiptStyles.body(theme)) + .lineSpacing(theme.typography().bodyLineSpacing()) + .margin(DocumentInsets.zero())); + } + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/receipt/presets/package-info.java b/templates/src/main/java/com/demcha/compose/document/templates/receipt/presets/package-info.java new file mode 100644 index 000000000..6093420c8 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/receipt/presets/package-info.java @@ -0,0 +1,13 @@ +/** + * Receipt presets — one class per visual style. + * + *

    {@link com.demcha.compose.document.templates.receipt.presets.ModernReceipt} + * is the reference preset: a bank-grade transfer confirmation built by + * sequencing the {@code receipt.widgets} blocks over a + * {@link com.demcha.compose.document.templates.core.theme.BrandTheme}.

    + * + *

    A preset is a thin orchestrator. Everything that renders lives in the + * widgets; what a preset owns is the order of the blocks, the branding knobs + * it exposes, and the handful of decisions no widget can make for it.

    + */ +package com.demcha.compose.document.templates.receipt.presets; diff --git a/templates/src/main/java/com/demcha/compose/document/templates/receipt/widgets/AmountHero.java b/templates/src/main/java/com/demcha/compose/document/templates/receipt/widgets/AmountHero.java new file mode 100644 index 000000000..bf7562f91 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/receipt/widgets/AmountHero.java @@ -0,0 +1,99 @@ +package com.demcha.compose.document.templates.receipt.widgets; + +import com.demcha.compose.document.dsl.SectionBuilder; +import com.demcha.compose.document.node.RowVerticalAlign; +import com.demcha.compose.document.node.TextAlign; +import com.demcha.compose.document.style.DocumentColor; +import com.demcha.compose.document.style.DocumentCornerRadius; +import com.demcha.compose.document.style.DocumentInsets; +import com.demcha.compose.document.templates.core.text.TextOrnaments; +import com.demcha.compose.document.templates.core.theme.BrandTheme; +import com.demcha.compose.document.templates.data.receipt.ReceiptData; +import com.demcha.compose.document.templates.receipt.components.FieldRowRenderer; +import com.demcha.compose.document.templates.receipt.components.ReceiptStyles; +import com.demcha.compose.document.templates.receipt.components.StatusPill; + +import java.util.Objects; + +/** + * The block that answers the question the reader opened the file for: how + * much, to what, and did it go through. + * + *

    The amount is set at the theme's headline size — larger than the + * document title — because on a receipt it is the headline. The + * status chip sits at the top of the right column so the two facts that + * matter share one eye-line, and the value/operation dates fill the rest of + * that column as a compact table rather than as another block further down + * the page.

    + * + *

    A brand accent strip runs down the left edge: it is the one place the + * issuer's colour appears above the fold, which is what makes the layout + * read as branded without tinting the numbers.

    + */ +public final class AmountHero { + + /** + * How much wider the amount column is than the status column. The amount + * needs room to stay on one line at 30pt; the dates beside it are short. + */ + private static final double AMOUNT_COLUMN_WEIGHT = 1.35; + + private static final double STATUS_COLUMN_WEIGHT = 1.0; + + private AmountHero() { + } + + /** + * Renders the hero panel. + * + * @param host section the panel is appended to + * @param data receipt content + * @param accent the issuer's brand accent + * @param theme active theme + */ + public static void render(SectionBuilder host, ReceiptData data, + DocumentColor accent, BrandTheme theme) { + Objects.requireNonNull(host, "host"); + Objects.requireNonNull(data, "data"); + Objects.requireNonNull(theme, "theme"); + + DocumentColor strip = accent == null ? theme.palette().ink() : accent; + + host.addSection("ReceiptAmountHero", panel -> panel + .softPanel(theme.palette().banner(), + DocumentCornerRadius.of(theme.spacing().bannerCornerRadius()), + theme.spacing().bannerInnerPadding()) + .accentLeft(strip, 3.0) + .keepTogether() + .addRow("ReceiptAmountRow", row -> row + .weights(AMOUNT_COLUMN_WEIGHT, STATUS_COLUMN_WEIGHT) + .spacing(20) + .verticalAlign(RowVerticalAlign.TOP) + .addSection("ReceiptAmountColumn", column -> { + column.spacing(3); + column.addParagraph(p -> p + .text(TextOrnaments.upper(data.amountLabel())) + .textStyle(ReceiptStyles.eyebrow(theme)) + .margin(DocumentInsets.zero())); + column.addParagraph(p -> p + .text(data.amount()) + .textStyle(ReceiptStyles.amount(theme)) + .margin(DocumentInsets.zero())); + if (!data.amountCaption().isBlank()) { + column.addParagraph(p -> p + .text(data.amountCaption()) + .textStyle(ReceiptStyles.caption(theme)) + .lineSpacing(theme.typography().bodyLineSpacing()) + .margin(DocumentInsets.zero())); + } + }) + .addSection("ReceiptStatusColumn", column -> { + column.spacing(theme.spacing().sectionBodySpacing()); + StatusPill.render(column, data.status(), accent, TextAlign.RIGHT, theme); + // Inline form: this column is inside a row, and a row + // cannot hold another row. + FieldRowRenderer.renderInline(column, data.summaryFields(), + TextAlign.RIGHT, theme); + }))); + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/receipt/widgets/DetailGroup.java b/templates/src/main/java/com/demcha/compose/document/templates/receipt/widgets/DetailGroup.java new file mode 100644 index 000000000..44755fd75 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/receipt/widgets/DetailGroup.java @@ -0,0 +1,56 @@ +package com.demcha.compose.document.templates.receipt.widgets; + +import com.demcha.compose.document.dsl.SectionBuilder; +import com.demcha.compose.document.style.DocumentInsets; +import com.demcha.compose.document.templates.core.text.TextOrnaments; +import com.demcha.compose.document.templates.core.theme.BrandTheme; +import com.demcha.compose.document.templates.data.receipt.ReceiptFieldGroup; +import com.demcha.compose.document.templates.receipt.components.FieldRowRenderer; +import com.demcha.compose.document.templates.receipt.components.ReceiptStyles; + +import java.util.Objects; + +/** + * A titled block of field rows — {@code TRANSFER DETAILS} over a hairline + * table of labels and values. + * + *

    The block runs the full content width rather than sitting in a narrow + * left column: the values right-align to the page edge, which is what turns + * a stack of unrelated rows into a column a reader can run an eye down.

    + * + *

    Each group keeps together, so a group never splits across a page + * boundary with its heading stranded on the previous one.

    + */ +public final class DetailGroup { + + /** Gap between the spaced-caps heading and the first row, in points. */ + private static final double TITLE_GAP = 7.0; + + private DetailGroup() { + } + + /** + * Renders one group; a group with no rows renders nothing. + * + * @param host section the group is appended to + * @param group the titled rows + * @param theme active theme + */ + public static void render(SectionBuilder host, ReceiptFieldGroup group, BrandTheme theme) { + Objects.requireNonNull(host, "host"); + Objects.requireNonNull(theme, "theme"); + if (group == null || group.fields().isEmpty()) { + return; + } + host.addSection("ReceiptDetailGroup", block -> { + block.keepTogether(); + if (!group.title().isBlank()) { + block.addParagraph(p -> p + .text(TextOrnaments.upper(group.title())) + .textStyle(ReceiptStyles.groupTitle(theme)) + .margin(new DocumentInsets(0, 0, TITLE_GAP, 0))); + } + FieldRowRenderer.renderAll(block, group.fields(), theme); + }); + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/receipt/widgets/PartyPair.java b/templates/src/main/java/com/demcha/compose/document/templates/receipt/widgets/PartyPair.java new file mode 100644 index 000000000..942c41d27 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/receipt/widgets/PartyPair.java @@ -0,0 +1,119 @@ +package com.demcha.compose.document.templates.receipt.widgets; + +import com.demcha.compose.document.dsl.SectionBuilder; +import com.demcha.compose.document.node.RowVerticalAlign; +import com.demcha.compose.document.node.TextAlign; +import com.demcha.compose.document.style.DocumentColor; +import com.demcha.compose.document.style.DocumentCornerRadius; +import com.demcha.compose.document.style.DocumentInsets; +import com.demcha.compose.document.style.ShapeOutline; +import com.demcha.compose.document.templates.core.text.TextOrnaments; +import com.demcha.compose.document.templates.core.theme.BrandTheme; +import com.demcha.compose.document.templates.data.receipt.ReceiptData; +import com.demcha.compose.document.templates.data.receipt.ReceiptParty; +import com.demcha.compose.document.templates.receipt.components.FieldRowRenderer; +import com.demcha.compose.document.templates.receipt.components.ReceiptStyles; + +import java.util.Objects; + +/** + * The two sides of the transfer, side by side inside one panel, with an + * arrow between them. + * + *

    The source document stacked payer and beneficiary as two unrelated grey + * tables and left the reader to infer the direction. Two columns and one + * arrow say it without a word.

    + * + *

    Both sides share a single panel rather than sitting in a card each: a + * section is measured against its content, so two cards of unequal text come + * out unequal in width, and a receipt in which "paid from" is visibly wider + * than "paid to" reads as a rendering accident. One panel is also the truer + * statement — this is one payment, not two facts that happen to be + * adjacent.

    + */ +public final class PartyPair { + + /** Weight of the narrow middle column holding the direction arrow. */ + private static final double ARROW_COLUMN_WEIGHT = 0.12; + + /** Size of the direction arrow, in points. */ + private static final double ARROW_SIZE = 11.0; + + private PartyPair() { + } + + /** + * Renders the panel; renders nothing when neither side is present. + * + * @param host section the panel is appended to + * @param data receipt content + * @param accent the issuer's brand accent, used for the arrow + * @param theme active theme + */ + public static void render(SectionBuilder host, ReceiptData data, + DocumentColor accent, BrandTheme theme) { + Objects.requireNonNull(host, "host"); + Objects.requireNonNull(data, "data"); + Objects.requireNonNull(theme, "theme"); + + if (!data.hasPayer() && !data.hasBeneficiary()) { + return; + } + DocumentColor arrowColor = accent == null ? theme.palette().muted() : accent; + boolean bothSides = data.hasPayer() && data.hasBeneficiary(); + + host.addSection("ReceiptPartyPanel", panel -> { + panel.softPanel(theme.palette().banner(), + DocumentCornerRadius.of(theme.spacing().bannerCornerRadius()), + theme.spacing().bannerInnerPadding()) + .keepTogether(); + if (!bothSides) { + // One side only: no direction to draw, so the column runs full width. + if (data.hasPayer()) { + side(panel, data.payerLabel(), data.payer(), theme); + } else { + side(panel, data.beneficiaryLabel(), data.beneficiary(), theme); + } + return; + } + panel.addRow("ReceiptPartyRow", row -> row + .weights(1, ARROW_COLUMN_WEIGHT, 1) + .spacing(10) + .verticalAlign(RowVerticalAlign.CENTER) + .addSection("ReceiptPayer", column -> + side(column, data.payerLabel(), data.payer(), theme)) + .addSection("ReceiptDirection", column -> column + .addParagraph(p -> p + .arrow(ARROW_SIZE, ShapeOutline.Direction.RIGHT, arrowColor) + .align(TextAlign.CENTER) + .margin(DocumentInsets.zero()))) + .addSection("ReceiptBeneficiary", column -> + side(column, data.beneficiaryLabel(), data.beneficiary(), theme))); + }); + } + + private static void side(SectionBuilder host, String label, ReceiptParty party, BrandTheme theme) { + host.spacing(theme.spacing().sectionBodySpacing()) + .addParagraph(p -> p + .text(TextOrnaments.upper(label)) + .textStyle(ReceiptStyles.eyebrow(theme)) + .margin(DocumentInsets.zero())); + + if (!party.name().isBlank()) { + host.addParagraph(p -> p + .text(party.name()) + .textStyle(ReceiptStyles.partyName(theme)) + .margin(DocumentInsets.zero())); + } + if (!party.addressLines().isEmpty()) { + host.addParagraph(p -> p + .text(String.join("\n", party.addressLines())) + .textStyle(ReceiptStyles.caption(theme)) + .lineSpacing(theme.typography().bodyLineSpacing()) + .margin(DocumentInsets.zero())); + } + // Inline form: a side is a column of the pair row, and a row cannot hold + // another row. + FieldRowRenderer.renderInline(host, party.fields(), TextAlign.LEFT, theme); + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/receipt/widgets/ReceiptFooter.java b/templates/src/main/java/com/demcha/compose/document/templates/receipt/widgets/ReceiptFooter.java new file mode 100644 index 000000000..b18c1de87 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/receipt/widgets/ReceiptFooter.java @@ -0,0 +1,123 @@ +package com.demcha.compose.document.templates.receipt.widgets; + +import com.demcha.compose.document.dsl.SectionBuilder; +import com.demcha.compose.document.node.RowVerticalAlign; +import com.demcha.compose.document.style.DocumentInsets; +import com.demcha.compose.document.templates.core.theme.BrandTheme; +import com.demcha.compose.document.templates.data.receipt.ReceiptData; +import com.demcha.compose.document.templates.receipt.components.ReceiptStyles; + +import java.util.Objects; + +/** + * The foot of the receipt: a verification QR code, how to reach the issuer, + * and the small print, under a hairline. + * + *

    The QR encodes a URL where the payment can be checked against the + * issuer's own records. That is the part of a receipt that survives being + * printed: a scanned code turns a piece of paper back into a live + * lookup.

    + * + *

    Every part is optional. A receipt with no verification URL renders the + * text alone at full width rather than leaving a hole where the code would + * have been.

    + */ +public final class ReceiptFooter { + + /** Edge length of the QR code, in points. */ + private static final double QR_SIZE = 52.0; + + /** Weight of the QR column against the text beside it. */ + private static final double QR_COLUMN_WEIGHT = 0.16; + + /** Gap above the hairline that separates the footer from the body. */ + private static final double RULE_GAP = 6.0; + + private ReceiptFooter() { + } + + /** + * Reports whether a receipt carries anything the footer would draw. + * + *

    A preset asks before adding the section that hosts it: an empty + * section still takes its turn in the page flow's spacing, so a receipt + * with no footer would otherwise end on a gap the size of one.

    + * + * @param data receipt content + * @return {@code true} when a QR code, support line, or small print exists + */ + public static boolean hasContent(ReceiptData data) { + return data != null + && (!data.verificationUrl().isBlank() + || !data.verificationText().isBlank() + || !data.supportLines().isEmpty() + || !data.legalNote().isBlank()); + } + + /** + * Renders the footer block; renders nothing when the receipt carries no + * QR code, support lines, or small print. + * + * @param host section the footer is appended to + * @param data receipt content + * @param theme active theme + */ + public static void render(SectionBuilder host, ReceiptData data, BrandTheme theme) { + Objects.requireNonNull(host, "host"); + Objects.requireNonNull(data, "data"); + Objects.requireNonNull(theme, "theme"); + + if (!hasContent(data)) { + return; + } + boolean hasQr = !data.verificationUrl().isBlank(); + + host.addLine(line -> line + .fill() + .thickness(theme.spacing().accentRuleWidth()) + .color(theme.palette().rule()) + .margin(new DocumentInsets(RULE_GAP, 0, RULE_GAP, 0))); + + if (hasQr) { + host.addRow("ReceiptFooterRow", row -> row + .weights(QR_COLUMN_WEIGHT, 1) + .spacing(14) + .verticalAlign(RowVerticalAlign.TOP) + .addSection("ReceiptVerificationCode", column -> column + .addBarcode(code -> code + .qrCode() + .data(data.verificationUrl()) + .size(QR_SIZE, QR_SIZE) + .quietZone(1) + .foreground(theme.palette().ink()) + .background(theme.palette().mainFill()))) + .addSection("ReceiptFooterText", column -> footerText(column, data, theme))); + } else { + host.addSection("ReceiptFooterText", column -> footerText(column, data, theme)); + } + } + + private static void footerText(SectionBuilder host, ReceiptData data, BrandTheme theme) { + host.spacing(theme.spacing().sectionBodySpacing()); + if (!data.verificationText().isBlank()) { + host.addParagraph(p -> p + .text(data.verificationText()) + .textStyle(ReceiptStyles.caption(theme)) + .margin(DocumentInsets.zero())); + } + if (!data.supportLines().isEmpty()) { + host.addParagraph(p -> p + .text(String.join("\n", data.supportLines())) + .textStyle(ReceiptStyles.smallPrint(theme)) + .lineSpacing(theme.typography().bodyLineSpacing()) + .margin(DocumentInsets.zero())); + } + if (!data.legalNote().isBlank()) { + host.addParagraph(p -> p + .text(data.legalNote()) + .textStyle(ReceiptStyles.smallPrint(theme)) + .lineSpacing(theme.typography().bodyLineSpacing()) + .margin(DocumentInsets.zero())); + } + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/receipt/widgets/ReceiptMasthead.java b/templates/src/main/java/com/demcha/compose/document/templates/receipt/widgets/ReceiptMasthead.java new file mode 100644 index 000000000..9f37375f2 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/receipt/widgets/ReceiptMasthead.java @@ -0,0 +1,118 @@ +package com.demcha.compose.document.templates.receipt.widgets; + +import com.demcha.compose.document.dsl.SectionBuilder; +import com.demcha.compose.document.node.RowVerticalAlign; +import com.demcha.compose.document.node.TextAlign; +import com.demcha.compose.document.style.DocumentColor; +import com.demcha.compose.document.style.DocumentInsets; +import com.demcha.compose.document.templates.core.identity.SvgGlyph; +import com.demcha.compose.document.templates.core.text.TextOrnaments; +import com.demcha.compose.document.templates.core.theme.BrandTheme; +import com.demcha.compose.document.templates.data.receipt.ReceiptData; +import com.demcha.compose.document.templates.receipt.components.ReceiptStyles; + +import java.util.Objects; + +/** + * Top of the page: the issuer on the left, what the document is on the + * right, and a hairline under both. + * + *

    The issuer's mark renders as a recolourable {@link SvgGlyph} silhouette + * rather than as an image, so one logo file serves a black wordmark on white + * and a white one on a dark page without a second asset. When no logo is + * supplied the issuer's name stands in as a spaced-caps wordmark, which is + * what keeps the masthead intact for a caller who has no vector mark to + * hand.

    + */ +public final class ReceiptMasthead { + + /** Gap between the masthead row and the hairline under it, in points. */ + private static final double RULE_GAP = 10.0; + + private ReceiptMasthead() { + } + + /** + * Renders the masthead and its hairline. + * + * @param host section the masthead is appended to + * @param data receipt content + * @param logo the issuer's mark, or {@code null} to fall back to the + * issuer's name as a spaced-caps wordmark + * @param logoWidth width of the mark in points; the height follows the + * glyph's own aspect ratio + * @param logoColor colour the mark is filled with + * @param theme active theme + */ + public static void render(SectionBuilder host, ReceiptData data, SvgGlyph logo, + double logoWidth, DocumentColor logoColor, BrandTheme theme) { + Objects.requireNonNull(host, "host"); + Objects.requireNonNull(data, "data"); + Objects.requireNonNull(theme, "theme"); + + DocumentColor markColor = logoColor == null ? theme.palette().ink() : logoColor; + + host.addRow("ReceiptMastheadRow", row -> row + .weights(1, 1) + .spacing(18) + .verticalAlign(RowVerticalAlign.CENTER) + .addSection("ReceiptIssuer", column -> { + boolean hasName = !data.issuerName().isBlank(); + if (logo == null && !hasName) { + return; + } + column.addParagraph(p -> { + if (logo != null) { + // Inline, so a symbol mark and the issuer's name sit on one + // baseline as a lockup. A caller whose mark is already a + // wordmark leaves the name blank and gets the mark alone. + p.shape(logo.outline(logoWidth), markColor); + if (hasName) { + p.inlineText(" "); + } + } + if (hasName) { + // With no mark the name IS the wordmark, so it takes + // the spaced caps; beside a mark it is set as written. + boolean wordmark = logo == null; + p.inlineText( + wordmark ? TextOrnaments.upper(data.issuerName()) + : data.issuerName(), + wordmark + ? ReceiptStyles.title(theme) + .withLetterSpacing(TextOrnaments.SPACED_CAPS) + : ReceiptStyles.title(theme)); + } + p.margin(DocumentInsets.zero()); + }); + }) + .addSection("ReceiptDocumentTitle", column -> { + column.spacing(2); + column.addParagraph(p -> p + .text(data.documentTitle()) + .textStyle(ReceiptStyles.title(theme)) + .align(TextAlign.RIGHT) + .margin(DocumentInsets.zero())); + if (!data.generatedOn().isBlank()) { + column.addParagraph(p -> p + .text("Generated on " + data.generatedOn()) + .textStyle(ReceiptStyles.caption(theme)) + .align(TextAlign.RIGHT) + .margin(DocumentInsets.zero())); + } + if (!data.reference().isBlank()) { + column.addParagraph(p -> p + .text("Reference " + data.reference()) + .textStyle(ReceiptStyles.caption(theme)) + .align(TextAlign.RIGHT) + .margin(DocumentInsets.zero())); + } + })); + + host.addLine(line -> line + .fill() + .thickness(theme.spacing().accentRuleWidth()) + .color(theme.palette().rule()) + .margin(new DocumentInsets(RULE_GAP, 0, 0, 0))); + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/receipt/widgets/StatusTrail.java b/templates/src/main/java/com/demcha/compose/document/templates/receipt/widgets/StatusTrail.java new file mode 100644 index 000000000..f8432ae04 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/receipt/widgets/StatusTrail.java @@ -0,0 +1,119 @@ +package com.demcha.compose.document.templates.receipt.widgets; + +import com.demcha.compose.document.dsl.SectionBuilder; +import com.demcha.compose.document.dsl.TimelineMarker; +import com.demcha.compose.document.style.DocumentColor; +import com.demcha.compose.document.style.DocumentInsets; +import com.demcha.compose.document.templates.core.text.TextOrnaments; +import com.demcha.compose.document.templates.core.theme.BrandTheme; +import com.demcha.compose.document.templates.data.receipt.ReceiptEvent; +import com.demcha.compose.document.templates.receipt.components.ReceiptStyles; + +import java.util.List; +import java.util.Objects; + +/** + * The payment's steps as a connected timeline — instructed, cleared, + * settled — with the last step marked as the one it reached. + * + *

    This is the block a status word cannot replace. "Completed" says the + * payment finished; the trail says when each stage happened, which is what a + * reader chasing a late payment or a support agent reading the receipt back + * actually needs.

    + * + *

    Steps before the last render as hollow circles in the rule colour and + * the last as a filled accent dot, so the eye lands on where the payment + * got to without reading a single timestamp.

    + */ +public final class StatusTrail { + + /** Diameter of a timeline marker, in points. */ + private static final double MARKER_SIZE = 7.0; + + /** Space between the connector rail and the marker, in points. */ + private static final double GUTTER = 12.0; + + /** + * Marker column weight. Small, because these markers are 7pt dots: the + * builder's default is sized for numbered discs and would indent every + * step by a tenth of the page. + */ + private static final double MARKER_COLUMN_WEIGHT = 0.022; + + /** Gap between a marker and its step name, in points. */ + private static final double MARKER_GAP = 7.0; + + /** Separator between a step's timestamp and its explanation. */ + private static final String META_SEPARATOR = " · "; + + private StatusTrail() { + } + + private static String joinMeta(ReceiptEvent event) { + if (event.timestamp().isBlank()) { + return event.detail(); + } + if (event.detail().isBlank()) { + return event.timestamp(); + } + return event.timestamp() + META_SEPARATOR + event.detail(); + } + + /** + * Renders the trail under a spaced-caps heading; an empty step list + * renders nothing. + * + * @param host section the trail is appended to + * @param title heading over the trail + * @param events the payment's steps, oldest first + * @param accent the issuer's brand accent, used for the reached step + * @param theme active theme + */ + public static void render(SectionBuilder host, String title, List events, + DocumentColor accent, BrandTheme theme) { + Objects.requireNonNull(host, "host"); + Objects.requireNonNull(theme, "theme"); + if (events == null || events.isEmpty()) { + return; + } + DocumentColor reached = accent == null ? theme.palette().ink() : accent; + + host.addSection("ReceiptStatusTrail", block -> { + block.keepTogether(); + if (title != null && !title.isBlank()) { + block.addParagraph(p -> p + .text(TextOrnaments.upper(title)) + .textStyle(ReceiptStyles.groupTitle(theme)) + .margin(new DocumentInsets(0, 0, 8, 0))); + } + block.addTimeline(timeline -> { + timeline.connector(theme.palette().rule(), theme.spacing().accentRuleWidth()) + .gutter(GUTTER) + .markerColumnWeight(MARKER_COLUMN_WEIGHT) + .markerGap(MARKER_GAP) + .spacing(theme.spacing().entrySeparation()) + .titleStyle(ReceiptStyles.valueStrong(theme)) + .metaStyle(ReceiptStyles.caption(theme)) + .keepEntriesTogether(); + for (int i = 0; i < events.size(); i++) { + ReceiptEvent event = events.get(i); + boolean last = i == events.size() - 1; + TimelineMarker marker = last + ? TimelineMarker.dot(MARKER_SIZE, reached) + : TimelineMarker.dot(MARKER_SIZE, theme.palette().rule()); + // Timestamp and detail go on one meta line rather than into the + // builder's body slot: the body renders as a sibling of the + // marker row, so it would start at the rail instead of under the + // step's own title. + String meta = joinMeta(event); + timeline.entry(marker, entry -> { + entry.title(event.label()); + if (!meta.isBlank()) { + entry.meta(meta); + } + }); + } + }); + }); + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/receipt/widgets/package-info.java b/templates/src/main/java/com/demcha/compose/document/templates/receipt/widgets/package-info.java new file mode 100644 index 000000000..be3957907 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/receipt/widgets/package-info.java @@ -0,0 +1,25 @@ +/** + * The visual blocks a receipt preset arranges. + * + *

    Each widget owns one block of the page and takes + * {@code (host, data, …, theme)}:

    + * + *
      + *
    • {@link com.demcha.compose.document.templates.receipt.widgets.ReceiptMasthead} + * — issuer mark, document title, hairline.
    • + *
    • {@link com.demcha.compose.document.templates.receipt.widgets.AmountHero} + * — the amount, the status chip, and the value/operation dates.
    • + *
    • {@link com.demcha.compose.document.templates.receipt.widgets.PartyPair} + * — payer and beneficiary cards with the direction between them.
    • + *
    • {@link com.demcha.compose.document.templates.receipt.widgets.DetailGroup} + * — one titled block of label/value rows.
    • + *
    • {@link com.demcha.compose.document.templates.receipt.widgets.StatusTrail} + * — the payment's steps as a connected timeline.
    • + *
    • {@link com.demcha.compose.document.templates.receipt.widgets.ReceiptFooter} + * — verification QR code, support lines, small print.
    • + *
    + * + *

    Every widget skips itself when its data is absent, so a preset can + * sequence all six without guarding each one.

    + */ +package com.demcha.compose.document.templates.receipt.widgets; diff --git a/templates/src/main/java/com/demcha/compose/document/templates/rota/presets/CobaltChips.java b/templates/src/main/java/com/demcha/compose/document/templates/rota/presets/CobaltChips.java new file mode 100644 index 000000000..cbef4b497 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/rota/presets/CobaltChips.java @@ -0,0 +1,99 @@ +package com.demcha.compose.document.templates.rota.presets; + +import com.demcha.compose.document.dsl.ParagraphBuilder; +import com.demcha.compose.document.dsl.ShapeContainerBuilder; +import com.demcha.compose.document.node.DocumentNode; +import com.demcha.compose.document.node.TextAlign; +import com.demcha.compose.document.style.DocumentColor; +import com.demcha.compose.document.style.DocumentInsets; +import com.demcha.compose.document.style.DocumentStroke; +import com.demcha.compose.document.style.DocumentTextStyle; +import com.demcha.compose.document.templates.data.rota.ShiftEmphasis; +import com.demcha.compose.document.templates.data.rota.ShiftStatus; + +import static com.demcha.compose.document.templates.rota.presets.CobaltStyles.CHIP_CORNER_RADIUS; +import static com.demcha.compose.document.templates.rota.presets.CobaltStyles.CHIP_ON_LIGHT; +import static com.demcha.compose.document.templates.rota.presets.CobaltStyles.CHIP_OUTLINE_WIDTH; +import static com.demcha.compose.document.templates.rota.presets.CobaltStyles.CHIP_SINGLE_ON_LIGHT; +import static com.demcha.compose.document.templates.rota.presets.CobaltStyles.PAPER; +import static com.demcha.compose.document.templates.rota.presets.CobaltStyles.colorFor; +import static com.demcha.compose.document.templates.rota.presets.CobaltStyles.labelOnFillFor; + +/** + * The sheet's one repeated shape: the chip a cell of the rota is drawn as. + * + *

    Three forms, and the third is the reason this is a shape at all. + * {@link ShiftEmphasis#STRONG} fills with the status's colour and labels it in + * that status's own contrast colour; {@link ShiftEmphasis#SOFT} keeps the paper + * and hairlines it in the same colour; {@link ShiftEmphasis#PLAIN} draws + * neither and is the same box carrying only text. A box rather than a bare + * paragraph, so an unmarked entry occupies exactly the height a chip does and + * the two seat alike in a row that mixes them.

    + */ +final class CobaltChips { + + private CobaltChips() { + } + + /** + * One chip. + * + * @param text what the chip prints + * @param status what the entry means, which decides the colour + * @param emphasis how loudly it is drawn + * @param width how wide its column leaves it + * @param height how tall to draw it — a lone entry is taller than one of a + * stacked pair + * @param lone whether it is the only entry in its cell, which decides the + * face: a pair keeps the smaller one, because two chips and a + * gap have to fit the block a single chip fills + * @param rowFill the row's own background, which a plain chip takes + * @return the chip + */ + static DocumentNode chip(String text, ShiftStatus status, ShiftEmphasis emphasis, + double width, double height, boolean lone, + DocumentColor rowFill) { + boolean filled = emphasis == ShiftEmphasis.STRONG; + boolean plain = emphasis == ShiftEmphasis.PLAIN; + DocumentColor colour = colorFor(status); + DocumentTextStyle label = filled + ? labelOnFillFor(status, lone) + : (lone ? CHIP_SINGLE_ON_LIGHT : CHIP_ON_LIGHT); + + // A plain entry is the absence of a chip, so it takes the row's own + // background; a soft one keeps the paper, because a white box hairlined + // in its status colour is what it is. + ShapeContainerBuilder chip = new ShapeContainerBuilder() + .name("Chip") + .roundedRect(width, height, CHIP_CORNER_RADIUS) + .fillColor(filled ? colour : (plain ? rowFill : PAPER)); + if (!filled && !plain) { + chip.stroke(DocumentStroke.of(colour, CHIP_OUTLINE_WIDTH)); + } + return chip.center(label(text, label, width)).build(); + } + + /** + * A chip's label, inset inside the box. + * + *

    The inset is not decoration: a shape container clamps rather than + * centres a child wider than itself, so a label kept narrower than its chip + * is what makes a long one centre instead of sitting against the left + * edge.

    + * + * @param text what the label prints + * @param style the face it is set in + * @param chipWidth the chip it has to fit inside + * @return the label + */ + static DocumentNode label(String text, DocumentTextStyle style, double chipWidth) { + return new ParagraphBuilder() + .name("ChipLabel") + .text(text) + .textStyle(style) + .align(TextAlign.CENTER) + .lineSpacing(0) + .margin(new DocumentInsets(0, chipWidth * 0.04, 0, chipWidth * 0.04)) + .build(); + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/rota/presets/CobaltHeader.java b/templates/src/main/java/com/demcha/compose/document/templates/rota/presets/CobaltHeader.java new file mode 100644 index 000000000..f4cef47e7 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/rota/presets/CobaltHeader.java @@ -0,0 +1,210 @@ +package com.demcha.compose.document.templates.rota.presets; + +import com.demcha.compose.document.dsl.ParagraphBuilder; +import com.demcha.compose.document.dsl.SectionBuilder; +import com.demcha.compose.document.dsl.TableBuilder; +import com.demcha.compose.document.node.DocumentNode; +import com.demcha.compose.document.node.TextAlign; +import com.demcha.compose.document.style.DocumentColor; +import com.demcha.compose.document.style.DocumentInsets; +import com.demcha.compose.document.style.DocumentStroke; +import com.demcha.compose.document.style.DocumentTextStyle; +import com.demcha.compose.document.table.DocumentTableCell; +import com.demcha.compose.document.table.DocumentTableStyle; +import com.demcha.compose.document.table.DocumentTableTextAnchor; +import com.demcha.compose.document.templates.data.rota.RotaDay; +import com.demcha.compose.document.templates.data.rota.RotaVenue; +import com.demcha.compose.document.templates.data.rota.StructuredRotaData; + +import java.util.ArrayList; +import java.util.List; + +import static com.demcha.compose.document.templates.rota.presets.CobaltStyles.BODY_TOP_RULE_TEXT; +import static com.demcha.compose.document.templates.rota.presets.CobaltStyles.DAY_NAME; +import static com.demcha.compose.document.templates.rota.presets.CobaltStyles.DAY_NAME_PAD_BOTTOM; +import static com.demcha.compose.document.templates.rota.presets.CobaltStyles.DAY_NAME_PAD_TOP; +import static com.demcha.compose.document.templates.rota.presets.CobaltStyles.DAY_NOTE; +import static com.demcha.compose.document.templates.rota.presets.CobaltStyles.DAY_NOTE_PAD_BOTTOM; +import static com.demcha.compose.document.templates.rota.presets.CobaltStyles.DAY_NOTE_PAD_TOP; +import static com.demcha.compose.document.templates.rota.presets.CobaltStyles.DAY_ORDINAL_SUFFIX; +import static com.demcha.compose.document.templates.rota.presets.CobaltStyles.GRID_LINE; +import static com.demcha.compose.document.templates.rota.presets.CobaltStyles.GRID_RULE; +import static com.demcha.compose.document.templates.rota.presets.CobaltStyles.HEADER_ROW_COUNT; +import static com.demcha.compose.document.templates.rota.presets.CobaltStyles.LOCKUP_LINE_SPACING; +import static com.demcha.compose.document.templates.rota.presets.CobaltStyles.MASTHEAD_RULE_TEXT; +import static com.demcha.compose.document.templates.rota.presets.CobaltStyles.NAVY; +import static com.demcha.compose.document.templates.rota.presets.CobaltStyles.NAVY_RULE; +import static com.demcha.compose.document.templates.rota.presets.CobaltStyles.PAPER; +import static com.demcha.compose.document.templates.rota.presets.CobaltStyles.WORDMARK; +import static com.demcha.compose.document.templates.rota.presets.CobaltStyles.WORDMARK_SUB; +import static com.demcha.compose.document.templates.rota.presets.CobaltStyles.labelHeaderStyle; + +/** + * The four rows that open the sheet: a navy rule, the lockup beside the day + * names, whatever is happening on each day, and the rule that closes the header + * and opens the body. + * + *

    Four real table rows and not one row with nested tables in it: a nested + * table in a header cell draws its own rules at its own heights, and the day + * columns, holding notes of one, two and three lines, would then close with a + * rule drawn at three different heights.

    + */ +final class CobaltHeader { + + private CobaltHeader() { + } + + /** + * The header, declared as repeating. + * + *

    The count is not optional. {@code repeatHeader()} with no argument + * repeats one row, which on a four-row header leaves a continuation page + * carrying unlabelled day columns under a stray rule.

    + * + * @param table the sheet's one table + * @param rota the document + * @param grid the sheet's geometry + */ + static void render(TableBuilder table, StructuredRotaData rota, CobaltStyles.Grid grid) { + table.headerCells(ruleCells(NAVY, NAVY_RULE, MASTHEAD_RULE_TEXT, grid)); + table.headerCells(dayNameCells(rota, grid)); + table.headerCells(dayNoteCells(rota, grid)); + table.headerCells(ruleCells(GRID_LINE, GRID_RULE, BODY_TOP_RULE_TEXT, grid)); + table.repeatHeader(HEADER_ROW_COUNT); + } + + /** + * A row that is only a rule. + * + *

    An empty cell is as tall as its own font, so the rule's thickness is + * carried by the text style rather than by a height nobody can set. Every + * column is filled, including the label column: the rule runs the width of + * the sheet.

    + */ + private static List ruleCells(DocumentColor fill, DocumentStroke stroke, + DocumentTextStyle thickness, + CobaltStyles.Grid grid) { + DocumentTableStyle rule = DocumentTableStyle.builder() + .padding(DocumentInsets.zero()) + .fillColor(fill) + .stroke(stroke) + .textStyle(thickness) + .textAnchor(DocumentTableTextAnchor.CENTER) + .lineSpacing(0) + .build(); + List cells = new ArrayList<>(grid.columnCount()); + for (int column = 0; column < grid.columnCount(); column++) { + cells.add(DocumentTableCell.text("").withStyle(rule)); + } + return cells; + } + + /** The lockup in the label column, then one heading per day. */ + private static List dayNameCells(StructuredRotaData rota, + CobaltStyles.Grid grid) { + DocumentTableStyle style = DocumentTableStyle.builder() + .padding(new DocumentInsets( + DAY_NAME_PAD_TOP, grid.cellPadX(), DAY_NAME_PAD_BOTTOM, grid.cellPadX())) + .fillColor(PAPER) + .stroke(GRID_RULE) + .textStyle(DAY_NAME) + .textAnchor(DocumentTableTextAnchor.CENTER) + .lineSpacing(0) + .build(); + List cells = new ArrayList<>(grid.columnCount()); + cells.add(DocumentTableCell.node(lockupNode(rota.venue())).withStyle(labelHeaderStyle())); + for (RotaDay day : rota.days()) { + cells.add(DocumentTableCell.node(dayNameNode(day)).withStyle(style)); + } + return cells; + } + + /** + * The venue's mark, which sits in the label column's header rather than in a + * masthead of its own — the sheet is one table, and a masthead above it + * would be a second structure to keep aligned with the first. + */ + private static DocumentNode lockupNode(RotaVenue venue) { + SectionBuilder lockup = new SectionBuilder(); + lockup.name("BrandLockup").spacing(0); + boolean named = !venue.wordmark().isBlank(); + if (named) { + lockup.addParagraph(p -> p + .name("Wordmark") + .text(venue.wordmark()) + .textStyle(WORDMARK) + .align(TextAlign.LEFT) + .lineSpacing(0)); + } + if (!venue.wordmarkSub().isBlank()) { + // The negative margin closes up the gap under the wordmark's own + // line box. With no wordmark there is no gap to close, and applying + // it anyway would pull the subtitle up through the cell's padding + // and into the rule above it. + double closeUp = named ? LOCKUP_LINE_SPACING : 0; + lockup.addParagraph(p -> p + .name("WordmarkSub") + .text(venue.wordmarkSub()) + .textStyle(WORDMARK_SUB) + .align(TextAlign.LEFT) + .lineSpacing(0) + .margin(new DocumentInsets(closeUp, 0, 0, 0))); + } + return lockup.build(); + } + + /** + * One day's heading: the weekday and the date, with the ordinal's tail set + * smaller. + * + *

    Two runs of one paragraph rather than two paragraphs, because the tail + * belongs on the same line as what it follows.

    + */ + private static DocumentNode dayNameNode(RotaDay day) { + ParagraphBuilder paragraph = new ParagraphBuilder() + .name("DayName") + .textStyle(DAY_NAME) + .align(TextAlign.CENTER) + .lineSpacing(0); + paragraph.rich(rich -> rich + .style(day.name() + " " + day.ordinal(), DAY_NAME) + .style(day.ordinalSuffix(), DAY_ORDINAL_SUFFIX)); + return paragraph.build(); + } + + /** What else is happening that day, under its heading. */ + private static List dayNoteCells(StructuredRotaData rota, + CobaltStyles.Grid grid) { + DocumentTableStyle style = DocumentTableStyle.builder() + .padding(new DocumentInsets( + DAY_NOTE_PAD_TOP, grid.cellPadX(), DAY_NOTE_PAD_BOTTOM, grid.cellPadX())) + .fillColor(PAPER) + .stroke(GRID_RULE) + .textStyle(DAY_NOTE) + .textAnchor(DocumentTableTextAnchor.TOP_LEFT) + .lineSpacing(1.5) + .build(); + List cells = new ArrayList<>(grid.columnCount()); + cells.add(DocumentTableCell.text("").withStyle(labelHeaderStyle())); + for (RotaDay day : rota.days()) { + cells.add(DocumentTableCell.node(dayNoteNode(day)).withStyle(style)); + } + return cells; + } + + /** + * A note as a paragraph node rather than as cell text: a text cell asks its + * fixed column for its natural width, and a note long enough to need two + * lines then overflows instead of wrapping. + */ + private static DocumentNode dayNoteNode(RotaDay day) { + return new ParagraphBuilder() + .name("DayNote") + .text(day.note()) + .textStyle(DAY_NOTE) + .align(TextAlign.CENTER) + .lineSpacing(1.5) + .margin(DocumentInsets.zero()) + .build(); + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/rota/presets/CobaltIcons.java b/templates/src/main/java/com/demcha/compose/document/templates/rota/presets/CobaltIcons.java new file mode 100644 index 000000000..ea07f5573 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/rota/presets/CobaltIcons.java @@ -0,0 +1,78 @@ +package com.demcha.compose.document.templates.rota.presets; + +import com.demcha.compose.document.svg.SvgIcon; + +import java.io.IOException; +import java.io.InputStream; +import java.io.UncheckedIOException; +import java.nio.charset.StandardCharsets; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * The marks the Cobalt rota packages, one per band of staff. + * + *

    A band may name one or name none: the band is the navy strip and its + * label, and the mark before it is the document's. A token this preset does not + * package is a data error reported by name, because an unnamed mark and a + * misspelt one are different mistakes and only one of them is silent.

    + */ +final class CobaltIcons { + + /** The marks a document may name on a band. */ + static final List TOKENS = List.of("management", "bartenders", "barbacks"); + + private static final String ICON_ROOT = "/templates/rota/cobalt/icons/"; + + /** + * Keyed on the token and not on a file path: the marks are classpath + * resources of this artifact, so the cache is as long-lived as the class + * and the same across every document. Concurrent because two render threads + * must not meet inside a plain map. + */ + private static final Map CACHE = new ConcurrentHashMap<>(); + + private CobaltIcons() { + } + + /** + * The packaged mark a token names. + * + * @param token one of this preset's tokens + * @return the parsed icon + * @throws IllegalArgumentException when the token names no packaged mark, + * listing the ones a document may choose — an icon token is data, + * so a wrong one is a data error and not a missing resource + */ + static SvgIcon icon(String token) { + if (!TOKENS.contains(token)) { + throw new IllegalArgumentException( + "Unknown cobalt rota icon token '" + token + + "'. This preset packages " + TOKENS + "."); + } + return CACHE.computeIfAbsent(token, CobaltIcons::read); + } + + /** + * Whether the document named a mark at all, for a band that may carry none. + * + * @param token the token a document states + * @return {@code true} when the document named one + */ + static boolean has(String token) { + return token != null && !token.isBlank(); + } + + private static SvgIcon read(String token) { + String resourcePath = ICON_ROOT + token + ".svg"; + try (InputStream input = CobaltIcons.class.getResourceAsStream(resourcePath)) { + if (input == null) { + throw new IllegalStateException("Missing cobalt rota icon: " + resourcePath); + } + return SvgIcon.parse(new String(input.readAllBytes(), StandardCharsets.UTF_8)); + } catch (IOException e) { + throw new UncheckedIOException("Failed to read cobalt rota icon: " + resourcePath, e); + } + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/rota/presets/CobaltRota.java b/templates/src/main/java/com/demcha/compose/document/templates/rota/presets/CobaltRota.java new file mode 100644 index 000000000..82875a63a --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/rota/presets/CobaltRota.java @@ -0,0 +1,203 @@ +package com.demcha.compose.document.templates.rota.presets; + +import com.demcha.compose.document.api.DocumentSession; +import com.demcha.compose.document.output.DocumentHeaderFooter; +import com.demcha.compose.document.output.DocumentHeaderFooterZone; +import com.demcha.compose.document.style.DocumentInsets; +import com.demcha.compose.document.table.DocumentTableColumn; +import com.demcha.compose.document.templates.api.DocumentTemplate; +import com.demcha.compose.document.templates.data.rota.RotaGroup; +import com.demcha.compose.document.templates.data.rota.RotaStaff; +import com.demcha.compose.document.templates.data.rota.StructuredRotaData; +import com.demcha.compose.document.templates.data.rota.StructuredRotaDocumentSpec; + +import java.util.Objects; + +import static com.demcha.compose.document.templates.rota.presets.CobaltStyles.FOOTER_RULE_WIDTH; +import static com.demcha.compose.document.templates.rota.presets.CobaltStyles.FOOTER_SIZE; +import static com.demcha.compose.document.templates.rota.presets.CobaltStyles.LABEL_COLUMN_WIDTH; +import static com.demcha.compose.document.templates.rota.presets.CobaltStyles.MARGIN_BOTTOM; +import static com.demcha.compose.document.templates.rota.presets.CobaltStyles.NAVY; +import static com.demcha.compose.document.templates.rota.presets.CobaltStyles.PAGE; +import static com.demcha.compose.document.templates.rota.presets.CobaltStyles.PAGE_MARGIN; +import static com.demcha.compose.document.templates.rota.presets.CobaltStyles.PAPER; + +/** + * Cobalt Rota — a staff rota on one landscape sheet: a navy-ruled grid of who + * works when, read across a row. + * + *

    The label column carries the venue's mark over the staff names; the day + * columns carry a heading, whatever else is happening that day, a strip of + * swatches saying what the colours mean, the covers each service is expecting, + * and then the people — in bands, each band opening with a navy strip and its + * own mark. A day's cell holds nothing, one entry, or a stack of them — two is + * what a split shift comes to and what the row's height is measured for; more + * than two fits, but stands taller than the row around it.

    + * + *

    One table, not a stack of them

    + * + *

    Everything from the masthead rule to the last person is rows of a single + * table. That is what keeps the columns aligned down the sheet: a masthead above + * the table, or a legend beside it, would be a second structure to keep in step + * with the first. It is also why the four header rows are declared repeating — a + * rota longer than a page carries its day headings onto the next one rather than + * leaving a page of unlabelled columns.

    + * + *

    The grid is the document's own shape

    + * + *

    The sheet has as many day columns as the document has days, and every + * person's entries are read by position against that list. Seven is the common + * case and nothing here requires it. A legend documenting more statuses than + * there are days is closed off at the sheet's width, and one documenting fewer + * is padded out, so the table stays square either way.

    + * + *

    The wordmark is sized to its column, not to itself

    + * + *

    The lockup sits in the label column and is set at a fixed fraction of it, + * which is what keeps it on one line at the size the design draws it. It does + * not shrink to fit: a venue whose name runs long wraps instead, so a long name + * belongs in {@code wordmarkSub} with a short form above it.

    + * + *

    Fonts

    + * + *

    The sheet is set in Carlito, with the wordmark in Spectral. The templates + * artifact carries no fonts: both arrive with {@code graph-compose-fonts} on + * the classpath, or a caller registers families of those names on the session + * itself. With neither, the engine substitutes and every size is solved for + * type that is not there.

    + * + *

    Usage:

    + *
    {@code
    + * DocumentTemplate template = CobaltRota.create();
    + * template.compose(session, spec);
    + * }
    + * + * @since 2.4.0 + */ +public final class CobaltRota { + + /** + * Stable template identifier. + */ + public static final String ID = "rota-cobalt"; + + /** + * Human-readable display name. + */ + public static final String DISPLAY_NAME = "Cobalt Rota"; + + /** + * Recommended session margin (in points). The preset sets its own page + * geometry — landscape A4 and the design's own margins — inside + * {@code compose}, so callers leave the session margin at zero and let the + * preset own the page frame. + */ + public static final double RECOMMENDED_MARGIN = 0.0; + + private CobaltRota() { + } + + /** + * Builds the preset. + * + * @return ready-to-use template + */ + public static DocumentTemplate create() { + return new Template(); + } + + private record Template() implements DocumentTemplate { + + @Override + public String id() { + return ID; + } + + @Override + public String displayName() { + return DISPLAY_NAME; + } + + @Override + public void compose(DocumentSession document, StructuredRotaDocumentSpec spec) { + Objects.requireNonNull(document, "document"); + StructuredRotaData rota = Objects.requireNonNull(spec, "spec").rota(); + + renderChrome(document, rota); + + document.pageFlow(page -> { + // spacing(0): the sheet is one table and the flow holds nothing + // else, so a flow spacing would only push it off its margin. + page.name("CobaltRota").spacing(0); + CobaltStyles.Grid grid = CobaltStyles.Grid.of(rota.days().size()); + page.addTable(table -> { + table.name("Schedule") + .columns(columns(grid)) + .padding(DocumentInsets.zero()) + .margin(DocumentInsets.zero()); + CobaltHeader.render(table, rota, grid); + CobaltRows.renderLegend(table, rota.legend(), grid); + CobaltRows.renderCovers(table, rota, grid); + for (RotaGroup group : rota.groups()) { + CobaltRows.renderBand(table, group, grid); + int row = 0; + for (RotaStaff staff : group.staff()) { + CobaltRows.renderStaff(table, staff, grid, row++ % 2 == 1); + } + } + }); + }); + } + + /** + * The page and the line that closes it. + * + *

    The footer is chrome rather than a last row of the table, because + * it repeats on a continuation page and the table's rows do not. Its + * rule is deliberately lighter than the grid's: the zone fixes the gap + * between rule and text, so a heavier one crowds the text against + * it.

    + */ + private static void renderChrome(DocumentSession document, StructuredRotaData rota) { + document.pageSize(PAGE) + .margin(PAGE_MARGIN) + .pageBackground(PAPER); + + String name = rota.venue().footerName(); + String range = rota.week().rangeLabel(); + String signature = name.isBlank() || range.isBlank() + ? name + range + : name + " | " + range; + + document.footer(DocumentHeaderFooter.builder() + .zone(DocumentHeaderFooterZone.FOOTER) + // The zone's depth is what places the footer: the separator + // draws at its top and the text just beneath. + .height((float) (MARGIN_BOTTOM * 0.90)) + .leftText(rota.footer().note()) + .rightText(signature) + .fontSize((float) FOOTER_SIZE) + .textColor(NAVY) + .showSeparator(true) + .separatorColor(NAVY) + .separatorThickness((float) FOOTER_RULE_WIDTH) + .build()); + } + + /** + * The label column and one column per day, all fixed. + * + *

    Fixed and not weighted: a weight is a share of what is left, and a + * table's own weights would have to be restated on every one of the + * rows, where a width is stated once for the sheet.

    + */ + private static DocumentTableColumn[] columns(CobaltStyles.Grid grid) { + DocumentTableColumn[] columns = new DocumentTableColumn[grid.columnCount()]; + columns[0] = DocumentTableColumn.fixed(LABEL_COLUMN_WIDTH); + for (int day = 1; day < grid.columnCount(); day++) { + columns[day] = DocumentTableColumn.fixed(grid.dayWidth()); + } + return columns; + } + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/rota/presets/CobaltRows.java b/templates/src/main/java/com/demcha/compose/document/templates/rota/presets/CobaltRows.java new file mode 100644 index 000000000..9460fcb07 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/rota/presets/CobaltRows.java @@ -0,0 +1,300 @@ +package com.demcha.compose.document.templates.rota.presets; + +import com.demcha.compose.document.dsl.ParagraphBuilder; +import com.demcha.compose.document.dsl.RowBuilder; +import com.demcha.compose.document.dsl.ShapeContainerBuilder; +import com.demcha.compose.document.dsl.TableBuilder; +import com.demcha.compose.document.node.DocumentNode; +import com.demcha.compose.document.node.RowVerticalAlign; +import com.demcha.compose.document.node.TextAlign; +import com.demcha.compose.document.style.DocumentColor; +import com.demcha.compose.document.style.DocumentInsets; +import com.demcha.compose.document.style.DocumentStroke; +import com.demcha.compose.document.table.DocumentTableCell; +import com.demcha.compose.document.table.DocumentTableColumn; +import com.demcha.compose.document.table.DocumentTableStyle; +import com.demcha.compose.document.table.DocumentTableTextAnchor; +import com.demcha.compose.document.templates.data.rota.RotaDay; +import com.demcha.compose.document.templates.data.rota.RotaGroup; +import com.demcha.compose.document.templates.data.rota.RotaLegend; +import com.demcha.compose.document.templates.data.rota.RotaShift; +import com.demcha.compose.document.templates.data.rota.RotaStaff; +import com.demcha.compose.document.templates.data.rota.ShiftEmphasis; +import com.demcha.compose.document.templates.data.rota.ShiftStatus; +import com.demcha.compose.document.templates.data.rota.StructuredRotaData; + +import java.util.ArrayList; +import java.util.List; + +import static com.demcha.compose.document.templates.rota.presets.CobaltStyles.BAND_ICON_SIZE; +import static com.demcha.compose.document.templates.rota.presets.CobaltStyles.BAND_PAD_X; +import static com.demcha.compose.document.templates.rota.presets.CobaltStyles.BAND_PAD_Y; +import static com.demcha.compose.document.templates.rota.presets.CobaltStyles.CELL_PAD_Y; +import static com.demcha.compose.document.templates.rota.presets.CobaltStyles.CHIP_CORNER_RADIUS; +import static com.demcha.compose.document.templates.rota.presets.CobaltStyles.CHIP_HEIGHT; +import static com.demcha.compose.document.templates.rota.presets.CobaltStyles.CHIP_STACK_GAP; +import static com.demcha.compose.document.templates.rota.presets.CobaltStyles.CHIP_STACK_HEIGHT; +import static com.demcha.compose.document.templates.rota.presets.CobaltStyles.CONTENT_WIDTH; +import static com.demcha.compose.document.templates.rota.presets.CobaltStyles.COVERS_LABEL; +import static com.demcha.compose.document.templates.rota.presets.CobaltStyles.COVERS_PAD_Y; +import static com.demcha.compose.document.templates.rota.presets.CobaltStyles.COVERS_TAG; +import static com.demcha.compose.document.templates.rota.presets.CobaltStyles.COVERS_VALUE; +import static com.demcha.compose.document.templates.rota.presets.CobaltStyles.GRID_RULE; +import static com.demcha.compose.document.templates.rota.presets.CobaltStyles.GROUP_LABEL; +import static com.demcha.compose.document.templates.rota.presets.CobaltStyles.LABEL_PAD_X; +import static com.demcha.compose.document.templates.rota.presets.CobaltStyles.LEGEND_CHIP_HEIGHT; +import static com.demcha.compose.document.templates.rota.presets.CobaltStyles.LEGEND_LABEL; +import static com.demcha.compose.document.templates.rota.presets.CobaltStyles.LEGEND_PAD_Y; +import static com.demcha.compose.document.templates.rota.presets.CobaltStyles.NAVY; +import static com.demcha.compose.document.templates.rota.presets.CobaltStyles.NAVY_RULE; +import static com.demcha.compose.document.templates.rota.presets.CobaltStyles.PAPER; +import static com.demcha.compose.document.templates.rota.presets.CobaltStyles.SINGLE_SLOT_PAD_Y; +import static com.demcha.compose.document.templates.rota.presets.CobaltStyles.STAFF_NAME; +import static com.demcha.compose.document.templates.rota.presets.CobaltStyles.ZEBRA_TINT; +import static com.demcha.compose.document.templates.rota.presets.CobaltStyles.chipCellStyle; +import static com.demcha.compose.document.templates.rota.presets.CobaltStyles.colorFor; +import static com.demcha.compose.document.templates.rota.presets.CobaltStyles.labelOnFillFor; +import static com.demcha.compose.document.templates.rota.presets.CobaltStyles.slotStyle; +import static com.demcha.compose.document.templates.rota.presets.CobaltStyles.textCellStyle; + +/** + * Every row of the sheet below the header: what the colours mean, how busy each + * day is, and then the bands of staff and their days. + */ +final class CobaltRows { + + private CobaltRows() { + } + + /** + * The strip of swatches that says what the colours mean. + * + *

    As many strips as it takes. A site documents as many statuses as it + * uses and the sheet has as many columns as it has days; where the first + * outruns the second the legend runs onto another row rather than losing + * its tail — a status a reader cannot look up is worse than a second + * strip.

    + */ + static void renderLegend(TableBuilder table, RotaLegend legend, CobaltStyles.Grid grid) { + if (!legend.isPresent()) { + return; + } + List entries = legend.entries(); + int perRow = Math.max(1, grid.days()); + int strips = Math.max(1, (entries.size() + perRow - 1) / perRow); + for (int strip = 0; strip < strips; strip++) { + List cells = new ArrayList<>(grid.columnCount()); + // Only the first strip is labelled; the ones under it continue it. + cells.add(DocumentTableCell.text(strip == 0 ? legend.label() : "") + .withStyle(textCellStyle(LEGEND_LABEL, DocumentTableTextAnchor.CENTER, + LEGEND_PAD_Y, LABEL_PAD_X, GRID_RULE, PAPER))); + int from = strip * perRow; + int to = Math.min(entries.size(), from + perRow); + for (int i = from; i < to; i++) { + cells.add(DocumentTableCell.node(swatchNode(entries.get(i), grid)) + .withStyle(chipCellStyle(grid.legendInset(), LEGEND_PAD_Y, + GRID_RULE, PAPER))); + } + squareOff(cells, grid, GRID_RULE, PAPER, LEGEND_PAD_Y); + table.rowCells(cells); + } + } + + private static DocumentNode swatchNode(RotaLegend.Entry entry, CobaltStyles.Grid grid) { + return new ShapeContainerBuilder() + .name("LegendSwatch") + .roundedRect(grid.legendWidth(), LEGEND_CHIP_HEIGHT, CHIP_CORNER_RADIUS) + .fillColor(colorFor(entry.status())) + .center(CobaltChips.label(entry.label(), + labelOnFillFor(entry.status(), false), grid.legendWidth())) + .build(); + } + + /** + * How many each day is expecting, split across the two services. + * + *

    Drawn only when the rota says. A navy bar with a blank label over seven + * empty boxes is two rows of a twelve-person sheet spent on nothing, and the + * sheet is short of rows as it is.

    + */ + static void renderCovers(TableBuilder table, StructuredRotaData rota, + CobaltStyles.Grid grid) { + RotaLegend legend = rota.legend(); + boolean stated = !legend.coversLabel().isBlank() + || rota.days().stream().anyMatch(day -> day.covers().isPresent()); + if (!stated) { + return; + } + DocumentTableStyle value = DocumentTableStyle.builder() + .padding(new DocumentInsets( + COVERS_PAD_Y, grid.cellPadX(), COVERS_PAD_Y, grid.cellPadX())) + .fillColor(PAPER) + .stroke(NAVY_RULE) + .textStyle(COVERS_VALUE) + .textAnchor(DocumentTableTextAnchor.CENTER) + .lineSpacing(0) + .build(); + List cells = new ArrayList<>(grid.columnCount()); + cells.add(DocumentTableCell.text(legend.coversLabel()) + .withStyle(DocumentTableStyle.builder() + .padding(new DocumentInsets( + COVERS_PAD_Y, LABEL_PAD_X, COVERS_PAD_Y, LABEL_PAD_X)) + .fillColor(NAVY) + .stroke(NAVY_RULE) + .textStyle(COVERS_LABEL) + .textAnchor(DocumentTableTextAnchor.CENTER) + .lineSpacing(0) + .build())); + for (RotaDay day : rota.days()) { + cells.add(DocumentTableCell.node(coversNode(day, legend)).withStyle(value)); + } + squareOff(cells, grid, NAVY_RULE, PAPER, COVERS_PAD_Y); + table.rowCells(cells); + } + + /** + * The two counts on one line, each behind its own mark. + * + *

    Five runs and not one string: the marks are set smaller and paler than + * the figures, which is what stops a reader having to know from elsewhere + * which count is which.

    + */ + private static DocumentNode coversNode(RotaDay day, RotaLegend legend) { + ParagraphBuilder paragraph = new ParagraphBuilder() + .name("CoversValue") + .textStyle(COVERS_VALUE) + .align(TextAlign.CENTER) + .lineSpacing(0) + .margin(DocumentInsets.zero()); + paragraph.rich(rich -> rich + .style(legend.coversLunchLabel() + " ", COVERS_TAG) + .style(day.covers().lunch(), COVERS_VALUE) + .style(" / ", COVERS_TAG) + .style(legend.coversDinnerLabel() + " ", COVERS_TAG) + .style(day.covers().dinner(), COVERS_VALUE)); + return paragraph.build(); + } + + /** The navy strip that opens a band of staff. */ + static void renderBand(TableBuilder table, RotaGroup group, CobaltStyles.Grid grid) { + RowBuilder band = new RowBuilder() + .name("GroupBand") + .verticalAlign(RowVerticalAlign.CENTER); + if (CobaltIcons.has(group.icon())) { + band.weights(BAND_ICON_SIZE, CONTENT_WIDTH - BAND_ICON_SIZE) + .add(CobaltIcons.icon(group.icon()).node(BAND_ICON_SIZE)); + } + band.addParagraph(p -> p + .name("GroupLabel") + .text(group.label()) + .textStyle(GROUP_LABEL) + .align(TextAlign.LEFT) + .lineSpacing(0)); + table.rowCells(DocumentTableCell.node(band.build()) + .withStyle(DocumentTableStyle.builder() + .padding(new DocumentInsets(BAND_PAD_Y, BAND_PAD_X, BAND_PAD_Y, BAND_PAD_X)) + .fillColor(NAVY) + .stroke(NAVY_RULE) + .textStyle(GROUP_LABEL) + .textAnchor(DocumentTableTextAnchor.CENTER_LEFT) + .lineSpacing(0) + .build()) + .colSpan(grid.columnCount())); + } + + /** + * One person and their days. + * + *

    The row's fill is threaded by hand into every cell and on into the + * plain chips inside them, rather than left to the table's own striping: a + * stripe set on the table would also stripe the header, the legend, the + * covers and the bands, and each of those otherwise paints its own paper + * over it.

    + * + * @param table the sheet's one table + * @param staff the person + * @param grid the sheet's geometry, which says how many days to walk + * @param stripe whether this row takes the tint + */ + static void renderStaff(TableBuilder table, RotaStaff staff, CobaltStyles.Grid grid, + boolean stripe) { + DocumentColor rowFill = stripe ? ZEBRA_TINT : PAPER; + List cells = new ArrayList<>(grid.columnCount()); + cells.add(DocumentTableCell.text(staff.name()) + .withStyle(textCellStyle(STAFF_NAME, DocumentTableTextAnchor.CENTER, + CELL_PAD_Y, LABEL_PAD_X, GRID_RULE, rowFill))); + for (int day = 0; day < grid.days(); day++) { + cells.add(shiftCell(staff.day(day), grid, rowFill)); + } + table.rowCells(cells); + } + + /** + * One day of one person: a block of the sheet's own height, holding none, + * one, or a stacked pair of chips. + * + *

    The block is a nested one-column table because a cell places a node + * child at its top: without a block of a stated height a short chip in a row + * made tall by a neighbour would hang with all the slack beneath it.

    + */ + private static DocumentTableCell shiftCell(List shifts, CobaltStyles.Grid grid, + DocumentColor rowFill) { + return DocumentTableCell.node(shiftBlock(shifts, grid, rowFill)) + .withStyle(chipCellStyle(grid.chipInsetX(), CELL_PAD_Y * 0.6, GRID_RULE, rowFill)); + } + + private static DocumentNode shiftBlock(List shifts, CobaltStyles.Grid grid, + DocumentColor rowFill) { + double width = grid.chipWidth(); + TableBuilder block = new TableBuilder() + .name("ShiftBlock") + .columns(DocumentTableColumn.fixed(width)) + .padding(DocumentInsets.zero()) + .margin(DocumentInsets.zero()); + if (shifts.isEmpty()) { + // An empty day is still a block of the same height, so the row it + // sits in does not collapse around it. + block.rowCells(DocumentTableCell.node(CobaltChips.chip( + "", ShiftStatus.NONE, ShiftEmphasis.PLAIN, width, CHIP_HEIGHT, + false, rowFill)) + .withStyle(slotStyle(SINGLE_SLOT_PAD_Y, rowFill))); + return block.build(); + } + if (shifts.size() == 1) { + RotaShift only = shifts.get(0); + block.rowCells(DocumentTableCell.node(CobaltChips.chip( + only.text(), only.status(), only.emphasis(), width, CHIP_HEIGHT, + true, rowFill)) + .withStyle(slotStyle(SINGLE_SLOT_PAD_Y, rowFill))); + return block.build(); + } + for (RotaShift shift : shifts) { + block.rowCells(DocumentTableCell.node(CobaltChips.chip( + shift.text(), shift.status(), shift.emphasis(), width, + CHIP_STACK_HEIGHT, false, rowFill)) + .withStyle(slotStyle(CHIP_STACK_GAP / 2, rowFill))); + } + return block.build(); + } + + /** + * Makes a row exactly as wide as the sheet. + * + *

    The legend documents as many statuses as a site uses and the sheet has + * as many columns as it has days; the two are not the same number. A short + * row is closed with empty cells carrying its own rule, and a long one is + * cut — either way the table stays square, which a ragged row would + * not.

    + */ + private static void squareOff(List cells, CobaltStyles.Grid grid, + DocumentStroke rule, DocumentColor fill, double padY) { + while (cells.size() < grid.columnCount()) { + cells.add(DocumentTableCell.text("") + .withStyle(textCellStyle(LEGEND_LABEL, DocumentTableTextAnchor.CENTER, + padY, grid.cellPadX(), rule, fill))); + } + while (cells.size() > grid.columnCount()) { + cells.remove(cells.size() - 1); + } + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/rota/presets/CobaltStyles.java b/templates/src/main/java/com/demcha/compose/document/templates/rota/presets/CobaltStyles.java new file mode 100644 index 000000000..c4b96388f --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/rota/presets/CobaltStyles.java @@ -0,0 +1,502 @@ +package com.demcha.compose.document.templates.rota.presets; + +import com.demcha.compose.document.api.DocumentPageSize; +import com.demcha.compose.document.style.DocumentColor; +import com.demcha.compose.document.style.DocumentInsets; +import com.demcha.compose.document.style.DocumentStroke; +import com.demcha.compose.document.style.DocumentTextDecoration; +import com.demcha.compose.document.style.DocumentTextStyle; +import com.demcha.compose.document.table.DocumentTableStyle; +import com.demcha.compose.document.table.DocumentTableTextAnchor; +import com.demcha.compose.document.templates.data.rota.ShiftStatus; +import com.demcha.compose.font.FontName; + +/** + * Every colour, size and measurement of the Cobalt rota. + * + *

    Two roots, and everything derives from them

    + * + *

    Horizontal geometry derives from the page: the label column takes a + * measured share of the content width and the day columns divide what is + * left. Type derives from {@link #BODY_SIZE}: every face is a ratio of it. Two + * numbers move the sheet, and the rest follows.

    + * + *

    One rule width, many colours

    + * + *

    Every cell edge in the table is drawn at {@link #GRID_LINE_WIDTH}; only the + * colour changes. A cell's stroke is centred on its box and so reaches half its + * width outside it — mix widths, or leave a neighbour unstroked, and the shared + * edge lands in a different place on each side and the grid steps.

    + */ +final class CobaltStyles { + + private CobaltStyles() { + } + + // ------------------------------------------------------------------ + // The page + // ------------------------------------------------------------------ + + /** A rota is read across its days, not down them, so the page turns on its side. */ + static final DocumentPageSize PAGE = DocumentPageSize.A4.landscape(); + + static final double MARGIN_X = PAGE.width() * 0.01475; + static final double MARGIN_TOP = PAGE.height() * 0.0178; + + /** + * Deep enough to hold the footer chrome. The separator draws at the top of + * the footer zone and the text just beneath it, so the zone's depth is what + * places the footer. + */ + static final double MARGIN_BOTTOM = PAGE.height() * 0.0654; + + static final DocumentInsets PAGE_MARGIN = + new DocumentInsets(MARGIN_TOP, MARGIN_X, MARGIN_BOTTOM, MARGIN_X); + + static final double CONTENT_WIDTH = PAGE.width() - 2 * MARGIN_X; + + // ------------------------------------------------------------------ + // The grid + // ------------------------------------------------------------------ + + /** + * The share of the content width the staff-name column takes. Every other + * horizontal dimension derives from it: the day columns are what is left, + * divided evenly. Change this one number and the grid moves together. + */ + static final double LABEL_COLUMN_WEIGHT = 0.1253; + + static final double LABEL_COLUMN_WIDTH = CONTENT_WIDTH * LABEL_COLUMN_WEIGHT; + + /** + * The horizontal geometry of one sheet, which is a property of the document + * rather than of the design: the day columns divide what the label column + * leaves, so a rota of five days makes five wider columns and one of seven + * makes seven narrower ones. Everything that sits inside a day column — its + * padding, its chips, its legend swatch — is a fraction of that width, so + * all of it moves together. + * + * @param days how many day columns the sheet has + * @param dayWidth the width of one of them + * @param cellPadX a body cell's horizontal padding + * @param chipInsetX the gap between a chip and its column's edge + * @param chipWidth what is left for the chip itself + * @param legendInset the same gap for a legend swatch, which sits closer + * @param legendWidth what is left for the swatch + */ + record Grid(int days, double dayWidth, double cellPadX, double chipInsetX, + double chipWidth, double legendInset, double legendWidth) { + + /** + * Rejects a grid whose parts do not belong to one another. + * + *

    Six of the seven components derive from the first, so the only way + * to build a grid is {@link #of(int)} — the check is here so that stays + * true rather than depending on nobody calling the constructor.

    + */ + Grid { + if (days < 0) { + throw new IllegalArgumentException("A rota cannot have " + days + " days."); + } + if (dayWidth <= 0 || chipWidth <= 0 || legendWidth <= 0) { + throw new IllegalArgumentException( + "Every width of a rota grid must be positive; got day " + dayWidth + + ", chip " + chipWidth + ", legend " + legendWidth + "."); + } + } + + /** + * The grid a rota of this many days is drawn on. + * + * @param days the document's own day count + * @return the geometry + */ + static Grid of(int days) { + // A rota with no days still has its label column, and a width is + // still asked for: one column's worth keeps the arithmetic finite + // and nothing is drawn in it. + double width = CONTENT_WIDTH * (1 - LABEL_COLUMN_WEIGHT) / Math.max(1, days); + double chipInset = width * 0.035; + double legendInset = width * 0.012; + return new Grid(days, width, width * 0.03, chipInset, width - 2 * chipInset, + legendInset, width - 2 * legendInset); + } + + /** The label column plus one per day. */ + int columnCount() { + return days + 1; + } + } + + /** + * The masthead rule, the day names, the day notes and the rule that closes + * the header — four rows, all repeating on a continuation page. + */ + static final int HEADER_ROW_COUNT = 4; + + static final double STAFF_ROW_HEIGHT = PAGE.height() * 0.0417; + static final double BAND_HEIGHT = PAGE.height() * 0.0332; + static final double BAND_ICON_SIZE = BAND_HEIGHT * 0.52; + + // ------------------------------------------------------------------ + // Rules: one hierarchy, one base + // ------------------------------------------------------------------ + + /** + * The hairline the body grid is drawn with, and the unit every heavier rule + * is a multiple of. + * + *

    Not half a point, which is what a design measures and what a rota + * pinned to a wall cannot use: below about a point the grid falls under a + * device pixel the moment the page is viewed at less than full size, and the + * sheet reads as floating text with no table under it. Raising this one + * number raises the whole hierarchy with it, so the rules keep their order + * rather than flattening into each other.

    + */ + static final double GRID_LINE_WIDTH = 0.9; + + /** The outline chip's hairline: a foreground detail, just above the grid. */ + static final double CHIP_OUTLINE_WIDTH = GRID_LINE_WIDTH * 1.22; + + /** The rule closing the header, heavier so the two read as different things. */ + static final double BODY_TOP_RULE_THICKNESS = GRID_LINE_WIDTH * 1.78; + + /** The masthead's rule, the heaviest line on the sheet. */ + static final double MASTHEAD_RULE_THICKNESS = GRID_LINE_WIDTH * 2.1; + + /** + * The footer's rule, which is chrome and not part of the table's grid, and + * so is deliberately not tied to the hierarchy above. + * + *

    {@code DocumentHeaderFooter} fixes the gap between that rule and the + * footer text, so a heavier rule crowds the text against it.

    + */ + static final double FOOTER_RULE_WIDTH = 0.5; + + // ------------------------------------------------------------------ + // Chips + // ------------------------------------------------------------------ + + /** A lone entry fills its cell: about seven tenths of the row. */ + static final double CHIP_HEIGHT = STAFF_ROW_HEIGHT * 0.705; + + /** One of a stacked pair is shorter, so two and a gap fit the same block. */ + static final double CHIP_STACK_HEIGHT = STAFF_ROW_HEIGHT * 0.37; + + static final double CHIP_STACK_GAP = 1.2; + static final double CHIP_CORNER_RADIUS = CHIP_HEIGHT * 0.23; + + /** + * The height every day cell's content occupies, whatever it holds. + * + *

    This is what makes a chip sit centred. A table cell places a node child + * at its top — {@code textAnchor} seats text, not a node — so a + * short chip in a row made tall by a neighbouring cell would hang with all + * the slack beneath it. Giving every day cell the same content height leaves + * no slack to hang in: a stacked pair fills this exactly, and a lone chip is + * centred in it by {@link #SINGLE_SLOT_PAD_Y}, which is the difference + * halved rather than a number nudged until it looked right.

    + */ + static final double CELL_BLOCK_HEIGHT = 2 * CHIP_STACK_HEIGHT + 2 * CHIP_STACK_GAP; + + /** Half the room a lone chip does not use, so it lands in the middle. */ + static final double SINGLE_SLOT_PAD_Y = (CELL_BLOCK_HEIGHT - CHIP_HEIGHT) / 2; + + /** The legend's swatches sit closer to their column's edges than a shift does. */ + static final double LEGEND_CHIP_HEIGHT = STAFF_ROW_HEIGHT * 0.62; + + // ------------------------------------------------------------------ + // Padding + // ------------------------------------------------------------------ + + static final double CELL_PAD_Y = STAFF_ROW_HEIGHT * 0.12; + + /** + * The legend and covers rows are taller than a staff row and own their own + * padding: one constant for two different rows would make shrinking the + * staff rows shorten these with them. + */ + static final double LEGEND_PAD_Y = STAFF_ROW_HEIGHT * 0.214; + static final double COVERS_PAD_Y = STAFF_ROW_HEIGHT * 0.270; + + static final double DAY_NAME_PAD_TOP = PAGE.height() * 0.010; + static final double DAY_NAME_PAD_BOTTOM = PAGE.height() * 0.018; + static final double DAY_NOTE_PAD_TOP = PAGE.height() * 0.012; + static final double DAY_NOTE_PAD_BOTTOM = PAGE.height() * 0.018; + + /** + * Barely any vertical padding: this cell sets the header row's height, and + * the day-name cell's generous insets here cost enough page to push a + * twelve-person sheet onto a second one. + */ + static final double LABEL_HEADER_PAD_Y = CELL_PAD_Y * 0.4; + + static final double BAND_PAD_X = CONTENT_WIDTH * 0.008; + static final double BAND_PAD_Y = BAND_HEIGHT * 0.22; + + // ------------------------------------------------------------------ + // Type + // ------------------------------------------------------------------ + + /** + * The face the sheet is set in. The templates artifact carries no fonts: + * Carlito and Spectral arrive with {@code graph-compose-fonts} on the + * classpath, or a caller registers families of those names on the session + * itself. With neither, the engine substitutes and every size is solved for + * type that is not there. + */ + static final FontName SANS = FontName.CARLITO; + + /** The wordmark's face, and the only serif on the sheet. */ + static final FontName SERIF = FontName.SPECTRAL; + + /** The body face every other size is a ratio of. */ + static final double BODY_SIZE = 10.4; + + /** + * The label column's own horizontal padding. + * + *

    Taken once from a seven-day grid and then held there. The label + * column's width does not change with the day count and the mark in it + * should not either: derived live, a five-day rota would set its wordmark at + * a different size from a seven-day one for no reason a reader could see. + * The seven is the design's own, and the number it produces is the padding + * the design gives this column.

    + */ + static final double LABEL_PAD_X = Grid.of(7).cellPadX(); + + /** + * The width the lockup gets: its column less the padding holding it off the + * rule that boxes the cell. Stated on its own because it is the constraint + * the wordmark is sized against. + */ + static final double LOCKUP_WIDTH = LABEL_COLUMN_WIDTH - 2 * LABEL_PAD_X; + + /** + * Sized by the width it has to fit rather than by the body scale: the lockup + * sits in the label column beside the day names, and this fraction is what + * keeps the wordmark on one line. + */ + static final double WORDMARK_SIZE = LOCKUP_WIDTH * 0.22; + static final double WORDMARK_SUB_SIZE = WORDMARK_SIZE * 0.4766; + + /** + * The lockup's two lines, closed up. + * + *

    Negative because it corrects a gap nothing asked for: the section + * stacks its children with no spacing and the wordmark's own line box still + * leaves about a third of its size between the lines. It would belong on the + * section, which owns the distance between its children, but {@code spacing} + * refuses a negative value — so it is a top margin on the one child it + * applies to. It is a fraction of the wordmark because the gap it corrects is + * that face's line box, so the two move together.

    + */ + static final double LOCKUP_LINE_SPACING = -WORDMARK_SIZE * 0.3076; + + static final double DAY_NAME_SIZE = BODY_SIZE * 0.87; + static final double DAY_ORDINAL_SUFFIX_SIZE = DAY_NAME_SIZE * 0.68; + static final double DAY_NOTE_SIZE = BODY_SIZE * 0.82; + static final double GROUP_LABEL_SIZE = BODY_SIZE * 0.95; + static final double STAFF_NAME_SIZE = BODY_SIZE * 1.03; + static final double LEGEND_LABEL_SIZE = BODY_SIZE * 0.79; + static final double COVERS_LABEL_SIZE = BODY_SIZE * 0.96; + static final double COVERS_VALUE_SIZE = BODY_SIZE; + static final double COVERS_TAG_SIZE = BODY_SIZE * 0.64; + static final double FOOTER_SIZE = BODY_SIZE * 0.70; + + /** + * The one face a box constrains rather than the scale: a chip in a stacked + * pair has only {@link #CHIP_STACK_HEIGHT} to sit in, so its size is a + * fraction of that slot. Tie it to the body scale and the pair stops fitting + * the block a single chip fills. + */ + static final double CHIP_SIZE = CHIP_STACK_HEIGHT * 0.89; + + /** A lone entry carries the body face; a stacked one keeps the smaller. */ + static final double CHIP_SINGLE_SIZE = BODY_SIZE; + + // ------------------------------------------------------------------ + // Colour + // ------------------------------------------------------------------ + + static final DocumentColor NAVY = DocumentColor.rgb(16, 32, 80); + static final DocumentColor GOLD = DocumentColor.rgb(166, 138, 66); + static final DocumentColor PAPER = DocumentColor.rgb(255, 255, 255); + static final DocumentColor GRID_LINE = DocumentColor.rgb(208, 208, 224); + + /** + * The tint on alternate staff rows: pale enough that the grid drawn over it + * stays the darker of the two, and that navy text and every status colour + * keep their contrast. Its job is to give the eye a rail to follow across + * the columns, not to be seen. + */ + static final DocumentColor ZEBRA_TINT = DocumentColor.rgb(228, 235, 248); + + /** Navy at half strength: enough to read the tag, not enough to compete. */ + static final DocumentColor COVERS_TAG_INK = DocumentColor.rgb(120, 132, 168); + + static final DocumentColor STATUS_REQUEST = DocumentColor.rgb(176, 176, 192); + static final DocumentColor STATUS_OFF = DocumentColor.rgb(208, 16, 16); + static final DocumentColor STATUS_HOLIDAY = DocumentColor.rgb(255, 192, 32); + static final DocumentColor STATUS_STOCK = DocumentColor.rgb(16, 160, 80); + static final DocumentColor STATUS_STANDBY = DocumentColor.rgb(192, 144, 224); + static final DocumentColor STATUS_TRAINING = DocumentColor.rgb(240, 80, 16); + static final DocumentColor STATUS_SUPPORT = DocumentColor.rgb(208, 216, 240); + + static final DocumentStroke NAVY_RULE = DocumentStroke.of(NAVY, GRID_LINE_WIDTH); + static final DocumentStroke GRID_RULE = DocumentStroke.of(GRID_LINE, GRID_LINE_WIDTH); + + // ------------------------------------------------------------------ + // Text styles + // ------------------------------------------------------------------ + + static DocumentTextStyle sans(double size, DocumentTextDecoration decoration, + DocumentColor color) { + return DocumentTextStyle.builder() + .fontName(SANS).size(size).decoration(decoration).color(color).build(); + } + + static DocumentTextStyle serif(double size, DocumentTextDecoration decoration, + DocumentColor color) { + return DocumentTextStyle.builder() + .fontName(SERIF).size(size).decoration(decoration).color(color).build(); + } + + static final DocumentTextStyle WORDMARK = + serif(WORDMARK_SIZE, DocumentTextDecoration.DEFAULT, GOLD); + static final DocumentTextStyle WORDMARK_SUB = + sans(WORDMARK_SUB_SIZE, DocumentTextDecoration.DEFAULT, GOLD); + static final DocumentTextStyle DAY_NAME = + sans(DAY_NAME_SIZE, DocumentTextDecoration.BOLD, NAVY); + static final DocumentTextStyle DAY_ORDINAL_SUFFIX = + sans(DAY_ORDINAL_SUFFIX_SIZE, DocumentTextDecoration.BOLD, NAVY); + static final DocumentTextStyle DAY_NOTE = + sans(DAY_NOTE_SIZE, DocumentTextDecoration.BOLD, NAVY); + static final DocumentTextStyle GROUP_LABEL = + sans(GROUP_LABEL_SIZE, DocumentTextDecoration.BOLD, PAPER); + static final DocumentTextStyle STAFF_NAME = + sans(STAFF_NAME_SIZE, DocumentTextDecoration.BOLD, NAVY); + static final DocumentTextStyle LEGEND_LABEL = + sans(LEGEND_LABEL_SIZE, DocumentTextDecoration.BOLD, NAVY); + static final DocumentTextStyle COVERS_LABEL = + sans(COVERS_LABEL_SIZE, DocumentTextDecoration.BOLD, PAPER); + static final DocumentTextStyle COVERS_VALUE = + sans(COVERS_VALUE_SIZE, DocumentTextDecoration.BOLD, NAVY); + static final DocumentTextStyle COVERS_TAG = + sans(COVERS_TAG_SIZE, DocumentTextDecoration.BOLD, COVERS_TAG_INK); + static final DocumentTextStyle CHIP_ON_LIGHT = + sans(CHIP_SIZE, DocumentTextDecoration.BOLD, NAVY); + static final DocumentTextStyle CHIP_ON_DARK = + sans(CHIP_SIZE, DocumentTextDecoration.BOLD, PAPER); + static final DocumentTextStyle CHIP_SINGLE_ON_LIGHT = + sans(CHIP_SINGLE_SIZE, DocumentTextDecoration.BOLD, NAVY); + static final DocumentTextStyle CHIP_SINGLE_ON_DARK = + sans(CHIP_SINGLE_SIZE, DocumentTextDecoration.BOLD, PAPER); + + /** + * A rule row is an empty cell, and an empty cell is as tall as its own font, + * so the rule's thickness is set as a type size. + */ + static final DocumentTextStyle MASTHEAD_RULE_TEXT = + sans(MASTHEAD_RULE_THICKNESS, DocumentTextDecoration.DEFAULT, NAVY); + static final DocumentTextStyle BODY_TOP_RULE_TEXT = + sans(BODY_TOP_RULE_THICKNESS, DocumentTextDecoration.DEFAULT, GRID_LINE); + + // ------------------------------------------------------------------ + // Cell styles + // ------------------------------------------------------------------ + + /** The colour a status is drawn in. */ + static DocumentColor colorFor(ShiftStatus status) { + return switch (status) { + case REQUEST -> STATUS_REQUEST; + case OFF -> STATUS_OFF; + case HOLIDAY -> STATUS_HOLIDAY; + case STOCK -> STATUS_STOCK; + case STANDBY -> STATUS_STANDBY; + case TRAINING -> STATUS_TRAINING; + case SUPPORT -> STATUS_SUPPORT; + case NONE -> PAPER; + }; + } + + /** + * The label colour a filled chip needs, which is a property of the status + * rather than of the chip: the pale fills carry navy labels and the + * saturated ones carry white. One white constant would make every holiday + * cell unreadable. + * + * @param status what the entry means + * @param lone whether it is the only entry in its cell, and so larger + * @return the style for its label + */ + static DocumentTextStyle labelOnFillFor(ShiftStatus status, boolean lone) { + boolean onDark = switch (status) { + case HOLIDAY, STANDBY, SUPPORT, NONE -> false; + case REQUEST, OFF, STOCK, TRAINING -> true; + }; + if (onDark) { + return lone ? CHIP_SINGLE_ON_DARK : CHIP_ON_DARK; + } + return lone ? CHIP_SINGLE_ON_LIGHT : CHIP_ON_LIGHT; + } + + /** One slot of a shift block; the padding is what seats the chip in it. */ + static DocumentTableStyle slotStyle(double padY, DocumentColor rowFill) { + return DocumentTableStyle.builder() + .padding(new DocumentInsets(padY, 0, padY, 0)) + .fillColor(rowFill) + .stroke(DocumentStroke.of(rowFill, 0)) + .textStyle(CHIP_ON_LIGHT) + .textAnchor(DocumentTableTextAnchor.CENTER) + .lineSpacing(0) + .build(); + } + + /** A cell whose content is a chip: the inset is its horizontal padding. */ + static DocumentTableStyle chipCellStyle(double insetX, double padY, + DocumentStroke rule, DocumentColor fill) { + return DocumentTableStyle.builder() + .padding(new DocumentInsets(padY, insetX, padY, insetX)) + .fillColor(fill) + .stroke(rule) + .textStyle(CHIP_ON_LIGHT) + .textAnchor(DocumentTableTextAnchor.CENTER) + .lineSpacing(0) + .build(); + } + + /** A plain text cell with the body padding and a named rule. */ + static DocumentTableStyle textCellStyle(DocumentTextStyle text, + DocumentTableTextAnchor anchor, double padY, + double padX, DocumentStroke rule, + DocumentColor fill) { + return DocumentTableStyle.builder() + .padding(new DocumentInsets(padY, padX, padY, padX)) + .fillColor(fill) + .stroke(rule) + .textStyle(text) + .textAnchor(anchor) + .lineSpacing(0) + .build(); + } + + /** + * The label column's own header cells — the lockup and the cell under it. + * + *

    Stroked, so the column is closed into the same grid as the day + * columns beside it. Left unstroked it reads as an open margin with a mark + * floating in it rather than as the first column of the sheet.

    + */ + static DocumentTableStyle labelHeaderStyle() { + return DocumentTableStyle.builder() + .padding(new DocumentInsets( + LABEL_HEADER_PAD_Y, LABEL_PAD_X, LABEL_HEADER_PAD_Y, LABEL_PAD_X)) + .fillColor(PAPER) + .stroke(GRID_RULE) + .textStyle(DAY_NAME) + .textAnchor(DocumentTableTextAnchor.CENTER) + .lineSpacing(0) + .build(); + } +} diff --git a/templates/src/main/java/com/demcha/compose/document/templates/rota/presets/package-info.java b/templates/src/main/java/com/demcha/compose/document/templates/rota/presets/package-info.java new file mode 100644 index 000000000..d8ae02be6 --- /dev/null +++ b/templates/src/main/java/com/demcha/compose/document/templates/rota/presets/package-info.java @@ -0,0 +1,21 @@ +/** + * Layered rota presets — thin orchestrators that compose a staff rota on the + * canonical DSL. + * + *

    Each preset is a {@code final} class whose {@code create} factory returns a + * {@link com.demcha.compose.document.templates.api.DocumentTemplate} + * parameterised on + * {@link com.demcha.compose.document.templates.data.rota.StructuredRotaDocumentSpec}, + * carrying its measured geometry and palette as preset-local tokens because it + * reproduces one designed look rather than a themeable family + * ({@code CobaltRota}).

    + * + *

    A rota is read across a row rather than down a page, so a preset here is + * built on one table whose columns are the document's own days. Colour belongs + * to the preset and meaning to the document: the model states a + * {@link com.demcha.compose.document.templates.data.rota.ShiftStatus} and the + * preset decides what that status looks like.

    + * + * @since 2.4.0 + */ +package com.demcha.compose.document.templates.rota.presets; diff --git a/templates/src/main/resources/templates/cv/charcoal-gold/icons/certificate.svg b/templates/src/main/resources/templates/cv/charcoal-gold/icons/certificate.svg new file mode 100644 index 000000000..70bfd06fe --- /dev/null +++ b/templates/src/main/resources/templates/cv/charcoal-gold/icons/certificate.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/cv/charcoal-gold/icons/email.svg b/templates/src/main/resources/templates/cv/charcoal-gold/icons/email.svg new file mode 100644 index 000000000..cb7317e63 --- /dev/null +++ b/templates/src/main/resources/templates/cv/charcoal-gold/icons/email.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/cv/charcoal-gold/icons/growth.svg b/templates/src/main/resources/templates/cv/charcoal-gold/icons/growth.svg new file mode 100644 index 000000000..7cd08248b --- /dev/null +++ b/templates/src/main/resources/templates/cv/charcoal-gold/icons/growth.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/cv/charcoal-gold/icons/linkedin.svg b/templates/src/main/resources/templates/cv/charcoal-gold/icons/linkedin.svg new file mode 100644 index 000000000..e8bf28f95 --- /dev/null +++ b/templates/src/main/resources/templates/cv/charcoal-gold/icons/linkedin.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/cv/charcoal-gold/icons/location.svg b/templates/src/main/resources/templates/cv/charcoal-gold/icons/location.svg new file mode 100644 index 000000000..936b3c22d --- /dev/null +++ b/templates/src/main/resources/templates/cv/charcoal-gold/icons/location.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/cv/charcoal-gold/icons/phone.svg b/templates/src/main/resources/templates/cv/charcoal-gold/icons/phone.svg new file mode 100644 index 000000000..ebdaf0d64 --- /dev/null +++ b/templates/src/main/resources/templates/cv/charcoal-gold/icons/phone.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/cv/charcoal-gold/icons/star.svg b/templates/src/main/resources/templates/cv/charcoal-gold/icons/star.svg new file mode 100644 index 000000000..4a012dbc6 --- /dev/null +++ b/templates/src/main/resources/templates/cv/charcoal-gold/icons/star.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/cv/charcoal-gold/icons/trophy.svg b/templates/src/main/resources/templates/cv/charcoal-gold/icons/trophy.svg new file mode 100644 index 000000000..12e9bcef6 --- /dev/null +++ b/templates/src/main/resources/templates/cv/charcoal-gold/icons/trophy.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/cv/charcoal-gold/icons/website.svg b/templates/src/main/resources/templates/cv/charcoal-gold/icons/website.svg new file mode 100644 index 000000000..26d1e0bc8 --- /dev/null +++ b/templates/src/main/resources/templates/cv/charcoal-gold/icons/website.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/cv/midnight-navy/icons/award.svg b/templates/src/main/resources/templates/cv/midnight-navy/icons/award.svg new file mode 100644 index 000000000..972d65546 --- /dev/null +++ b/templates/src/main/resources/templates/cv/midnight-navy/icons/award.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/cv/midnight-navy/icons/email.svg b/templates/src/main/resources/templates/cv/midnight-navy/icons/email.svg new file mode 100644 index 000000000..3ceb64bd2 --- /dev/null +++ b/templates/src/main/resources/templates/cv/midnight-navy/icons/email.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/cv/midnight-navy/icons/growth.svg b/templates/src/main/resources/templates/cv/midnight-navy/icons/growth.svg new file mode 100644 index 000000000..0bcd4785c --- /dev/null +++ b/templates/src/main/resources/templates/cv/midnight-navy/icons/growth.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/cv/midnight-navy/icons/linkedin.svg b/templates/src/main/resources/templates/cv/midnight-navy/icons/linkedin.svg new file mode 100644 index 000000000..c8b287ae2 --- /dev/null +++ b/templates/src/main/resources/templates/cv/midnight-navy/icons/linkedin.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/cv/midnight-navy/icons/location.svg b/templates/src/main/resources/templates/cv/midnight-navy/icons/location.svg new file mode 100644 index 000000000..165457ab2 --- /dev/null +++ b/templates/src/main/resources/templates/cv/midnight-navy/icons/location.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/cv/midnight-navy/icons/phone.svg b/templates/src/main/resources/templates/cv/midnight-navy/icons/phone.svg new file mode 100644 index 000000000..ab75561c9 --- /dev/null +++ b/templates/src/main/resources/templates/cv/midnight-navy/icons/phone.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/cv/midnight-navy/icons/trophy.svg b/templates/src/main/resources/templates/cv/midnight-navy/icons/trophy.svg new file mode 100644 index 000000000..580a8c963 --- /dev/null +++ b/templates/src/main/resources/templates/cv/midnight-navy/icons/trophy.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/cv/midnight-navy/icons/website.svg b/templates/src/main/resources/templates/cv/midnight-navy/icons/website.svg new file mode 100644 index 000000000..797b7444a --- /dev/null +++ b/templates/src/main/resources/templates/cv/midnight-navy/icons/website.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/cv/navy-sidebar/icons/briefcase.png b/templates/src/main/resources/templates/cv/navy-sidebar/icons/briefcase.png new file mode 100644 index 000000000..98d131523 Binary files /dev/null and b/templates/src/main/resources/templates/cv/navy-sidebar/icons/briefcase.png differ diff --git a/templates/src/main/resources/templates/cv/navy-sidebar/icons/certificate.png b/templates/src/main/resources/templates/cv/navy-sidebar/icons/certificate.png new file mode 100644 index 000000000..c3da29ef5 Binary files /dev/null and b/templates/src/main/resources/templates/cv/navy-sidebar/icons/certificate.png differ diff --git a/templates/src/main/resources/templates/cv/navy-sidebar/icons/email.png b/templates/src/main/resources/templates/cv/navy-sidebar/icons/email.png new file mode 100644 index 000000000..29bf4216f Binary files /dev/null and b/templates/src/main/resources/templates/cv/navy-sidebar/icons/email.png differ diff --git a/templates/src/main/resources/templates/cv/navy-sidebar/icons/linkedin.png b/templates/src/main/resources/templates/cv/navy-sidebar/icons/linkedin.png new file mode 100644 index 000000000..a77bf7bf8 Binary files /dev/null and b/templates/src/main/resources/templates/cv/navy-sidebar/icons/linkedin.png differ diff --git a/templates/src/main/resources/templates/cv/navy-sidebar/icons/location.png b/templates/src/main/resources/templates/cv/navy-sidebar/icons/location.png new file mode 100644 index 000000000..0d58db247 Binary files /dev/null and b/templates/src/main/resources/templates/cv/navy-sidebar/icons/location.png differ diff --git a/templates/src/main/resources/templates/cv/navy-sidebar/icons/phone.png b/templates/src/main/resources/templates/cv/navy-sidebar/icons/phone.png new file mode 100644 index 000000000..749c4c181 Binary files /dev/null and b/templates/src/main/resources/templates/cv/navy-sidebar/icons/phone.png differ diff --git a/templates/src/main/resources/templates/cv/navy-sidebar/icons/trophy.png b/templates/src/main/resources/templates/cv/navy-sidebar/icons/trophy.png new file mode 100644 index 000000000..80e7964ff Binary files /dev/null and b/templates/src/main/resources/templates/cv/navy-sidebar/icons/trophy.png differ diff --git a/templates/src/main/resources/templates/cv/orange-ops/icons/achievement-accuracy.svg b/templates/src/main/resources/templates/cv/orange-ops/icons/achievement-accuracy.svg new file mode 100644 index 000000000..4acd24b1b --- /dev/null +++ b/templates/src/main/resources/templates/cv/orange-ops/icons/achievement-accuracy.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/cv/orange-ops/icons/achievement-productivity.svg b/templates/src/main/resources/templates/cv/orange-ops/icons/achievement-productivity.svg new file mode 100644 index 000000000..7e01ed82c --- /dev/null +++ b/templates/src/main/resources/templates/cv/orange-ops/icons/achievement-productivity.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/cv/orange-ops/icons/achievement-safety.svg b/templates/src/main/resources/templates/cv/orange-ops/icons/achievement-safety.svg new file mode 100644 index 000000000..ed04bed25 --- /dev/null +++ b/templates/src/main/resources/templates/cv/orange-ops/icons/achievement-safety.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/cv/orange-ops/icons/achievement-savings.svg b/templates/src/main/resources/templates/cv/orange-ops/icons/achievement-savings.svg new file mode 100644 index 000000000..c80d1294d --- /dev/null +++ b/templates/src/main/resources/templates/cv/orange-ops/icons/achievement-savings.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/cv/orange-ops/icons/driving.svg b/templates/src/main/resources/templates/cv/orange-ops/icons/driving.svg new file mode 100644 index 000000000..1277390b1 --- /dev/null +++ b/templates/src/main/resources/templates/cv/orange-ops/icons/driving.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/cv/orange-ops/icons/email.svg b/templates/src/main/resources/templates/cv/orange-ops/icons/email.svg new file mode 100644 index 000000000..a8b288cd0 --- /dev/null +++ b/templates/src/main/resources/templates/cv/orange-ops/icons/email.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/cv/orange-ops/icons/graduation.svg b/templates/src/main/resources/templates/cv/orange-ops/icons/graduation.svg new file mode 100644 index 000000000..7bd6b994a --- /dev/null +++ b/templates/src/main/resources/templates/cv/orange-ops/icons/graduation.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/cv/orange-ops/icons/interests.svg b/templates/src/main/resources/templates/cv/orange-ops/icons/interests.svg new file mode 100644 index 000000000..e1f819a08 --- /dev/null +++ b/templates/src/main/resources/templates/cv/orange-ops/icons/interests.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/cv/orange-ops/icons/kpi-accuracy.svg b/templates/src/main/resources/templates/cv/orange-ops/icons/kpi-accuracy.svg new file mode 100644 index 000000000..d92589d89 --- /dev/null +++ b/templates/src/main/resources/templates/cv/orange-ops/icons/kpi-accuracy.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/cv/orange-ops/icons/kpi-delivery.svg b/templates/src/main/resources/templates/cv/orange-ops/icons/kpi-delivery.svg new file mode 100644 index 000000000..544ceb08b --- /dev/null +++ b/templates/src/main/resources/templates/cv/orange-ops/icons/kpi-delivery.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/cv/orange-ops/icons/kpi-productivity.svg b/templates/src/main/resources/templates/cv/orange-ops/icons/kpi-productivity.svg new file mode 100644 index 000000000..c3e0c7cea --- /dev/null +++ b/templates/src/main/resources/templates/cv/orange-ops/icons/kpi-productivity.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/cv/orange-ops/icons/kpi-safety.svg b/templates/src/main/resources/templates/cv/orange-ops/icons/kpi-safety.svg new file mode 100644 index 000000000..a1f9c5865 --- /dev/null +++ b/templates/src/main/resources/templates/cv/orange-ops/icons/kpi-safety.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/cv/orange-ops/icons/languages.svg b/templates/src/main/resources/templates/cv/orange-ops/icons/languages.svg new file mode 100644 index 000000000..153473140 --- /dev/null +++ b/templates/src/main/resources/templates/cv/orange-ops/icons/languages.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/cv/orange-ops/icons/linkedin.svg b/templates/src/main/resources/templates/cv/orange-ops/icons/linkedin.svg new file mode 100644 index 000000000..1af11efbc --- /dev/null +++ b/templates/src/main/resources/templates/cv/orange-ops/icons/linkedin.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/cv/orange-ops/icons/location.svg b/templates/src/main/resources/templates/cv/orange-ops/icons/location.svg new file mode 100644 index 000000000..8bfcc6b34 --- /dev/null +++ b/templates/src/main/resources/templates/cv/orange-ops/icons/location.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/cv/orange-ops/icons/phone.svg b/templates/src/main/resources/templates/cv/orange-ops/icons/phone.svg new file mode 100644 index 000000000..ef5dd3a38 --- /dev/null +++ b/templates/src/main/resources/templates/cv/orange-ops/icons/phone.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/cv/orange-ops/icons/systems.svg b/templates/src/main/resources/templates/cv/orange-ops/icons/systems.svg new file mode 100644 index 000000000..5e702de5d --- /dev/null +++ b/templates/src/main/resources/templates/cv/orange-ops/icons/systems.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/cv/orange-ops/icons/website.svg b/templates/src/main/resources/templates/cv/orange-ops/icons/website.svg new file mode 100644 index 000000000..5e702de5d --- /dev/null +++ b/templates/src/main/resources/templates/cv/orange-ops/icons/website.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/cv/professional-sidebar/icons/email.png b/templates/src/main/resources/templates/cv/professional-sidebar/icons/email.png new file mode 100644 index 000000000..b7fc73ca2 Binary files /dev/null and b/templates/src/main/resources/templates/cv/professional-sidebar/icons/email.png differ diff --git a/templates/src/main/resources/templates/cv/professional-sidebar/icons/linkedin.png b/templates/src/main/resources/templates/cv/professional-sidebar/icons/linkedin.png new file mode 100644 index 000000000..710983a5f Binary files /dev/null and b/templates/src/main/resources/templates/cv/professional-sidebar/icons/linkedin.png differ diff --git a/templates/src/main/resources/templates/cv/professional-sidebar/icons/location.png b/templates/src/main/resources/templates/cv/professional-sidebar/icons/location.png new file mode 100644 index 000000000..ccdaf6f53 Binary files /dev/null and b/templates/src/main/resources/templates/cv/professional-sidebar/icons/location.png differ diff --git a/templates/src/main/resources/templates/cv/professional-sidebar/icons/phone.png b/templates/src/main/resources/templates/cv/professional-sidebar/icons/phone.png new file mode 100644 index 000000000..6d27c23de Binary files /dev/null and b/templates/src/main/resources/templates/cv/professional-sidebar/icons/phone.png differ diff --git a/templates/src/main/resources/templates/cv/professional-sidebar/icons/website.png b/templates/src/main/resources/templates/cv/professional-sidebar/icons/website.png new file mode 100644 index 000000000..d4a24b64d Binary files /dev/null and b/templates/src/main/resources/templates/cv/professional-sidebar/icons/website.png differ diff --git a/templates/src/main/resources/templates/cv/serif-headline/icons/api.png b/templates/src/main/resources/templates/cv/serif-headline/icons/api.png new file mode 100644 index 000000000..8e0672abb Binary files /dev/null and b/templates/src/main/resources/templates/cv/serif-headline/icons/api.png differ diff --git a/templates/src/main/resources/templates/cv/serif-headline/icons/cart.png b/templates/src/main/resources/templates/cv/serif-headline/icons/cart.png new file mode 100644 index 000000000..50db340f2 Binary files /dev/null and b/templates/src/main/resources/templates/cv/serif-headline/icons/cart.png differ diff --git a/templates/src/main/resources/templates/cv/serif-headline/icons/chart.png b/templates/src/main/resources/templates/cv/serif-headline/icons/chart.png new file mode 100644 index 000000000..000df8f14 Binary files /dev/null and b/templates/src/main/resources/templates/cv/serif-headline/icons/chart.png differ diff --git a/templates/src/main/resources/templates/cv/serif-headline/icons/email.png b/templates/src/main/resources/templates/cv/serif-headline/icons/email.png new file mode 100644 index 000000000..5bf3300dd Binary files /dev/null and b/templates/src/main/resources/templates/cv/serif-headline/icons/email.png differ diff --git a/templates/src/main/resources/templates/cv/serif-headline/icons/github.png b/templates/src/main/resources/templates/cv/serif-headline/icons/github.png new file mode 100644 index 000000000..63aa407cd Binary files /dev/null and b/templates/src/main/resources/templates/cv/serif-headline/icons/github.png differ diff --git a/templates/src/main/resources/templates/cv/serif-headline/icons/linkedin.png b/templates/src/main/resources/templates/cv/serif-headline/icons/linkedin.png new file mode 100644 index 000000000..55cdefe62 Binary files /dev/null and b/templates/src/main/resources/templates/cv/serif-headline/icons/linkedin.png differ diff --git a/templates/src/main/resources/templates/cv/serif-headline/icons/location.png b/templates/src/main/resources/templates/cv/serif-headline/icons/location.png new file mode 100644 index 000000000..ace3af59e Binary files /dev/null and b/templates/src/main/resources/templates/cv/serif-headline/icons/location.png differ diff --git a/templates/src/main/resources/templates/cv/serif-headline/icons/medal.png b/templates/src/main/resources/templates/cv/serif-headline/icons/medal.png new file mode 100644 index 000000000..eeb1ea036 Binary files /dev/null and b/templates/src/main/resources/templates/cv/serif-headline/icons/medal.png differ diff --git a/templates/src/main/resources/templates/cv/serif-headline/icons/phone.png b/templates/src/main/resources/templates/cv/serif-headline/icons/phone.png new file mode 100644 index 000000000..a32310749 Binary files /dev/null and b/templates/src/main/resources/templates/cv/serif-headline/icons/phone.png differ diff --git a/templates/src/main/resources/templates/cv/serif-headline/icons/rocket.png b/templates/src/main/resources/templates/cv/serif-headline/icons/rocket.png new file mode 100644 index 000000000..9bde581dc Binary files /dev/null and b/templates/src/main/resources/templates/cv/serif-headline/icons/rocket.png differ diff --git a/templates/src/main/resources/templates/cv/serif-headline/icons/trophy.png b/templates/src/main/resources/templates/cv/serif-headline/icons/trophy.png new file mode 100644 index 000000000..6afe735ca Binary files /dev/null and b/templates/src/main/resources/templates/cv/serif-headline/icons/trophy.png differ diff --git a/templates/src/main/resources/templates/cv/slate-orange/icons/achievement.svg b/templates/src/main/resources/templates/cv/slate-orange/icons/achievement.svg new file mode 100644 index 000000000..c66f37eb8 --- /dev/null +++ b/templates/src/main/resources/templates/cv/slate-orange/icons/achievement.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/cv/slate-orange/icons/availability.svg b/templates/src/main/resources/templates/cv/slate-orange/icons/availability.svg new file mode 100644 index 000000000..4f051b9b1 --- /dev/null +++ b/templates/src/main/resources/templates/cv/slate-orange/icons/availability.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/cv/slate-orange/icons/calendar.svg b/templates/src/main/resources/templates/cv/slate-orange/icons/calendar.svg new file mode 100644 index 000000000..2d7a77e2f --- /dev/null +++ b/templates/src/main/resources/templates/cv/slate-orange/icons/calendar.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/cv/slate-orange/icons/communication.svg b/templates/src/main/resources/templates/cv/slate-orange/icons/communication.svg new file mode 100644 index 000000000..dba396bdb --- /dev/null +++ b/templates/src/main/resources/templates/cv/slate-orange/icons/communication.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/cv/slate-orange/icons/crm.svg b/templates/src/main/resources/templates/cv/slate-orange/icons/crm.svg new file mode 100644 index 000000000..cb1adeac7 --- /dev/null +++ b/templates/src/main/resources/templates/cv/slate-orange/icons/crm.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/cv/slate-orange/icons/customer-service.svg b/templates/src/main/resources/templates/cv/slate-orange/icons/customer-service.svg new file mode 100644 index 000000000..a5f199ad3 --- /dev/null +++ b/templates/src/main/resources/templates/cv/slate-orange/icons/customer-service.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/cv/slate-orange/icons/email.svg b/templates/src/main/resources/templates/cv/slate-orange/icons/email.svg new file mode 100644 index 000000000..cec0bf4aa --- /dev/null +++ b/templates/src/main/resources/templates/cv/slate-orange/icons/email.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/cv/slate-orange/icons/linkedin.svg b/templates/src/main/resources/templates/cv/slate-orange/icons/linkedin.svg new file mode 100644 index 000000000..c8b287ae2 --- /dev/null +++ b/templates/src/main/resources/templates/cv/slate-orange/icons/linkedin.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/cv/slate-orange/icons/location.svg b/templates/src/main/resources/templates/cv/slate-orange/icons/location.svg new file mode 100644 index 000000000..d7ac490e4 --- /dev/null +++ b/templates/src/main/resources/templates/cv/slate-orange/icons/location.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/cv/slate-orange/icons/office-suite.svg b/templates/src/main/resources/templates/cv/slate-orange/icons/office-suite.svg new file mode 100644 index 000000000..eaf930ab5 --- /dev/null +++ b/templates/src/main/resources/templates/cv/slate-orange/icons/office-suite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/cv/slate-orange/icons/operations.svg b/templates/src/main/resources/templates/cv/slate-orange/icons/operations.svg new file mode 100644 index 000000000..435b26489 --- /dev/null +++ b/templates/src/main/resources/templates/cv/slate-orange/icons/operations.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/cv/slate-orange/icons/phone.svg b/templates/src/main/resources/templates/cv/slate-orange/icons/phone.svg new file mode 100644 index 000000000..4628e2568 --- /dev/null +++ b/templates/src/main/resources/templates/cv/slate-orange/icons/phone.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/cv/slate-orange/icons/problem-solving.svg b/templates/src/main/resources/templates/cv/slate-orange/icons/problem-solving.svg new file mode 100644 index 000000000..a430a1b79 --- /dev/null +++ b/templates/src/main/resources/templates/cv/slate-orange/icons/problem-solving.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/cv/slate-orange/icons/relocation.svg b/templates/src/main/resources/templates/cv/slate-orange/icons/relocation.svg new file mode 100644 index 000000000..e5d78cbeb --- /dev/null +++ b/templates/src/main/resources/templates/cv/slate-orange/icons/relocation.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/cv/slate-orange/icons/remote-work.svg b/templates/src/main/resources/templates/cv/slate-orange/icons/remote-work.svg new file mode 100644 index 000000000..157c50180 --- /dev/null +++ b/templates/src/main/resources/templates/cv/slate-orange/icons/remote-work.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/cv/slate-orange/icons/reporting.svg b/templates/src/main/resources/templates/cv/slate-orange/icons/reporting.svg new file mode 100644 index 000000000..1e0f0ce1d --- /dev/null +++ b/templates/src/main/resources/templates/cv/slate-orange/icons/reporting.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/cv/slate-orange/icons/sales.svg b/templates/src/main/resources/templates/cv/slate-orange/icons/sales.svg new file mode 100644 index 000000000..cc4c5a563 --- /dev/null +++ b/templates/src/main/resources/templates/cv/slate-orange/icons/sales.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/cv/slate-orange/icons/time-management.svg b/templates/src/main/resources/templates/cv/slate-orange/icons/time-management.svg new file mode 100644 index 000000000..4f051b9b1 --- /dev/null +++ b/templates/src/main/resources/templates/cv/slate-orange/icons/time-management.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/cv/teal-pulse/icons/additional.svg b/templates/src/main/resources/templates/cv/teal-pulse/icons/additional.svg new file mode 100644 index 000000000..50719fb48 --- /dev/null +++ b/templates/src/main/resources/templates/cv/teal-pulse/icons/additional.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/cv/teal-pulse/icons/brand-heart.svg b/templates/src/main/resources/templates/cv/teal-pulse/icons/brand-heart.svg new file mode 100644 index 000000000..e678eff0f --- /dev/null +++ b/templates/src/main/resources/templates/cv/teal-pulse/icons/brand-heart.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/cv/teal-pulse/icons/brand-pulse.svg b/templates/src/main/resources/templates/cv/teal-pulse/icons/brand-pulse.svg new file mode 100644 index 000000000..b02fd7af5 --- /dev/null +++ b/templates/src/main/resources/templates/cv/teal-pulse/icons/brand-pulse.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/cv/teal-pulse/icons/certifications.svg b/templates/src/main/resources/templates/cv/teal-pulse/icons/certifications.svg new file mode 100644 index 000000000..972d65546 --- /dev/null +++ b/templates/src/main/resources/templates/cv/teal-pulse/icons/certifications.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/cv/teal-pulse/icons/education.svg b/templates/src/main/resources/templates/cv/teal-pulse/icons/education.svg new file mode 100644 index 000000000..66eb8e377 --- /dev/null +++ b/templates/src/main/resources/templates/cv/teal-pulse/icons/education.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/cv/teal-pulse/icons/email.svg b/templates/src/main/resources/templates/cv/teal-pulse/icons/email.svg new file mode 100644 index 000000000..291f1c368 --- /dev/null +++ b/templates/src/main/resources/templates/cv/teal-pulse/icons/email.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/cv/teal-pulse/icons/experience.svg b/templates/src/main/resources/templates/cv/teal-pulse/icons/experience.svg new file mode 100644 index 000000000..d474a2838 --- /dev/null +++ b/templates/src/main/resources/templates/cv/teal-pulse/icons/experience.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/cv/teal-pulse/icons/heart.svg b/templates/src/main/resources/templates/cv/teal-pulse/icons/heart.svg new file mode 100644 index 000000000..20d5f5076 --- /dev/null +++ b/templates/src/main/resources/templates/cv/teal-pulse/icons/heart.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/cv/teal-pulse/icons/linkedin.svg b/templates/src/main/resources/templates/cv/teal-pulse/icons/linkedin.svg new file mode 100644 index 000000000..3ed716e9e --- /dev/null +++ b/templates/src/main/resources/templates/cv/teal-pulse/icons/linkedin.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/cv/teal-pulse/icons/location.svg b/templates/src/main/resources/templates/cv/teal-pulse/icons/location.svg new file mode 100644 index 000000000..876c7101e --- /dev/null +++ b/templates/src/main/resources/templates/cv/teal-pulse/icons/location.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/cv/teal-pulse/icons/phone.svg b/templates/src/main/resources/templates/cv/teal-pulse/icons/phone.svg new file mode 100644 index 000000000..bfab7b00d --- /dev/null +++ b/templates/src/main/resources/templates/cv/teal-pulse/icons/phone.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/cv/teal-pulse/icons/summary.svg b/templates/src/main/resources/templates/cv/teal-pulse/icons/summary.svg new file mode 100644 index 000000000..173514856 --- /dev/null +++ b/templates/src/main/resources/templates/cv/teal-pulse/icons/summary.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/cv/terracotta-rail/icons/badge.svg b/templates/src/main/resources/templates/cv/terracotta-rail/icons/badge.svg new file mode 100644 index 000000000..0d35d8d18 --- /dev/null +++ b/templates/src/main/resources/templates/cv/terracotta-rail/icons/badge.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/cv/terracotta-rail/icons/building.svg b/templates/src/main/resources/templates/cv/terracotta-rail/icons/building.svg new file mode 100644 index 000000000..9a6de887c --- /dev/null +++ b/templates/src/main/resources/templates/cv/terracotta-rail/icons/building.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/cv/terracotta-rail/icons/clock.svg b/templates/src/main/resources/templates/cv/terracotta-rail/icons/clock.svg new file mode 100644 index 000000000..f06099851 --- /dev/null +++ b/templates/src/main/resources/templates/cv/terracotta-rail/icons/clock.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/cv/terracotta-rail/icons/email.svg b/templates/src/main/resources/templates/cv/terracotta-rail/icons/email.svg new file mode 100644 index 000000000..817215132 --- /dev/null +++ b/templates/src/main/resources/templates/cv/terracotta-rail/icons/email.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/cv/terracotta-rail/icons/globe.svg b/templates/src/main/resources/templates/cv/terracotta-rail/icons/globe.svg new file mode 100644 index 000000000..4a6528580 --- /dev/null +++ b/templates/src/main/resources/templates/cv/terracotta-rail/icons/globe.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/cv/terracotta-rail/icons/hotel.svg b/templates/src/main/resources/templates/cv/terracotta-rail/icons/hotel.svg new file mode 100644 index 000000000..13fb4cd92 --- /dev/null +++ b/templates/src/main/resources/templates/cv/terracotta-rail/icons/hotel.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/cv/terracotta-rail/icons/house.svg b/templates/src/main/resources/templates/cv/terracotta-rail/icons/house.svg new file mode 100644 index 000000000..9c1299801 --- /dev/null +++ b/templates/src/main/resources/templates/cv/terracotta-rail/icons/house.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/cv/terracotta-rail/icons/linkedin.svg b/templates/src/main/resources/templates/cv/terracotta-rail/icons/linkedin.svg new file mode 100644 index 000000000..d955b83d5 --- /dev/null +++ b/templates/src/main/resources/templates/cv/terracotta-rail/icons/linkedin.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/cv/terracotta-rail/icons/location.svg b/templates/src/main/resources/templates/cv/terracotta-rail/icons/location.svg new file mode 100644 index 000000000..6564329cc --- /dev/null +++ b/templates/src/main/resources/templates/cv/terracotta-rail/icons/location.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/cv/terracotta-rail/icons/phone.svg b/templates/src/main/resources/templates/cv/terracotta-rail/icons/phone.svg new file mode 100644 index 000000000..b8c11ac2d --- /dev/null +++ b/templates/src/main/resources/templates/cv/terracotta-rail/icons/phone.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/cv/terracotta-rail/icons/square.svg b/templates/src/main/resources/templates/cv/terracotta-rail/icons/square.svg new file mode 100644 index 000000000..ee5989d1e --- /dev/null +++ b/templates/src/main/resources/templates/cv/terracotta-rail/icons/square.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/cv/violet-grid/icons/design-systems.svg b/templates/src/main/resources/templates/cv/violet-grid/icons/design-systems.svg new file mode 100644 index 000000000..abccd250f --- /dev/null +++ b/templates/src/main/resources/templates/cv/violet-grid/icons/design-systems.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/cv/violet-grid/icons/email.svg b/templates/src/main/resources/templates/cv/violet-grid/icons/email.svg new file mode 100644 index 000000000..24628a6eb --- /dev/null +++ b/templates/src/main/resources/templates/cv/violet-grid/icons/email.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/cv/violet-grid/icons/graduation.svg b/templates/src/main/resources/templates/cv/violet-grid/icons/graduation.svg new file mode 100644 index 000000000..f89ae1c16 --- /dev/null +++ b/templates/src/main/resources/templates/cv/violet-grid/icons/graduation.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/cv/violet-grid/icons/information-architecture.svg b/templates/src/main/resources/templates/cv/violet-grid/icons/information-architecture.svg new file mode 100644 index 000000000..ebadcc790 --- /dev/null +++ b/templates/src/main/resources/templates/cv/violet-grid/icons/information-architecture.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/cv/violet-grid/icons/linkedin.svg b/templates/src/main/resources/templates/cv/violet-grid/icons/linkedin.svg new file mode 100644 index 000000000..ab2d1bd8e --- /dev/null +++ b/templates/src/main/resources/templates/cv/violet-grid/icons/linkedin.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/cv/violet-grid/icons/location.svg b/templates/src/main/resources/templates/cv/violet-grid/icons/location.svg new file mode 100644 index 000000000..fde000d03 --- /dev/null +++ b/templates/src/main/resources/templates/cv/violet-grid/icons/location.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/cv/violet-grid/icons/phone.svg b/templates/src/main/resources/templates/cv/violet-grid/icons/phone.svg new file mode 100644 index 000000000..7c823dae3 --- /dev/null +++ b/templates/src/main/resources/templates/cv/violet-grid/icons/phone.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/cv/violet-grid/icons/project-health.svg b/templates/src/main/resources/templates/cv/violet-grid/icons/project-health.svg new file mode 100644 index 000000000..d42590cf3 --- /dev/null +++ b/templates/src/main/resources/templates/cv/violet-grid/icons/project-health.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/cv/violet-grid/icons/project-wallet.svg b/templates/src/main/resources/templates/cv/violet-grid/icons/project-wallet.svg new file mode 100644 index 000000000..70bd4b1c6 --- /dev/null +++ b/templates/src/main/resources/templates/cv/violet-grid/icons/project-wallet.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/cv/violet-grid/icons/prototyping.svg b/templates/src/main/resources/templates/cv/violet-grid/icons/prototyping.svg new file mode 100644 index 000000000..6dc414529 --- /dev/null +++ b/templates/src/main/resources/templates/cv/violet-grid/icons/prototyping.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/cv/violet-grid/icons/quote.svg b/templates/src/main/resources/templates/cv/violet-grid/icons/quote.svg new file mode 100644 index 000000000..281a38b10 --- /dev/null +++ b/templates/src/main/resources/templates/cv/violet-grid/icons/quote.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/cv/violet-grid/icons/usability-testing.svg b/templates/src/main/resources/templates/cv/violet-grid/icons/usability-testing.svg new file mode 100644 index 000000000..b2857b690 --- /dev/null +++ b/templates/src/main/resources/templates/cv/violet-grid/icons/usability-testing.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/cv/violet-grid/icons/ux-research.svg b/templates/src/main/resources/templates/cv/violet-grid/icons/ux-research.svg new file mode 100644 index 000000000..31f42319d --- /dev/null +++ b/templates/src/main/resources/templates/cv/violet-grid/icons/ux-research.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/cv/violet-grid/icons/website.svg b/templates/src/main/resources/templates/cv/violet-grid/icons/website.svg new file mode 100644 index 000000000..faddf384b --- /dev/null +++ b/templates/src/main/resources/templates/cv/violet-grid/icons/website.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/cv/violet-grid/icons/wireframing.svg b/templates/src/main/resources/templates/cv/violet-grid/icons/wireframing.svg new file mode 100644 index 000000000..0273be191 --- /dev/null +++ b/templates/src/main/resources/templates/cv/violet-grid/icons/wireframing.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/invoice/consulting/icons/bank.png b/templates/src/main/resources/templates/invoice/consulting/icons/bank.png new file mode 100644 index 000000000..0abf6a0a9 Binary files /dev/null and b/templates/src/main/resources/templates/invoice/consulting/icons/bank.png differ diff --git a/templates/src/main/resources/templates/invoice/consulting/icons/email.png b/templates/src/main/resources/templates/invoice/consulting/icons/email.png new file mode 100644 index 000000000..7ba93e4c7 Binary files /dev/null and b/templates/src/main/resources/templates/invoice/consulting/icons/email.png differ diff --git a/templates/src/main/resources/templates/invoice/consulting/icons/location.png b/templates/src/main/resources/templates/invoice/consulting/icons/location.png new file mode 100644 index 000000000..23be9cdad Binary files /dev/null and b/templates/src/main/resources/templates/invoice/consulting/icons/location.png differ diff --git a/templates/src/main/resources/templates/invoice/consulting/icons/payment-calendar.png b/templates/src/main/resources/templates/invoice/consulting/icons/payment-calendar.png new file mode 100644 index 000000000..cf8d11a51 Binary files /dev/null and b/templates/src/main/resources/templates/invoice/consulting/icons/payment-calendar.png differ diff --git a/templates/src/main/resources/templates/invoice/consulting/icons/phone.png b/templates/src/main/resources/templates/invoice/consulting/icons/phone.png new file mode 100644 index 000000000..3f55069b7 Binary files /dev/null and b/templates/src/main/resources/templates/invoice/consulting/icons/phone.png differ diff --git a/templates/src/main/resources/templates/invoice/consulting/icons/website.png b/templates/src/main/resources/templates/invoice/consulting/icons/website.png new file mode 100644 index 000000000..1605a03ab Binary files /dev/null and b/templates/src/main/resources/templates/invoice/consulting/icons/website.png differ diff --git a/templates/src/main/resources/templates/invoice/luma-studio/icons/bank.svg b/templates/src/main/resources/templates/invoice/luma-studio/icons/bank.svg new file mode 100644 index 000000000..ea87a64fb --- /dev/null +++ b/templates/src/main/resources/templates/invoice/luma-studio/icons/bank.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/invoice/luma-studio/icons/email.svg b/templates/src/main/resources/templates/invoice/luma-studio/icons/email.svg new file mode 100644 index 000000000..13dc2974c --- /dev/null +++ b/templates/src/main/resources/templates/invoice/luma-studio/icons/email.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/invoice/luma-studio/icons/heart.svg b/templates/src/main/resources/templates/invoice/luma-studio/icons/heart.svg new file mode 100644 index 000000000..24d90b97a --- /dev/null +++ b/templates/src/main/resources/templates/invoice/luma-studio/icons/heart.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/invoice/luma-studio/icons/notes.svg b/templates/src/main/resources/templates/invoice/luma-studio/icons/notes.svg new file mode 100644 index 000000000..a91fe98cf --- /dev/null +++ b/templates/src/main/resources/templates/invoice/luma-studio/icons/notes.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/invoice/luma-studio/icons/phone.svg b/templates/src/main/resources/templates/invoice/luma-studio/icons/phone.svg new file mode 100644 index 000000000..b2a8bfc1b --- /dev/null +++ b/templates/src/main/resources/templates/invoice/luma-studio/icons/phone.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/invoice/luma-studio/icons/sprig.svg b/templates/src/main/resources/templates/invoice/luma-studio/icons/sprig.svg new file mode 100644 index 000000000..ea5849182 --- /dev/null +++ b/templates/src/main/resources/templates/invoice/luma-studio/icons/sprig.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/invoice/luma-studio/icons/website.svg b/templates/src/main/resources/templates/invoice/luma-studio/icons/website.svg new file mode 100644 index 000000000..d2929ac57 --- /dev/null +++ b/templates/src/main/resources/templates/invoice/luma-studio/icons/website.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/invoice/merchant/icons/bag.svg b/templates/src/main/resources/templates/invoice/merchant/icons/bag.svg new file mode 100644 index 000000000..67a16b0d8 --- /dev/null +++ b/templates/src/main/resources/templates/invoice/merchant/icons/bag.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/invoice/merchant/icons/bank.svg b/templates/src/main/resources/templates/invoice/merchant/icons/bank.svg new file mode 100644 index 000000000..09e80e610 --- /dev/null +++ b/templates/src/main/resources/templates/invoice/merchant/icons/bank.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/invoice/merchant/icons/bill-to.svg b/templates/src/main/resources/templates/invoice/merchant/icons/bill-to.svg new file mode 100644 index 000000000..a91fe98cf --- /dev/null +++ b/templates/src/main/resources/templates/invoice/merchant/icons/bill-to.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/invoice/merchant/icons/calendar.svg b/templates/src/main/resources/templates/invoice/merchant/icons/calendar.svg new file mode 100644 index 000000000..020559961 --- /dev/null +++ b/templates/src/main/resources/templates/invoice/merchant/icons/calendar.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/invoice/merchant/icons/email.svg b/templates/src/main/resources/templates/invoice/merchant/icons/email.svg new file mode 100644 index 000000000..8bd3199e2 --- /dev/null +++ b/templates/src/main/resources/templates/invoice/merchant/icons/email.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/invoice/merchant/icons/globe.svg b/templates/src/main/resources/templates/invoice/merchant/icons/globe.svg new file mode 100644 index 000000000..00462900f --- /dev/null +++ b/templates/src/main/resources/templates/invoice/merchant/icons/globe.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/invoice/merchant/icons/info.svg b/templates/src/main/resources/templates/invoice/merchant/icons/info.svg new file mode 100644 index 000000000..78858ccc0 --- /dev/null +++ b/templates/src/main/resources/templates/invoice/merchant/icons/info.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/invoice/merchant/icons/phone.svg b/templates/src/main/resources/templates/invoice/merchant/icons/phone.svg new file mode 100644 index 000000000..0c0e13e1f --- /dev/null +++ b/templates/src/main/resources/templates/invoice/merchant/icons/phone.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/invoice/merchant/icons/ship-to.svg b/templates/src/main/resources/templates/invoice/merchant/icons/ship-to.svg new file mode 100644 index 000000000..924aa3b48 --- /dev/null +++ b/templates/src/main/resources/templates/invoice/merchant/icons/ship-to.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/invoice/merchant/icons/support.svg b/templates/src/main/resources/templates/invoice/merchant/icons/support.svg new file mode 100644 index 000000000..77108f358 --- /dev/null +++ b/templates/src/main/resources/templates/invoice/merchant/icons/support.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/invoice/merchant/icons/tag.svg b/templates/src/main/resources/templates/invoice/merchant/icons/tag.svg new file mode 100644 index 000000000..9d11afe54 --- /dev/null +++ b/templates/src/main/resources/templates/invoice/merchant/icons/tag.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/invoice/merchant/icons/website.svg b/templates/src/main/resources/templates/invoice/merchant/icons/website.svg new file mode 100644 index 000000000..0c727ede9 --- /dev/null +++ b/templates/src/main/resources/templates/invoice/merchant/icons/website.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/invoice/metered/icons/bill-to.svg b/templates/src/main/resources/templates/invoice/metered/icons/bill-to.svg new file mode 100644 index 000000000..78b3cecb4 --- /dev/null +++ b/templates/src/main/resources/templates/invoice/metered/icons/bill-to.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/invoice/metered/icons/calendar.svg b/templates/src/main/resources/templates/invoice/metered/icons/calendar.svg new file mode 100644 index 000000000..7610d234a --- /dev/null +++ b/templates/src/main/resources/templates/invoice/metered/icons/calendar.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/invoice/metered/icons/card.svg b/templates/src/main/resources/templates/invoice/metered/icons/card.svg new file mode 100644 index 000000000..d4b504c64 --- /dev/null +++ b/templates/src/main/resources/templates/invoice/metered/icons/card.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/invoice/metered/icons/compute.svg b/templates/src/main/resources/templates/invoice/metered/icons/compute.svg new file mode 100644 index 000000000..0c9288aa3 --- /dev/null +++ b/templates/src/main/resources/templates/invoice/metered/icons/compute.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/invoice/metered/icons/database.svg b/templates/src/main/resources/templates/invoice/metered/icons/database.svg new file mode 100644 index 000000000..ce150f4c9 --- /dev/null +++ b/templates/src/main/resources/templates/invoice/metered/icons/database.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/invoice/metered/icons/info.svg b/templates/src/main/resources/templates/invoice/metered/icons/info.svg new file mode 100644 index 000000000..6e89f8dd3 --- /dev/null +++ b/templates/src/main/resources/templates/invoice/metered/icons/info.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/invoice/metered/icons/ship-to.svg b/templates/src/main/resources/templates/invoice/metered/icons/ship-to.svg new file mode 100644 index 000000000..03c9ea081 --- /dev/null +++ b/templates/src/main/resources/templates/invoice/metered/icons/ship-to.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/invoice/metered/icons/storage.svg b/templates/src/main/resources/templates/invoice/metered/icons/storage.svg new file mode 100644 index 000000000..5d2940770 --- /dev/null +++ b/templates/src/main/resources/templates/invoice/metered/icons/storage.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/invoice/metered/icons/support.svg b/templates/src/main/resources/templates/invoice/metered/icons/support.svg new file mode 100644 index 000000000..99f6d84f5 --- /dev/null +++ b/templates/src/main/resources/templates/invoice/metered/icons/support.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/invoice/metered/icons/transfer.svg b/templates/src/main/resources/templates/invoice/metered/icons/transfer.svg new file mode 100644 index 000000000..980b6eedd --- /dev/null +++ b/templates/src/main/resources/templates/invoice/metered/icons/transfer.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/invoice/obsidian/icons/notes.svg b/templates/src/main/resources/templates/invoice/obsidian/icons/notes.svg new file mode 100644 index 000000000..bb98a3fd9 --- /dev/null +++ b/templates/src/main/resources/templates/invoice/obsidian/icons/notes.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/invoice/obsidian/icons/payment.svg b/templates/src/main/resources/templates/invoice/obsidian/icons/payment.svg new file mode 100644 index 000000000..f5c17f96b --- /dev/null +++ b/templates/src/main/resources/templates/invoice/obsidian/icons/payment.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/invoice/payments/icons/bank.svg b/templates/src/main/resources/templates/invoice/payments/icons/bank.svg new file mode 100644 index 000000000..0058b58a9 --- /dev/null +++ b/templates/src/main/resources/templates/invoice/payments/icons/bank.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/invoice/payments/icons/bill-to.svg b/templates/src/main/resources/templates/invoice/payments/icons/bill-to.svg new file mode 100644 index 000000000..0aaae9c3f --- /dev/null +++ b/templates/src/main/resources/templates/invoice/payments/icons/bill-to.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/invoice/payments/icons/calendar.svg b/templates/src/main/resources/templates/invoice/payments/icons/calendar.svg new file mode 100644 index 000000000..d1952bcc7 --- /dev/null +++ b/templates/src/main/resources/templates/invoice/payments/icons/calendar.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/invoice/payments/icons/card-settings.svg b/templates/src/main/resources/templates/invoice/payments/icons/card-settings.svg new file mode 100644 index 000000000..a10a3b2c0 --- /dev/null +++ b/templates/src/main/resources/templates/invoice/payments/icons/card-settings.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/invoice/payments/icons/card.svg b/templates/src/main/resources/templates/invoice/payments/icons/card.svg new file mode 100644 index 000000000..8a9fe32ed --- /dev/null +++ b/templates/src/main/resources/templates/invoice/payments/icons/card.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/invoice/payments/icons/document.svg b/templates/src/main/resources/templates/invoice/payments/icons/document.svg new file mode 100644 index 000000000..5aec8edf5 --- /dev/null +++ b/templates/src/main/resources/templates/invoice/payments/icons/document.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/invoice/payments/icons/globe.svg b/templates/src/main/resources/templates/invoice/payments/icons/globe.svg new file mode 100644 index 000000000..323edcf15 --- /dev/null +++ b/templates/src/main/resources/templates/invoice/payments/icons/globe.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/invoice/payments/icons/headset.svg b/templates/src/main/resources/templates/invoice/payments/icons/headset.svg new file mode 100644 index 000000000..3031d0940 --- /dev/null +++ b/templates/src/main/resources/templates/invoice/payments/icons/headset.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/invoice/payments/icons/mobile.svg b/templates/src/main/resources/templates/invoice/payments/icons/mobile.svg new file mode 100644 index 000000000..4e5915f59 --- /dev/null +++ b/templates/src/main/resources/templates/invoice/payments/icons/mobile.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/invoice/payments/icons/shield.svg b/templates/src/main/resources/templates/invoice/payments/icons/shield.svg new file mode 100644 index 000000000..b08b7e7a9 --- /dev/null +++ b/templates/src/main/resources/templates/invoice/payments/icons/shield.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/invoice/payments/icons/ship-to.svg b/templates/src/main/resources/templates/invoice/payments/icons/ship-to.svg new file mode 100644 index 000000000..03efd2212 --- /dev/null +++ b/templates/src/main/resources/templates/invoice/payments/icons/ship-to.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/invoice/payments/icons/support.svg b/templates/src/main/resources/templates/invoice/payments/icons/support.svg new file mode 100644 index 000000000..ef8b01748 --- /dev/null +++ b/templates/src/main/resources/templates/invoice/payments/icons/support.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/invoice/platform/icons/bank.svg b/templates/src/main/resources/templates/invoice/platform/icons/bank.svg new file mode 100644 index 000000000..308bc13d0 --- /dev/null +++ b/templates/src/main/resources/templates/invoice/platform/icons/bank.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/invoice/platform/icons/bill-to.svg b/templates/src/main/resources/templates/invoice/platform/icons/bill-to.svg new file mode 100644 index 000000000..98e94c3ac --- /dev/null +++ b/templates/src/main/resources/templates/invoice/platform/icons/bill-to.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/invoice/platform/icons/calendar.svg b/templates/src/main/resources/templates/invoice/platform/icons/calendar.svg new file mode 100644 index 000000000..ec36dc3f6 --- /dev/null +++ b/templates/src/main/resources/templates/invoice/platform/icons/calendar.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/invoice/platform/icons/compute.svg b/templates/src/main/resources/templates/invoice/platform/icons/compute.svg new file mode 100644 index 000000000..75fcdd873 --- /dev/null +++ b/templates/src/main/resources/templates/invoice/platform/icons/compute.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/invoice/platform/icons/database.svg b/templates/src/main/resources/templates/invoice/platform/icons/database.svg new file mode 100644 index 000000000..b382c996d --- /dev/null +++ b/templates/src/main/resources/templates/invoice/platform/icons/database.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/invoice/platform/icons/email.svg b/templates/src/main/resources/templates/invoice/platform/icons/email.svg new file mode 100644 index 000000000..e25560c04 --- /dev/null +++ b/templates/src/main/resources/templates/invoice/platform/icons/email.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/invoice/platform/icons/info-outline.svg b/templates/src/main/resources/templates/invoice/platform/icons/info-outline.svg new file mode 100644 index 000000000..8ebf8147b --- /dev/null +++ b/templates/src/main/resources/templates/invoice/platform/icons/info-outline.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/invoice/platform/icons/info.svg b/templates/src/main/resources/templates/invoice/platform/icons/info.svg new file mode 100644 index 000000000..8ebf8147b --- /dev/null +++ b/templates/src/main/resources/templates/invoice/platform/icons/info.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/invoice/platform/icons/network.svg b/templates/src/main/resources/templates/invoice/platform/icons/network.svg new file mode 100644 index 000000000..5bffd238c --- /dev/null +++ b/templates/src/main/resources/templates/invoice/platform/icons/network.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/invoice/platform/icons/phone.svg b/templates/src/main/resources/templates/invoice/platform/icons/phone.svg new file mode 100644 index 000000000..6cb9b3630 --- /dev/null +++ b/templates/src/main/resources/templates/invoice/platform/icons/phone.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/invoice/platform/icons/ship-to.svg b/templates/src/main/resources/templates/invoice/platform/icons/ship-to.svg new file mode 100644 index 000000000..c2abefab2 --- /dev/null +++ b/templates/src/main/resources/templates/invoice/platform/icons/ship-to.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/invoice/platform/icons/storage.svg b/templates/src/main/resources/templates/invoice/platform/icons/storage.svg new file mode 100644 index 000000000..8779098c4 --- /dev/null +++ b/templates/src/main/resources/templates/invoice/platform/icons/storage.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/invoice/platform/icons/support.svg b/templates/src/main/resources/templates/invoice/platform/icons/support.svg new file mode 100644 index 000000000..04679786f --- /dev/null +++ b/templates/src/main/resources/templates/invoice/platform/icons/support.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/invoice/platform/icons/website.svg b/templates/src/main/resources/templates/invoice/platform/icons/website.svg new file mode 100644 index 000000000..b7c38fe5d --- /dev/null +++ b/templates/src/main/resources/templates/invoice/platform/icons/website.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/invoice/subscription/icons/bank.svg b/templates/src/main/resources/templates/invoice/subscription/icons/bank.svg new file mode 100644 index 000000000..bd29cf79b --- /dev/null +++ b/templates/src/main/resources/templates/invoice/subscription/icons/bank.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/invoice/subscription/icons/beneficiary.svg b/templates/src/main/resources/templates/invoice/subscription/icons/beneficiary.svg new file mode 100644 index 000000000..996b9ad7e --- /dev/null +++ b/templates/src/main/resources/templates/invoice/subscription/icons/beneficiary.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/invoice/subscription/icons/card.svg b/templates/src/main/resources/templates/invoice/subscription/icons/card.svg new file mode 100644 index 000000000..cb11e906e --- /dev/null +++ b/templates/src/main/resources/templates/invoice/subscription/icons/card.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/invoice/subscription/icons/reference.svg b/templates/src/main/resources/templates/invoice/subscription/icons/reference.svg new file mode 100644 index 000000000..6e21e0e50 --- /dev/null +++ b/templates/src/main/resources/templates/invoice/subscription/icons/reference.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/invoice/subscription/icons/shield.svg b/templates/src/main/resources/templates/invoice/subscription/icons/shield.svg new file mode 100644 index 000000000..839647a90 --- /dev/null +++ b/templates/src/main/resources/templates/invoice/subscription/icons/shield.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/invoice/subscription/icons/transfer.svg b/templates/src/main/resources/templates/invoice/subscription/icons/transfer.svg new file mode 100644 index 000000000..7b4ed303d --- /dev/null +++ b/templates/src/main/resources/templates/invoice/subscription/icons/transfer.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/invoice/workspace/icons/bank.svg b/templates/src/main/resources/templates/invoice/workspace/icons/bank.svg new file mode 100644 index 000000000..fbbc8fae9 --- /dev/null +++ b/templates/src/main/resources/templates/invoice/workspace/icons/bank.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/invoice/workspace/icons/bill-to.svg b/templates/src/main/resources/templates/invoice/workspace/icons/bill-to.svg new file mode 100644 index 000000000..a91fe98cf --- /dev/null +++ b/templates/src/main/resources/templates/invoice/workspace/icons/bill-to.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/invoice/workspace/icons/calendar.svg b/templates/src/main/resources/templates/invoice/workspace/icons/calendar.svg new file mode 100644 index 000000000..3233ebbb5 --- /dev/null +++ b/templates/src/main/resources/templates/invoice/workspace/icons/calendar.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/invoice/workspace/icons/grid.svg b/templates/src/main/resources/templates/invoice/workspace/icons/grid.svg new file mode 100644 index 000000000..1e92b92da --- /dev/null +++ b/templates/src/main/resources/templates/invoice/workspace/icons/grid.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/invoice/workspace/icons/info.svg b/templates/src/main/resources/templates/invoice/workspace/icons/info.svg new file mode 100644 index 000000000..b88a167fa --- /dev/null +++ b/templates/src/main/resources/templates/invoice/workspace/icons/info.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/invoice/workspace/icons/search.svg b/templates/src/main/resources/templates/invoice/workspace/icons/search.svg new file mode 100644 index 000000000..d685eadd9 --- /dev/null +++ b/templates/src/main/resources/templates/invoice/workspace/icons/search.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/invoice/workspace/icons/shield.svg b/templates/src/main/resources/templates/invoice/workspace/icons/shield.svg new file mode 100644 index 000000000..878df61a1 --- /dev/null +++ b/templates/src/main/resources/templates/invoice/workspace/icons/shield.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/invoice/workspace/icons/ship-to.svg b/templates/src/main/resources/templates/invoice/workspace/icons/ship-to.svg new file mode 100644 index 000000000..924aa3b48 --- /dev/null +++ b/templates/src/main/resources/templates/invoice/workspace/icons/ship-to.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/proposal/editorial/icons/fact-contact.svg b/templates/src/main/resources/templates/proposal/editorial/icons/fact-contact.svg new file mode 100644 index 000000000..301914753 --- /dev/null +++ b/templates/src/main/resources/templates/proposal/editorial/icons/fact-contact.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/proposal/editorial/icons/fact-duration.svg b/templates/src/main/resources/templates/proposal/editorial/icons/fact-duration.svg new file mode 100644 index 000000000..f435148fc --- /dev/null +++ b/templates/src/main/resources/templates/proposal/editorial/icons/fact-duration.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/proposal/editorial/icons/fact-start.svg b/templates/src/main/resources/templates/proposal/editorial/icons/fact-start.svg new file mode 100644 index 000000000..f82fe3be2 --- /dev/null +++ b/templates/src/main/resources/templates/proposal/editorial/icons/fact-start.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/proposal/editorial/icons/fact-validity.svg b/templates/src/main/resources/templates/proposal/editorial/icons/fact-validity.svg new file mode 100644 index 000000000..75d81c791 --- /dev/null +++ b/templates/src/main/resources/templates/proposal/editorial/icons/fact-validity.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/proposal/editorial/icons/goal-check.svg b/templates/src/main/resources/templates/proposal/editorial/icons/goal-check.svg new file mode 100644 index 000000000..159ab3553 --- /dev/null +++ b/templates/src/main/resources/templates/proposal/editorial/icons/goal-check.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/proposal/indigo/icons/apis.svg b/templates/src/main/resources/templates/proposal/indigo/icons/apis.svg new file mode 100644 index 000000000..2851e9b6a --- /dev/null +++ b/templates/src/main/resources/templates/proposal/indigo/icons/apis.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/proposal/indigo/icons/date.svg b/templates/src/main/resources/templates/proposal/indigo/icons/date.svg new file mode 100644 index 000000000..36b979ac0 --- /dev/null +++ b/templates/src/main/resources/templates/proposal/indigo/icons/date.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/proposal/indigo/icons/global.svg b/templates/src/main/resources/templates/proposal/indigo/icons/global.svg new file mode 100644 index 000000000..6cd57802e --- /dev/null +++ b/templates/src/main/resources/templates/proposal/indigo/icons/global.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/proposal/indigo/icons/identifier.svg b/templates/src/main/resources/templates/proposal/indigo/icons/identifier.svg new file mode 100644 index 000000000..c80ddb764 --- /dev/null +++ b/templates/src/main/resources/templates/proposal/indigo/icons/identifier.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/proposal/indigo/icons/infrastructure.svg b/templates/src/main/resources/templates/proposal/indigo/icons/infrastructure.svg new file mode 100644 index 000000000..3ec227c9b --- /dev/null +++ b/templates/src/main/resources/templates/proposal/indigo/icons/infrastructure.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/proposal/indigo/icons/prepared-by.svg b/templates/src/main/resources/templates/proposal/indigo/icons/prepared-by.svg new file mode 100644 index 000000000..e2edb44b9 --- /dev/null +++ b/templates/src/main/resources/templates/proposal/indigo/icons/prepared-by.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/proposal/indigo/icons/secure.svg b/templates/src/main/resources/templates/proposal/indigo/icons/secure.svg new file mode 100644 index 000000000..8af44e21c --- /dev/null +++ b/templates/src/main/resources/templates/proposal/indigo/icons/secure.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/proposal/indigo/icons/valid.svg b/templates/src/main/resources/templates/proposal/indigo/icons/valid.svg new file mode 100644 index 000000000..998a38ca4 --- /dev/null +++ b/templates/src/main/resources/templates/proposal/indigo/icons/valid.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/proposal/northline/icons/badge-acceptance.png b/templates/src/main/resources/templates/proposal/northline/icons/badge-acceptance.png new file mode 100644 index 000000000..2c7eca12b Binary files /dev/null and b/templates/src/main/resources/templates/proposal/northline/icons/badge-acceptance.png differ diff --git a/templates/src/main/resources/templates/proposal/northline/icons/badge-deliverables.png b/templates/src/main/resources/templates/proposal/northline/icons/badge-deliverables.png new file mode 100644 index 000000000..ffe3a6432 Binary files /dev/null and b/templates/src/main/resources/templates/proposal/northline/icons/badge-deliverables.png differ diff --git a/templates/src/main/resources/templates/proposal/northline/icons/badge-goals.png b/templates/src/main/resources/templates/proposal/northline/icons/badge-goals.png new file mode 100644 index 000000000..94fa8267e Binary files /dev/null and b/templates/src/main/resources/templates/proposal/northline/icons/badge-goals.png differ diff --git a/templates/src/main/resources/templates/proposal/northline/icons/badge-investment.png b/templates/src/main/resources/templates/proposal/northline/icons/badge-investment.png new file mode 100644 index 000000000..8d422c44d Binary files /dev/null and b/templates/src/main/resources/templates/proposal/northline/icons/badge-investment.png differ diff --git a/templates/src/main/resources/templates/proposal/northline/icons/badge-scope.png b/templates/src/main/resources/templates/proposal/northline/icons/badge-scope.png new file mode 100644 index 000000000..c4af91d00 Binary files /dev/null and b/templates/src/main/resources/templates/proposal/northline/icons/badge-scope.png differ diff --git a/templates/src/main/resources/templates/proposal/northline/icons/badge-summary.png b/templates/src/main/resources/templates/proposal/northline/icons/badge-summary.png new file mode 100644 index 000000000..cb967c5cb Binary files /dev/null and b/templates/src/main/resources/templates/proposal/northline/icons/badge-summary.png differ diff --git a/templates/src/main/resources/templates/proposal/northline/icons/badge-terms.png b/templates/src/main/resources/templates/proposal/northline/icons/badge-terms.png new file mode 100644 index 000000000..968a5b69d Binary files /dev/null and b/templates/src/main/resources/templates/proposal/northline/icons/badge-terms.png differ diff --git a/templates/src/main/resources/templates/proposal/northline/icons/badge-timeline.png b/templates/src/main/resources/templates/proposal/northline/icons/badge-timeline.png new file mode 100644 index 000000000..861438a15 Binary files /dev/null and b/templates/src/main/resources/templates/proposal/northline/icons/badge-timeline.png differ diff --git a/templates/src/main/resources/templates/proposal/northline/icons/fact-contact.png b/templates/src/main/resources/templates/proposal/northline/icons/fact-contact.png new file mode 100644 index 000000000..553d008b2 Binary files /dev/null and b/templates/src/main/resources/templates/proposal/northline/icons/fact-contact.png differ diff --git a/templates/src/main/resources/templates/proposal/northline/icons/fact-duration.png b/templates/src/main/resources/templates/proposal/northline/icons/fact-duration.png new file mode 100644 index 000000000..c9c7cc34d Binary files /dev/null and b/templates/src/main/resources/templates/proposal/northline/icons/fact-duration.png differ diff --git a/templates/src/main/resources/templates/proposal/northline/icons/fact-start.png b/templates/src/main/resources/templates/proposal/northline/icons/fact-start.png new file mode 100644 index 000000000..6a39b6cbc Binary files /dev/null and b/templates/src/main/resources/templates/proposal/northline/icons/fact-start.png differ diff --git a/templates/src/main/resources/templates/proposal/northline/icons/fact-validity.png b/templates/src/main/resources/templates/proposal/northline/icons/fact-validity.png new file mode 100644 index 000000000..66fcde3bf Binary files /dev/null and b/templates/src/main/resources/templates/proposal/northline/icons/fact-validity.png differ diff --git a/templates/src/main/resources/templates/proposal/northline/icons/goal-brand.png b/templates/src/main/resources/templates/proposal/northline/icons/goal-brand.png new file mode 100644 index 000000000..711a15978 Binary files /dev/null and b/templates/src/main/resources/templates/proposal/northline/icons/goal-brand.png differ diff --git a/templates/src/main/resources/templates/proposal/northline/icons/goal-growth.png b/templates/src/main/resources/templates/proposal/northline/icons/goal-growth.png new file mode 100644 index 000000000..e697c58d8 Binary files /dev/null and b/templates/src/main/resources/templates/proposal/northline/icons/goal-growth.png differ diff --git a/templates/src/main/resources/templates/proposal/northline/icons/goal-message.png b/templates/src/main/resources/templates/proposal/northline/icons/goal-message.png new file mode 100644 index 000000000..a0300e3ae Binary files /dev/null and b/templates/src/main/resources/templates/proposal/northline/icons/goal-message.png differ diff --git a/templates/src/main/resources/templates/proposal/northline/icons/goal-usability.png b/templates/src/main/resources/templates/proposal/northline/icons/goal-usability.png new file mode 100644 index 000000000..3cc3c8a51 Binary files /dev/null and b/templates/src/main/resources/templates/proposal/northline/icons/goal-usability.png differ diff --git a/templates/src/main/resources/templates/rota/cobalt/icons/barbacks.svg b/templates/src/main/resources/templates/rota/cobalt/icons/barbacks.svg new file mode 100644 index 000000000..15f1e345b --- /dev/null +++ b/templates/src/main/resources/templates/rota/cobalt/icons/barbacks.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/rota/cobalt/icons/bartenders.svg b/templates/src/main/resources/templates/rota/cobalt/icons/bartenders.svg new file mode 100644 index 000000000..b37422ee8 --- /dev/null +++ b/templates/src/main/resources/templates/rota/cobalt/icons/bartenders.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/main/resources/templates/rota/cobalt/icons/management.svg b/templates/src/main/resources/templates/rota/cobalt/icons/management.svg new file mode 100644 index 000000000..98e94c3ac --- /dev/null +++ b/templates/src/main/resources/templates/rota/cobalt/icons/management.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/src/test/java/com/demcha/compose/document/templates/data/invoice/StructuredInvoiceDataTest.java b/templates/src/test/java/com/demcha/compose/document/templates/data/invoice/StructuredInvoiceDataTest.java new file mode 100644 index 000000000..6a513dfdb --- /dev/null +++ b/templates/src/test/java/com/demcha/compose/document/templates/data/invoice/StructuredInvoiceDataTest.java @@ -0,0 +1,173 @@ +package com.demcha.compose.document.templates.data.invoice; + +import com.demcha.compose.document.image.DocumentImageData; +import org.junit.jupiter.api.Test; + +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +/** + * Normalization and immutability contract of the structured invoice model: + * absent components become their empty forms, leaf strings never surface as + * {@code null}, money and quantities default to zero rather than null, the + * brand logo stays optional, and every collection is frozen at construction. + */ +class StructuredInvoiceDataTest { + + @Test + void emptyBuilderYieldsEmptyFormsForEveryComponent() { + StructuredInvoiceData data = StructuredInvoiceData.builder().build(); + + assertThat(data.brand().name()).isEmpty(); + assertThat(data.brand().hasLogo()).isFalse(); + assertThat(data.supplier().addressLines()).isEmpty(); + assertThat(data.masthead().entries()).isEmpty(); + assertThat(data.billTo().addressLines()).isEmpty(); + assertThat(data.summary().intro()).isEmpty(); + assertThat(data.serviceLines().lines()).isEmpty(); + assertThat(data.serviceLines().columns().amount()).isEmpty(); + assertThat(data.totals().rows()).isEmpty(); + assertThat(data.totals().totalAmount()).isEqualByComparingTo(BigDecimal.ZERO); + assertThat(data.payment().fields()).isEmpty(); + assertThat(data.notes().paragraphs()).isEmpty(); + assertThat(data.currencyCode()).isEmpty(); + } + + @Test + void leafRecordsNormalizeNullStringsToEmpty() { + InvoiceBrand brand = new InvoiceBrand(null, null, null, null); + assertThat(brand.name()).isEmpty(); + assertThat(brand.qualifier()).isEmpty(); + assertThat(brand.tagline()).isEmpty(); + + InvoiceContactBlock supplier = + new InvoiceContactBlock(null, null, null, null, null, null, null); + assertThat(supplier.legalName()).isEmpty(); + assertThat(supplier.website()).isEmpty(); + assertThat(supplier.registrationLabel()).isEmpty(); + + InvoiceMasthead.Entry entry = new InvoiceMasthead.Entry(null, null, false); + assertThat(entry.label()).isEmpty(); + assertThat(entry.value()).isEmpty(); + + InvoiceRecipient recipient = new InvoiceRecipient(null, null, null, null, null, null); + assertThat(recipient.heading()).isEmpty(); + assertThat(recipient.emailLabel()).isEmpty(); + assertThat(recipient.subline()).isEmpty(); + + InvoicePaymentBlock.Field field = new InvoicePaymentBlock.Field(null, null); + assertThat(field.label()).isEmpty(); + assertThat(field.value()).isEmpty(); + } + + @Test + void moneyAndQuantitiesDefaultToZeroRatherThanNull() { + InvoiceServiceLines.Line line = new InvoiceServiceLines.Line( + 1, null, null, null, null, null, null, null); + assertThat(line.quantity()).isEqualByComparingTo(BigDecimal.ZERO); + assertThat(line.unitPrice()).isEqualByComparingTo(BigDecimal.ZERO); + assertThat(line.amount()).isEqualByComparingTo(BigDecimal.ZERO); + + InvoiceTotalsBlock.Row row = new InvoiceTotalsBlock.Row("Subtotal", null); + assertThat(row.amount()).isEqualByComparingTo(BigDecimal.ZERO); + + InvoiceTotalsBlock totals = new InvoiceTotalsBlock(null, null, null); + assertThat(totals.totalAmount()).isEqualByComparingTo(BigDecimal.ZERO); + } + + @Test + void brandLogoIsOptionalAndReportedByHasLogo() { + DocumentImageData logo = DocumentImageData.fromBytes(new byte[] {1, 2, 3}); + InvoiceBrand withLogo = new InvoiceBrand(logo, "Northpoint", "Consulting", "Tagline"); + assertThat(withLogo.hasLogo()).isTrue(); + assertThat(withLogo.logo()).isSameAs(logo); + + assertThat(new InvoiceBrand(null, "Northpoint", "", "").hasLogo()).isFalse(); + } + + @Test + void everyCollectionIsFrozenAtConstruction() { + List source = new ArrayList<>(List.of("seed")); + InvoiceContactBlock supplier = new InvoiceContactBlock( + "Studio", source, "", "", "", "ABN", "12 345"); + source.add("added after construction"); + assertThat(supplier.addressLines()).containsExactly("seed"); + + List> frozen = List.of( + supplier.addressLines(), + new InvoiceMasthead("INVOICE", new ArrayList<>()).entries(), + new InvoiceRecipient("BILLED TO", "N", "", new ArrayList<>(), "", "").addressLines(), + new InvoiceServiceLines(null, new ArrayList<>()).lines(), + new InvoiceTotalsBlock(new ArrayList<>(), "TOTAL", BigDecimal.ONE).rows(), + new InvoicePaymentBlock("PAY", new ArrayList<>(), "", "", "").fields(), + new InvoiceNotesBlock("NOTES", new ArrayList<>(), "", "").paragraphs()); + for (List list : frozen) { + assertThatThrownBy(() -> list.add(null)) + .isInstanceOf(UnsupportedOperationException.class); + } + } + + @Test + void builderPassesEveryComponentThroughUnchanged() { + InvoiceBrand brand = new InvoiceBrand(null, "Northpoint", "Consulting", "Strategy."); + InvoiceContactBlock supplier = new InvoiceContactBlock("Northpoint Pty Ltd", + List.of("Level 8"), "+61", "hello@example.com", "example.com", "ABN", "12 345"); + InvoiceMasthead masthead = new InvoiceMasthead("INVOICE", + List.of(new InvoiceMasthead.Entry("Due Date:", "25 June 2025", true))); + InvoiceRecipient billTo = new InvoiceRecipient("BILLED TO", "Greenfield", + "Accounts Payable", List.of("12 Innovation Drive"), "Email:", "ap@example.com"); + InvoiceSummaryBlock summary = + new InvoiceSummaryBlock("INVOICE SUMMARY", "Services rendered.", "1–25 May"); + InvoiceServiceLines serviceLines = new InvoiceServiceLines( + new InvoiceServiceLines.Columns("#", "DESCRIPTION", "SERVICE PERIOD", + "QTY", "UNIT PRICE", "AMOUNT"), + List.of(new InvoiceServiceLines.Line(1, "Consultation", "Workshops", + "1–10 May", new BigDecimal("10"), "hrs", + new BigDecimal("250"), new BigDecimal("2500")))); + InvoiceTotalsBlock totals = new InvoiceTotalsBlock( + List.of(new InvoiceTotalsBlock.Row("SUBTOTAL", new BigDecimal("14000"))), + "TOTAL DUE", new BigDecimal("15400")); + InvoicePaymentBlock payment = new InvoicePaymentBlock("PAYMENT INFORMATION", + List.of(new InvoicePaymentBlock.Field("BSB:", "123-456")), + "Include the invoice number.", "Payment is due within 30 days.", "30 days"); + InvoiceNotesBlock notes = new InvoiceNotesBlock("NOTES", + List.of("Thank you."), "accounts@example.com", "+61"); + StructuredInvoiceData data = StructuredInvoiceData.builder() + .brand(brand) + .supplier(supplier) + .masthead(masthead) + .billTo(billTo) + .summary(summary) + .serviceLines(serviceLines) + .totals(totals) + .payment(payment) + .notes(notes) + .currencyCode("AUD") + .build(); + + assertThat(data.brand()).isSameAs(brand); + assertThat(data.supplier()).isSameAs(supplier); + assertThat(data.masthead()).isSameAs(masthead); + assertThat(data.billTo()).isSameAs(billTo); + assertThat(data.summary()).isSameAs(summary); + assertThat(data.serviceLines()).isSameAs(serviceLines); + assertThat(data.totals()).isSameAs(totals); + assertThat(data.payment()).isSameAs(payment); + assertThat(data.notes()).isSameAs(notes); + assertThat(data.currencyCode()).isEqualTo("AUD"); + assertThat(data.masthead().entries().get(0).emphasized()).isTrue(); + } + + @Test + void documentSpecNormalizesNullInvoiceToEmptyData() { + assertThat(StructuredInvoiceDocumentSpec.from(null).invoice()).isNotNull(); + assertThat(new StructuredInvoiceDocumentSpec(null).invoice().payment().fields()).isEmpty(); + + StructuredInvoiceData data = StructuredInvoiceData.builder().build(); + assertThat(StructuredInvoiceDocumentSpec.from(data).invoice()).isSameAs(data); + } +} diff --git a/templates/src/test/java/com/demcha/compose/document/templates/data/proposal/StructuredProposalDataTest.java b/templates/src/test/java/com/demcha/compose/document/templates/data/proposal/StructuredProposalDataTest.java new file mode 100644 index 000000000..3d2c96ac1 --- /dev/null +++ b/templates/src/test/java/com/demcha/compose/document/templates/data/proposal/StructuredProposalDataTest.java @@ -0,0 +1,198 @@ +package com.demcha.compose.document.templates.data.proposal; + +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +/** + * Normalization and immutability contract of the structured proposal model: + * absent components become their empty forms, leaf strings never surface as + * {@code null}, row roles default to {@code NONE}, and every collection is + * frozen at construction. + */ +class StructuredProposalDataTest { + + @Test + void emptyBuilderYieldsEmptyFormsForEveryComponent() { + StructuredProposalData data = StructuredProposalData.builder().build(); + + assertThat(data.brand().monogram()).isEmpty(); + assertThat(data.title().lead()).isEmpty(); + assertThat(data.meta().preparedFor()).isEmpty(); + assertThat(data.executiveSummary().paragraphs()).isEmpty(); + assertThat(data.glance().facts()).isEmpty(); + assertThat(data.goals().items()).isEmpty(); + assertThat(data.scope().items()).isEmpty(); + assertThat(data.deliverables().leftColumn()).isEmpty(); + assertThat(data.deliverables().rightColumn()).isEmpty(); + assertThat(data.timeline().columnHeaders()).isEmpty(); + assertThat(data.timeline().phases()).isEmpty(); + assertThat(data.investment().rows()).isEmpty(); + assertThat(data.terms().items()).isEmpty(); + assertThat(data.acceptance().fields()).isEmpty(); + } + + @Test + void leafRecordsNormalizeNullStringsToEmpty() { + ProposalGlance.Fact fact = new ProposalGlance.Fact(null, null, null, null); + assertThat(fact.icon()).isEmpty(); + assertThat(fact.label()).isEmpty(); + assertThat(fact.value()).isEmpty(); + assertThat(fact.note()).isEmpty(); + + ProposalGoals.Goal goal = new ProposalGoals.Goal(null, null); + assertThat(goal.icon()).isEmpty(); + assertThat(goal.text()).isEmpty(); + + ProposalScope.Item item = new ProposalScope.Item(null, null, null); + assertThat(item.number()).isEmpty(); + assertThat(item.title()).isEmpty(); + assertThat(item.description()).isEmpty(); + + ProposalPhaseGrid.Phase phase = new ProposalPhaseGrid.Phase(null, null, null, null, null); + assertThat(phase.number()).isEmpty(); + assertThat(phase.name()).isEmpty(); + assertThat(phase.focus()).isEmpty(); + assertThat(phase.duration()).isEmpty(); + assertThat(phase.output()).isEmpty(); + + ProposalInvestment.Row row = new ProposalInvestment.Row(null, null, null); + assertThat(row.label()).isEmpty(); + assertThat(row.amount()).isEmpty(); + + ProposalMetaLine meta = new ProposalMetaLine(null, null, null); + assertThat(meta.preparedFor()).isEmpty(); + assertThat(meta.preparedBy()).isEmpty(); + assertThat(meta.date()).isEmpty(); + + ProposalTitleLines title = new ProposalTitleLines(null, null, null); + assertThat(title.lead()).isEmpty(); + assertThat(title.second()).isEmpty(); + assertThat(title.third()).isEmpty(); + } + + @Test + void sectionHeadingsAndIconsNormalizeNullToEmpty() { + assertThat(new ProposalSummaryBlock(null, null, null).heading()).isEmpty(); + assertThat(new ProposalSummaryBlock(null, null, null).icon()).isEmpty(); + assertThat(new ProposalGlance(null, null).heading()).isEmpty(); + assertThat(new ProposalGoals(null, null, null).icon()).isEmpty(); + assertThat(new ProposalScope(null, null, null).heading()).isEmpty(); + assertThat(new ProposalDeliverables(null, null, null, null).icon()).isEmpty(); + assertThat(new ProposalPhaseGrid(null, null, null, null).heading()).isEmpty(); + assertThat(new ProposalInvestment(null, null, null, null, null, null, null) + .amountHeader()).isEmpty(); + assertThat(new ProposalTermsBlock(null, null, null).icon()).isEmpty(); + assertThat(new ProposalAcceptance(null, null, null, null).statement()).isEmpty(); + assertThat(new ProposalBrand(null, null, null, null, null, null).footerName()).isEmpty(); + } + + @Test + void investmentRowRoleDefaultsToNone() { + ProposalInvestment.Row row = new ProposalInvestment.Row("Design", "$1,000", null); + assertThat(row.role()).isEqualTo(ProposalInvestment.Role.NONE); + } + + @Test + void everyCollectionIsFrozenAtConstruction() { + List source = new ArrayList<>(List.of("seed")); + ProposalDeliverables deliverables = + new ProposalDeliverables("DELIVERABLES", "badge-deliverables", source, source); + source.add("added after construction"); + assertThat(deliverables.leftColumn()).containsExactly("seed"); + assertThat(deliverables.rightColumn()).containsExactly("seed"); + + List> frozen = List.of( + deliverables.leftColumn(), + deliverables.rightColumn(), + new ProposalSummaryBlock("S", "i", new ArrayList<>()).paragraphs(), + new ProposalGlance("G", new ArrayList<>()).facts(), + new ProposalGoals("G", "i", new ArrayList<>()).items(), + new ProposalScope("S", "i", new ArrayList<>()).items(), + new ProposalPhaseGrid("T", "i", new ArrayList<>(), new ArrayList<>()).columnHeaders(), + new ProposalPhaseGrid("T", "i", new ArrayList<>(), new ArrayList<>()).phases(), + new ProposalInvestment("I", "i", "ITEM", "AMOUNT", + new ArrayList<>(), "TOTAL", "$0").rows(), + new ProposalTermsBlock("T", "i", new ArrayList<>()).items(), + new ProposalAcceptance("A", "i", "s", new ArrayList<>()).fields()); + for (List list : frozen) { + assertThatThrownBy(() -> list.remove(0)) + .isInstanceOf(UnsupportedOperationException.class); + } + } + + @Test + void builderPassesEveryComponentThroughUnchanged() { + ProposalBrand brand = new ProposalBrand("N", "Northline", "Digital Studio", + "Proposal", "northline.example", "Northline Digital"); + ProposalTitleLines title = new ProposalTitleLines("A proposal", "for the", "redesign"); + ProposalMetaLine meta = new ProposalMetaLine("Client", "Studio", "2026-08-30"); + ProposalSummaryBlock summary = new ProposalSummaryBlock("SUMMARY", "badge-summary", + List.of("First paragraph.")); + ProposalGlance glance = new ProposalGlance("AT A GLANCE", + List.of(new ProposalGlance.Fact("fact-start", "START", "Sep 2026", ""))); + ProposalGoals goals = new ProposalGoals("GOALS", "badge-goals", + List.of(new ProposalGoals.Goal("goal-brand", "Sharpen the brand"))); + ProposalScope scope = new ProposalScope("SCOPE", "badge-scope", + List.of(new ProposalScope.Item("01", "Discovery", "Interviews and audit"))); + ProposalDeliverables deliverables = new ProposalDeliverables("DELIVERABLES", + "badge-deliverables", List.of("Style guide"), List.of("Launch support")); + ProposalPhaseGrid timeline = new ProposalPhaseGrid("TIMELINE", "badge-timeline", + List.of("PHASE", "FOCUS", "DURATION", "OUTPUT"), + List.of(new ProposalPhaseGrid.Phase("1", "Discovery", "Research", + "2 weeks", "Findings deck"))); + ProposalInvestment investment = new ProposalInvestment("INVESTMENT", + "badge-investment", "ITEM", "AMOUNT", + List.of(new ProposalInvestment.Row("Subtotal", "$9,000", + ProposalInvestment.Role.SUBTOTAL)), + "TOTAL", "$9,900"); + ProposalTermsBlock terms = new ProposalTermsBlock("TERMS", "badge-terms", + List.of("Net 14.")); + ProposalAcceptance acceptance = new ProposalAcceptance("ACCEPTANCE", + "badge-acceptance", "Signing below accepts this proposal.", + List.of("Name", "Date", "Signature")); + + StructuredProposalData data = StructuredProposalData.builder() + .brand(brand) + .title(title) + .meta(meta) + .executiveSummary(summary) + .glance(glance) + .goals(goals) + .scope(scope) + .deliverables(deliverables) + .timeline(timeline) + .investment(investment) + .terms(terms) + .acceptance(acceptance) + .build(); + + assertThat(data.brand()).isSameAs(brand); + assertThat(data.title()).isSameAs(title); + assertThat(data.meta()).isSameAs(meta); + assertThat(data.executiveSummary()).isSameAs(summary); + assertThat(data.glance()).isSameAs(glance); + assertThat(data.goals()).isSameAs(goals); + assertThat(data.scope()).isSameAs(scope); + assertThat(data.deliverables()).isSameAs(deliverables); + assertThat(data.timeline()).isSameAs(timeline); + assertThat(data.investment()).isSameAs(investment); + assertThat(data.terms()).isSameAs(terms); + assertThat(data.acceptance()).isSameAs(acceptance); + assertThat(data.investment().rows().get(0).role()) + .isEqualTo(ProposalInvestment.Role.SUBTOTAL); + } + + @Test + void documentSpecNormalizesNullProposalToEmptyData() { + assertThat(StructuredProposalDocumentSpec.from(null).proposal()).isNotNull(); + assertThat(new StructuredProposalDocumentSpec(null).proposal().glance().facts()).isEmpty(); + + StructuredProposalData data = StructuredProposalData.builder().build(); + assertThat(StructuredProposalDocumentSpec.from(data).proposal()).isSameAs(data); + } +} diff --git a/templates/src/test/java/com/demcha/compose/document/templates/data/schedule/WeeklyScheduleDataBuilderTest.java b/templates/src/test/java/com/demcha/compose/document/templates/data/schedule/WeeklyScheduleDataBuilderTest.java index f08bffc67..49006000c 100644 --- a/templates/src/test/java/com/demcha/compose/document/templates/data/schedule/WeeklyScheduleDataBuilderTest.java +++ b/templates/src/test/java/com/demcha/compose/document/templates/data/schedule/WeeklyScheduleDataBuilderTest.java @@ -31,14 +31,14 @@ void builderShouldCreateScheduleWithDaysCategoriesMetricsPeopleAssignmentsAndFoo .headerMetric(metric -> metric .label("FOCUS") .dayValues(List.of("API", "Regression"))) - .person("artem", "ARTEM", 10) + .person("omar", "OMAR", 10) .person(person -> person - .id("alex") - .displayName("ALEX") + .id("iva") + .displayName("IVA") .sortOrder(20)) - .assignment("artem", "mon", "delivery", ScheduleSlot.of("09:00", "17:00")) + .assignment("omar", "mon", "delivery", ScheduleSlot.of("09:00", "17:00")) .assignment(assignment -> assignment - .personId("alex") + .personId("iva") .dayId("tue") .categoryId("qa") .slot("10:00", "18:00") @@ -51,7 +51,7 @@ void builderShouldCreateScheduleWithDaysCategoriesMetricsPeopleAssignmentsAndFoo assertThat(schedule.days()).extracting(ScheduleDay::id).containsExactly("mon", "tue"); assertThat(schedule.categories()).extracting(ScheduleCategory::id).containsExactly("delivery", "qa"); assertThat(schedule.headerMetrics()).extracting(ScheduleMetricRow::label).containsExactly("CAPACITY", "FOCUS"); - assertThat(schedule.people()).extracting(SchedulePerson::id).containsExactly("artem", "alex"); + assertThat(schedule.people()).extracting(SchedulePerson::id).containsExactly("omar", "iva"); assertThat(schedule.assignments()).hasSize(2); assertThat(schedule.assignments().get(0).slots().get(0).displayText()).isEqualTo("09:00 17:00"); assertThat(schedule.assignments().get(1).note()).isEqualTo("Regression owner"); diff --git a/templates/src/test/java/com/demcha/compose/document/templates/data/schedule/WeeklyScheduleDocumentSpecTest.java b/templates/src/test/java/com/demcha/compose/document/templates/data/schedule/WeeklyScheduleDocumentSpecTest.java index b9442e7d5..1d3fc5746 100644 --- a/templates/src/test/java/com/demcha/compose/document/templates/data/schedule/WeeklyScheduleDocumentSpecTest.java +++ b/templates/src/test/java/com/demcha/compose/document/templates/data/schedule/WeeklyScheduleDocumentSpecTest.java @@ -16,8 +16,8 @@ void builderShouldCreateDocumentLevelWeeklyScheduleSpec() { .day("mon", "Monday", "Release prep", "delivery") .category("delivery", "DELIVERY", new Color(0, 173, 76), new Color(0, 110, 49)) .headerMetric("CAPACITY", "4 engineers") - .person("artem", "ARTEM", 10) - .assignment("artem", "mon", "delivery", ScheduleSlot.of("09:00", "17:00")) + .person("omar", "OMAR", 10) + .assignment("omar", "mon", "delivery", ScheduleSlot.of("09:00", "17:00")) .footerNote("Keep assignments data-driven.") .build(); @@ -34,7 +34,7 @@ void builderShouldCreateDocumentLevelWeeklyScheduleSpec() { .isEqualTo("CAPACITY"); assertThat(spec.schedule().people()).singleElement() .extracting(SchedulePerson::id) - .isEqualTo("artem"); + .isEqualTo("omar"); assertThat(spec.schedule().assignments()).singleElement() .satisfies(assignment -> assertThat(assignment.slots()).singleElement() .extracting(ScheduleSlot::displayText) diff --git a/templates/src/test/java/com/demcha/mock/WeeklyScheduleDataFixtures.java b/templates/src/test/java/com/demcha/mock/WeeklyScheduleDataFixtures.java index 9c2946be7..4cc69637e 100644 --- a/templates/src/test/java/com/demcha/mock/WeeklyScheduleDataFixtures.java +++ b/templates/src/test/java/com/demcha/mock/WeeklyScheduleDataFixtures.java @@ -19,32 +19,32 @@ private WeeklyScheduleDataFixtures() { public static WeeklyScheduleData standardSchedule() { return new WeeklyScheduleData( - "Scott's Weekly Floor Schedule", + "Quayside Weekly Floor Schedule", "Week Of 30 Mar - 05 Apr 2026", List.of( new ScheduleDay("mon", "Monday\n30th", "Clean crushed ice\nMachine & area", "request"), new ScheduleDay("tue", "Tuesday\n31st", "Pianist 18:30\nClean ice machine", "off"), - new ScheduleDay("wed", "Wednesday\n1st", "Motown GF\nPianist FF", "hol"), - new ScheduleDay("thu", "Thursday\n2nd", "Terrace\ndinner\nMGM meeting\n3:30pm", "stock"), + new ScheduleDay("wed", "Wednesday\n1st", "Soul GF\nPianist FF", "hol"), + new ScheduleDay("thu", "Thursday\n2nd", "Terrace\ndinner\nPartner meeting\n3:30pm", "stock"), new ScheduleDay("fri", "Friday\n3rd", "Good Friday\nPianist 19:00", "standby"), new ScheduleDay("sat", "Saturday\n4th", "Masterclass 2x2PAX\nPianist 19:00", "training"), new ScheduleDay("sun", "Sunday\n5th", "Easter Sunday\nFull stock take", "bar-back")), baseCategories(), List.of( new ScheduleMetricRow("COVERS", List.of("27 / 37", "41 / 36", "30 / 29", "57 / 63", "46 / 71", "73 / 97", "155 / 26")), - new ScheduleMetricRow("TEAM FOCUS", List.of("Alex floor", "Glass count", "Vee lab", "Sergii lab", "Pianist", "Alex floor", "Sergii floor"))), + new ScheduleMetricRow("TEAM FOCUS", List.of("Iva floor", "Glass count", "Nils lab", "Rowan lab", "Pianist", "Iva floor", "Rowan floor"))), List.of( - new SchedulePerson("sergii", "SERGII", 10), - new SchedulePerson("mark", "MARK", 20), - new SchedulePerson("alex", "ALEX", 30), - new SchedulePerson("bianca", "BIANCA", 40), - new SchedulePerson("artem", "ARTEM", 50), - new SchedulePerson("kharren", "KHARREN", 60), - new SchedulePerson("daria", "DARIA", 70), - new SchedulePerson("violetta", "VIOLETTA", 80), - new SchedulePerson("peter", "PETER", 90), - new SchedulePerson("dmytro", "DMYTRO", 100), - new SchedulePerson("daniel", "DANIEL", 110)), + new SchedulePerson("rowan", "ROWAN", 10), + new SchedulePerson("nils", "NILS", 20), + new SchedulePerson("iva", "IVA", 30), + new SchedulePerson("pilar", "PILAR", 40), + new SchedulePerson("omar", "OMAR", 50), + new SchedulePerson("lena", "LENA", 60), + new SchedulePerson("freya", "FREYA", 70), + new SchedulePerson("noor", "NOOR", 80), + new SchedulePerson("bowen", "BOWEN", 90), + new SchedulePerson("sasha", "SASHA", 100), + new SchedulePerson("hugo", "HUGO", 110)), baseAssignments(), List.of( "Add or remove people by editing only the people and assignments lists.", @@ -99,7 +99,7 @@ public static WeeklyScheduleData withAddedAndRemovedCategory() { categories.add(new ScheduleCategory("masterclass", "MASTERCLASS", new Color(255, 227, 163), Color.BLACK, new Color(196, 145, 16))); List assignments = new ArrayList<>(base.assignments()); - assignments.add(new ScheduleAssignment("alex", "sun", "masterclass", List.of(slot("18:00", "23:00")), "Guest setup")); + assignments.add(new ScheduleAssignment("iva", "sun", "masterclass", List.of(slot("18:00", "23:00")), "Guest setup")); return new WeeklyScheduleData( base.title(), @@ -126,50 +126,50 @@ private static List baseCategories() { private static List baseAssignments() { return List.of( - new ScheduleAssignment("sergii", "mon", "stock", List.of(slot("09:00", "18:00")), ""), - new ScheduleAssignment("sergii", "thu", "request", List.of(slot("09:00", "18:00")), ""), - new ScheduleAssignment("sergii", "fri", "training", List.of(slot("08:00", "16:00"), slot("16:00", "22:00")), ""), - new ScheduleAssignment("sergii", "sun", "standby", List.of(slot("12:00", "16:00"), slot("16:00", "22:00")), ""), - new ScheduleAssignment("mark", "mon", "request", List.of(slot("16:00", "00:00")), ""), - new ScheduleAssignment("mark", "tue", "off", List.of(slot("12:00", "18:00")), ""), - new ScheduleAssignment("mark", "wed", "request", List.of(slot("16:00", "00:00")), ""), - new ScheduleAssignment("mark", "fri", "standby", List.of(slot("16:00", "01:00")), ""), - new ScheduleAssignment("mark", "sun", "bar-back", List.of(slot("17:00", "03:00")), ""), - new ScheduleAssignment("alex", "tue", "off", List.of(slot("16:00", "00:00")), ""), - new ScheduleAssignment("alex", "wed", "request", List.of(slot("16:00", "00:00")), ""), - new ScheduleAssignment("alex", "thu", "request", List.of(slot("16:00", "00:00")), ""), - new ScheduleAssignment("alex", "fri", "standby", List.of(slot("16:00", "22:00")), ""), - new ScheduleAssignment("alex", "sat", "training", List.of(slot("12:00", "16:00"), slot("17:00", "01:00")), ""), - new ScheduleAssignment("bianca", "thu", "standby", List.of(slot("16:00", "00:00")), ""), - new ScheduleAssignment("bianca", "fri", "standby", List.of(slot("17:00", "01:00")), ""), - new ScheduleAssignment("bianca", "sat", "training", List.of(slot("12:00", "16:00"), slot("17:00", "01:00")), ""), - new ScheduleAssignment("bianca", "sun", "request", List.of(slot("12:00", "16:00"), slot("17:00", "00:00")), ""), - new ScheduleAssignment("artem", "tue", "off", List.of(slot("17:00", "00:00")), ""), - new ScheduleAssignment("artem", "wed", "request", List.of(slot("17:00", "00:00")), ""), - new ScheduleAssignment("artem", "thu", "request", List.of(slot("17:00", "00:00")), ""), - new ScheduleAssignment("artem", "fri", "standby", List.of(slot("17:00", "01:00")), ""), - new ScheduleAssignment("artem", "sat", "training", List.of(slot("12:00", "16:00"), slot("17:00", "01:00")), ""), - new ScheduleAssignment("kharren", "wed", "request", List.of(slot("09:00", "17:00")), ""), - new ScheduleAssignment("kharren", "thu", "request", List.of(slot("09:00", "17:00")), ""), - new ScheduleAssignment("kharren", "fri", "standby", List.of(slot("09:00", "17:00")), ""), - new ScheduleAssignment("kharren", "sat", "training", List.of(slot("16:00", "22:00")), ""), - new ScheduleAssignment("kharren", "sun", "request", List.of(slot("09:00", "16:00"), slot("17:00", "22:00")), ""), - new ScheduleAssignment("daria", "sat", "training", List.of(slot("16:00", "01:00")), ""), - new ScheduleAssignment("daria", "sun", "request", List.of(slot("12:00", "16:00"), slot("17:00", "00:00")), ""), - new ScheduleAssignment("violetta", "tue", "request", List.of(slot("09:00", "17:00")), ""), - new ScheduleAssignment("violetta", "wed", "request", List.of(slot("09:00", "18:00")), ""), - new ScheduleAssignment("violetta", "fri", "standby", List.of(slot("16:00", "22:00")), ""), - new ScheduleAssignment("violetta", "sat", "request", List.of(slot("09:00", "17:00")), ""), - new ScheduleAssignment("peter", "fri", "standby", List.of(slot("16:00", "01:00")), ""), - new ScheduleAssignment("peter", "sat", "training", List.of(slot("12:00", "16:00"), slot("17:00", "01:00")), ""), - new ScheduleAssignment("dmytro", "mon", "request", List.of(slot("17:00", "00:00")), ""), - new ScheduleAssignment("dmytro", "sat", "training", List.of(slot("12:00", "16:00"), slot("17:00", "01:00")), ""), - new ScheduleAssignment("dmytro", "sun", "request", List.of(slot("12:00", "16:00"), slot("17:00", "00:00")), ""), - new ScheduleAssignment("daniel", "tue", "request", List.of(slot("17:00", "00:00")), ""), - new ScheduleAssignment("daniel", "wed", "request", List.of(slot("17:00", "00:00")), ""), - new ScheduleAssignment("daniel", "thu", "standby", List.of(slot("17:00", "00:00")), ""), - new ScheduleAssignment("daniel", "fri", "standby", List.of(slot("17:00", "01:00")), ""), - new ScheduleAssignment("daniel", "sun", "request", List.of(slot("12:00", "16:00"), slot("17:00", "00:00")), "")); + new ScheduleAssignment("rowan", "mon", "stock", List.of(slot("09:00", "18:00")), ""), + new ScheduleAssignment("rowan", "thu", "request", List.of(slot("09:00", "18:00")), ""), + new ScheduleAssignment("rowan", "fri", "training", List.of(slot("08:00", "16:00"), slot("16:00", "22:00")), ""), + new ScheduleAssignment("rowan", "sun", "standby", List.of(slot("12:00", "16:00"), slot("16:00", "22:00")), ""), + new ScheduleAssignment("nils", "mon", "request", List.of(slot("16:00", "00:00")), ""), + new ScheduleAssignment("nils", "tue", "off", List.of(slot("12:00", "18:00")), ""), + new ScheduleAssignment("nils", "wed", "request", List.of(slot("16:00", "00:00")), ""), + new ScheduleAssignment("nils", "fri", "standby", List.of(slot("16:00", "01:00")), ""), + new ScheduleAssignment("nils", "sun", "bar-back", List.of(slot("17:00", "03:00")), ""), + new ScheduleAssignment("iva", "tue", "off", List.of(slot("16:00", "00:00")), ""), + new ScheduleAssignment("iva", "wed", "request", List.of(slot("16:00", "00:00")), ""), + new ScheduleAssignment("iva", "thu", "request", List.of(slot("16:00", "00:00")), ""), + new ScheduleAssignment("iva", "fri", "standby", List.of(slot("16:00", "22:00")), ""), + new ScheduleAssignment("iva", "sat", "training", List.of(slot("12:00", "16:00"), slot("17:00", "01:00")), ""), + new ScheduleAssignment("pilar", "thu", "standby", List.of(slot("16:00", "00:00")), ""), + new ScheduleAssignment("pilar", "fri", "standby", List.of(slot("17:00", "01:00")), ""), + new ScheduleAssignment("pilar", "sat", "training", List.of(slot("12:00", "16:00"), slot("17:00", "01:00")), ""), + new ScheduleAssignment("pilar", "sun", "request", List.of(slot("12:00", "16:00"), slot("17:00", "00:00")), ""), + new ScheduleAssignment("omar", "tue", "off", List.of(slot("17:00", "00:00")), ""), + new ScheduleAssignment("omar", "wed", "request", List.of(slot("17:00", "00:00")), ""), + new ScheduleAssignment("omar", "thu", "request", List.of(slot("17:00", "00:00")), ""), + new ScheduleAssignment("omar", "fri", "standby", List.of(slot("17:00", "01:00")), ""), + new ScheduleAssignment("omar", "sat", "training", List.of(slot("12:00", "16:00"), slot("17:00", "01:00")), ""), + new ScheduleAssignment("lena", "wed", "request", List.of(slot("09:00", "17:00")), ""), + new ScheduleAssignment("lena", "thu", "request", List.of(slot("09:00", "17:00")), ""), + new ScheduleAssignment("lena", "fri", "standby", List.of(slot("09:00", "17:00")), ""), + new ScheduleAssignment("lena", "sat", "training", List.of(slot("16:00", "22:00")), ""), + new ScheduleAssignment("lena", "sun", "request", List.of(slot("09:00", "16:00"), slot("17:00", "22:00")), ""), + new ScheduleAssignment("freya", "sat", "training", List.of(slot("16:00", "01:00")), ""), + new ScheduleAssignment("freya", "sun", "request", List.of(slot("12:00", "16:00"), slot("17:00", "00:00")), ""), + new ScheduleAssignment("noor", "tue", "request", List.of(slot("09:00", "17:00")), ""), + new ScheduleAssignment("noor", "wed", "request", List.of(slot("09:00", "18:00")), ""), + new ScheduleAssignment("noor", "fri", "standby", List.of(slot("16:00", "22:00")), ""), + new ScheduleAssignment("noor", "sat", "request", List.of(slot("09:00", "17:00")), ""), + new ScheduleAssignment("bowen", "fri", "standby", List.of(slot("16:00", "01:00")), ""), + new ScheduleAssignment("bowen", "sat", "training", List.of(slot("12:00", "16:00"), slot("17:00", "01:00")), ""), + new ScheduleAssignment("sasha", "mon", "request", List.of(slot("17:00", "00:00")), ""), + new ScheduleAssignment("sasha", "sat", "training", List.of(slot("12:00", "16:00"), slot("17:00", "01:00")), ""), + new ScheduleAssignment("sasha", "sun", "request", List.of(slot("12:00", "16:00"), slot("17:00", "00:00")), ""), + new ScheduleAssignment("hugo", "tue", "request", List.of(slot("17:00", "00:00")), ""), + new ScheduleAssignment("hugo", "wed", "request", List.of(slot("17:00", "00:00")), ""), + new ScheduleAssignment("hugo", "thu", "standby", List.of(slot("17:00", "00:00")), ""), + new ScheduleAssignment("hugo", "fri", "standby", List.of(slot("17:00", "01:00")), ""), + new ScheduleAssignment("hugo", "sun", "request", List.of(slot("12:00", "16:00"), slot("17:00", "00:00")), "")); } private static ScheduleSlot slot(String start, String end) {