/proto · page.proto
page.proto
Lazy source payload for raster-authoritative ink visuals. The hot page item
Messages
24
Enums
12
Fields
288
Source
29.3 KB
Imports
4
InkSourceFormat enum · 5 values
| # | name | notes |
|---|---|---|
| 0 | INK_SOURCE_FORMAT_UNSPECIFIED | |
| 1 | INK_SOURCE_FORMAT_PPTX_CONTENT_PART_INKML | |
| 2 | INK_SOURCE_FORMAT_PPTX_P14_INLINE_INK | |
| 3 | INK_SOURCE_FORMAT_CUSTOM_GEOMETRY_INK | |
| 4 | INK_SOURCE_FORMAT_OPAQUE_BLOB |
InkSourceData message · 4 fields
Lazy source payload for raster-authoritative ink visuals. The hot page item
remains a normal IMG; this sidecar exists only when ContentItem has
rasterization_type=7 and the caller wants to inspect/edit the source ink.
| # | label | type | name | notes |
|---|---|---|---|---|
| 1 | single | InkSourceFormat | source_format | |
| 20 | optional | string | raw_xml | |
| 21 | optional | bytes | raw_blob | |
| 22 | optional | bytes | source_sha256 |
OleSourceData message · 5 fields
Lazy source payload for OLE / object embeddings. Visible body remains an
ordinary IMG / Picture in the hot page; this sidecar exists only when
ContentItem has rasterization_type=6 and the caller wants the original
embedded object bytes (e.g., embedded Excel/Word/Equation/PowerPoint blobs)
for inspection, download, or in-place editing. Routed through
blob-raster-sources/{hash}/t6.pb.
| # | label | type | name | notes |
|---|---|---|---|---|
| 1 | optional | string | prog_id | OOXML progId string (e.g., "Excel.Sheet.12", "Equation.3"). Empty when the producer did not declare one — `content_type` is the authoritative typed signal. |
| 2 | optional | string | content_type | IANA media type for the embedded blob, derived from progId or filename extension (e.g., "application/vnd.ms-excel", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"). |
| 3 | optional | string | source_filename | Original filename of the embedded part as stored inside the package (e.g., "oleObject1.bin", "Microsoft_Excel_Worksheet.xlsx"). Useful for download / "open original" UX. |
| 21 | optional | bytes | raw_blob | The raw embedded blob. Always populated — OLE source payloads are typically modest in size and downstream consumers expect inline access. |
| 22 | optional | bytes | source_sha256 |
BreakKind enum · 3 values
==============================================================================
PACKED POSITION FORMAT (Variable Length)
==============================================================================
All elements use a compact packed array for position and z-index.
The array length indicates which values are present:
STANDARD FORMAT (5 values): [left, top, width, height, z]
Index 0: left - X coordinate in milli-units (1/1000th of a pixel)
Index 1: top - Y coordinate in milli-units
Index 2: width - Width in milli-units
Index 3: height - Height in milli-units
Index 4: z - Z-index for layering
EXTENDED FORMAT (7 values): [left, top, width, height, z, right, bottom]
Index 5: right - Explicit right edge (optional, for non-standard layouts)
Index 6: bottom - Explicit bottom edge (optional, for non-standard layouts)
Benefits:
- 60% smaller wire format vs separate Rect message + z field
- Single field for all positioning data
- Packed encoding for maximum efficiency
- Optional right/bottom adds negligible overhead (only when needed)
- Array length indicates data availability (5 = standard, 7 = extended)
==============================================================================
| # | name | notes |
|---|---|---|
| 0 | BREAK_KIND_NONE | |
| 1 | BREAK_KIND_SOFT | SOFT: hint-only line break. Renderer MAY honor it; safe to ignore for reflow. Currently unused by the PDF pipeline (which emits HARD instead). |
| 2 | BREAK_KIND_HARD | HARD: forced visual line break, equivalent to <br>. Used by the PDF pipeline to mark the boundary between visual rows that the textbox merger heuristically grouped into a single textbox/paragraph. The renderer MUST honor this so reflow does not silently re-wrap rows. |
AutofitMode enum · 4 values
| # | name | notes |
|---|---|---|
| 0 | AUTOFIT_UNSPECIFIED | |
| 1 | AUTOFIT_NONE | |
| 2 | AUTOFIT_SHRINK_TEXT | |
| 3 | AUTOFIT_RESIZE_SHAPE |
TextRunCss message · 29 fields
| # | label | type | name | notes |
|---|---|---|---|---|
| 1 | optional | uint32 | font_ref_id | |
| 2 | optional | uint32 | color_ref_id | |
| 3 | optional | uint32 | font_size_ref_id | |
| 4 | optional | int32 | font_weight | |
| 5 | optional | uint32 | flags | Run flags bitmask: 0x01: Italic 0x02: Underline (presence; see underline_type for CSS style) 0x04: Strikethrough (presence; see strikethrough_type for CSS style) 0x08: Small caps |
| 6 | optional | uint32 | line_height_ref_id | |
| 7 | optional | uint32 | letter_spacing_ref_id | |
| 8 | optional | uint32 | word_spacing_ref_id | |
| 9 | optional | uint32 | background_color_ref_id | |
| 10 | optional | TextShaping | text_shaping | |
| 11 | optional | ColorValue | text_decoration_color | |
| 12 | optional | Position | position | |
| 13 | optional | int32 | top_offset | |
| 14 | optional | int32 | bottom_offset | |
| 15 | optional | TextRunCssExtra | extra | |
| 16 | repeated | Transform | transforms | |
| 17 | optional | string | gradient_css | |
| 18 | optional | uint32 | underline_type | |
| 19 | optional | uint32 | strikethrough_type | |
| 20 | optional | int32 | text_stroke_width | |
| 21 | optional | uint32 | text_stroke_color_ref_id | |
| 22 | optional | string | text_shadow_css | |
| 23 | optional | string | font_family | Direct CSS value literals for common run-level text properties. Ref IDs above remain the preferred representation when the value comes from the ref table; these fields preserve inline CSS values without falling back to the generic extra.css declaration bag. |
| 24 | optional | string | font_size | |
| 25 | optional | string | line_height | |
| 26 | optional | string | letter_spacing | |
| 27 | optional | string | word_spacing | |
| 28 | optional | string | color | |
| 29 | optional | string | background_color |
TextRunCss. Transform message · 2 fields
| # | label | type | name | notes |
|---|---|---|---|---|
| 1 | single | TransformType | type | |
| 2 | single | string | value |
TextRunCss.Transform. TransformType enum · 10 values
| # | name | notes |
|---|---|---|
| 0 | TRANSFORM_UNSPECIFIED | |
| 1 | TRANSFORM_TRANSLATE_X | |
| 2 | TRANSFORM_TRANSLATE_Y | |
| 3 | TRANSFORM_TRANSLATE | |
| 4 | TRANSFORM_SCALE | |
| 5 | TRANSFORM_SCALE_X | |
| 6 | TRANSFORM_SCALE_Y | |
| 7 | TRANSFORM_ROTATE | |
| 8 | TRANSFORM_SKEW_X | |
| 9 | TRANSFORM_SKEW_Y |
TextRun message · 6 fields
| # | label | type | name | notes |
|---|---|---|---|---|
| 1 | optional | string | t | |
| 3 | optional | int32 | flags | |
| 6 | optional | TextRunCss | css | |
| 7 | optional | uint32 | class_id | |
| 16 | optional | BreakKind | break_before | |
| 30 | optional | int32 | inline_gap_mpx |
ParagraphList message · 9 fields
| # | label | type | name | notes |
|---|---|---|---|---|
| 1 | single | Kind | kind | |
| 2 | optional | string | marker_text | |
| 3 | optional | uint32 | marker_font_ref_id | |
| 4 | optional | uint32 | marker_color_ref_id | |
| 5 | optional | uint32 | marker_size_ref_id | |
| 6 | optional | string | autonum_type | |
| 7 | optional | int32 | start_at | |
| 8 | optional | string | marker_image_src | |
| 9 | optional | bytes | marker_image_hash |
ParagraphList. Kind enum · 3 values
| # | name | notes |
|---|---|---|
| 0 | KIND_NONE | |
| 1 | KIND_BULLET | |
| 2 | KIND_NUMBER |
ParagraphCss message · 16 fields
| # | label | type | name | notes |
|---|---|---|---|---|
| 1 | optional | TextAlign | text_align | |
| 2 | optional | int32 | line_height | |
| 3 | optional | int32 | margin_top | |
| 4 | optional | int32 | margin_bottom | |
| 5 | optional | int32 | padding_left | |
| 6 | optional | int32 | padding_right | |
| 7 | optional | int32 | text_indent | |
| 8 | optional | int32 | letter_spacing | |
| 9 | optional | int32 | word_spacing | |
| 10 | optional | bool | white_space_nowrap | |
| 11 | optional | TextOverflow | text_overflow | |
| 12 | optional | int32 | max_width | |
| 13 | optional | ParagraphCssExtra | extra | |
| 14 | optional | uint32 | line_height_ref_id | |
| 15 | optional | uint32 | letter_spacing_ref_id | |
| 16 | optional | uint32 | word_spacing_ref_id |
Paragraph message · 5 fields
| # | label | type | name | notes |
|---|---|---|---|---|
| 1 | repeated | TextRun | runs | |
| 2 | optional | ParagraphCss | css | |
| 3 | optional | uint32 | class_id | |
| 4 | optional | int32 | list_level | |
| 5 | optional | ParagraphList | list |
TextBox message · 25 fields
| # | label | type | name | notes |
|---|---|---|---|---|
| 1 | single | uint32 | id | |
| 3 | repeated | int32 | b | Packed position: [left, top, width, height, z] [packed = true] |
| 6 | repeated | Paragraph | paragraphs | |
| 7 | optional | uint32 | class_id | |
| 39 | optional | string | t | |
| 40 | optional | float | c | Controlled by STORE_TEXT_TEXTBOX_LEVEL |
| 41 | optional | string | alt_text | Controlled by STORE_CONFIDENCE |
| 42 | optional | string | text_warp | |
| 43 | optional | int32 | shadow_dx | prstTxWarp preset name (e.g., "textWave1") Outer shadow (maps to CSS box-shadow or filter: drop-shadow) |
| 44 | optional | int32 | shadow_dy | |
| 45 | optional | int32 | shadow_blur | |
| 46 | optional | uint32 | shadow_color_ref_id | |
| 47 | optional | int32 | shadow_spread | |
| 48 | optional | int32 | inner_shadow_dx | opacity * 1000 (1000 = fully opaque) Inner shadow (maps to CSS box-shadow: inset) |
| 49 | optional | int32 | inner_shadow_dy | |
| 50 | optional | int32 | inner_shadow_blur | |
| 51 | optional | uint32 | inner_shadow_color_ref_id | |
| 52 | optional | int32 | inner_shadow_spread | |
| 53 | optional | int32 | glow_radius | opacity × 1000 Glow (maps to CSS filter: drop-shadow(0 0 Npx color)) |
| 54 | optional | uint32 | glow_color_ref_id | |
| 55 | optional | int32 | soft_edge_radius | Soft edge (maps to CSS mask-image feathering) |
| 56 | optional | int32 | fill_opacity | Fill opacity (maps to CSS opacity) |
| 57 | optional | AutofitMode | autofit | 0-1000 (1000 = fully opaque) |
| 58 | optional | int32 | rotation_cdeg | Geometric transform applied to the textbox AFTER placement. Mirrors ContentItem.rotation_cdeg / flip semantics. The packed `b` field carries the UN-ROTATED axis-aligned bounding box: the renderer rotates the box around its own center so `b` remains the "editable" rect users see. Used by the PDF pipeline to preserve upside-down / rotated text stamps (e.g. arXiv margin markers) without flattening them to axis-aligned horizontal text. Extractors for OOXML shape text should also populate these from xfrm@rot when the shape body carries text. rotation_cdeg: clockwise rotation in centi-degrees (1/100°). 0 = no rotation. Range: [0, 36000). Convert to CSS: rotation_cdeg / 100. From OOXML xfrm@rot (60000ths of a degree): rotation_cdeg = rot / 600. |
| 59 | optional | uint32 | flip | flip: bitmask — bit 0 (0x1) = flipH, bit 1 (0x2) = flipV. Rare for PDF (mirror-writing stamps); emitted by OOXML when xfrm has flipH/flipV on a text-bearing shape. |
ContentAssetKind enum · 6 values
| # | name | notes |
|---|---|---|
| 0 | CONTENT_ASSET_KIND_UNSPECIFIED | |
| 1 | CONTENT_ASSET_KIND_SVG | |
| 2 | CONTENT_ASSET_KIND_IMAGE | |
| 3 | CONTENT_ASSET_KIND_CHART | |
| 4 | CONTENT_ASSET_KIND_VIDEO | |
| 5 | CONTENT_ASSET_KIND_AUDIO |
ContentPlacementCoordSpace enum · 3 values
| # | name | notes |
|---|---|---|
| 0 | CONTENT_PLACEMENT_COORD_SPACE_UNSPECIFIED | |
| 1 | CONTENT_PLACEMENT_COORD_SPACE_PAGE | |
| 2 | CONTENT_PLACEMENT_COORD_SPACE_FRAME_LOCAL |
ContentItemAssetRef message · 5 fields
| # | label | type | name | notes |
|---|---|---|---|---|
| 1 | single | ContentAssetKind | kind | |
| 2 | optional | uint32 | legacy_numeric_ref | Compatibility join key for older producers that route frame content by numeric offsets such as image_index + 10000. New consumers should prefer kind + content_id/hash/src below. |
| 3 | optional | uint32 | content_id | Reusable visual asset/content identity. This is not a placement id. |
| 4 | optional | bytes | hash | |
| 5 | optional | string | src |
ContentItemPlacement message · 11 fields
| # | label | type | name | notes |
|---|---|---|---|---|
| 1 | optional | uint32 | contract_version | Placement contract version. Version 1 is additive: legacy ContentItem.id, src/hash, and b remain populated and readable. |
| 2 | optional | uint32 | capability_flags | Capability flags: 0x01 = explicit placement id 0x02 = typed asset ref 0x04 = page-space bbox present 0x08 = frame-local bbox present 0x10 = rasterization type mirrored on placement 0x20 = transform present |
| 3 | optional | uint32 | placement_id | |
| 4 | optional | ContentItemAssetRef | asset | |
| 5 | optional | ContentPlacementCoordSpace | b_coord_space | |
| 6 | repeated | int32 | page_bbox | Packed [left, top, width, height, z] in page coordinates. [packed = true] |
| 7 | repeated | int32 | frame_local_bbox | Packed [left, top, width, height, z] in the owning frame's local space. [packed = true] |
| 8 | repeated | float | transform | Packed [a, b, c, d, e, f] placement transform when captured. [packed = true] |
| 9 | optional | int32 | z | |
| 10 | optional | uint32 | owner_id | |
| 11 | optional | uint32 | rasterization_type |
ContentItem message · 44 fields
| # | label | type | name | notes |
|---|---|---|---|---|
| 1 | single | uint32 | id | src=icon/thumb; media.source=playable blob |
| 2 | single | ContentType | type | |
| 3 | optional | string | src | |
| 4 | repeated | int32 | b | Packed position: [left, top, width, height, z] [packed = true] |
| 6 | optional | bytes | hash | |
| 9 | optional | int32 | crop_left | Image crop percentages (0-100000, where 100000 = 100%) Applied as inset crop from each edge |
| 10 | optional | int32 | crop_top | |
| 11 | optional | int32 | crop_right | |
| 12 | optional | int32 | crop_bottom | |
| 13 | optional | string | alt_text | |
| 14 | optional | int32 | shadow_dx | Outer shadow (maps to CSS box-shadow or filter: drop-shadow) |
| 15 | optional | int32 | shadow_dy | X offset in milli-pixels |
| 16 | optional | int32 | shadow_blur | Y offset in milli-pixels |
| 17 | optional | uint32 | shadow_color_ref_id | blur radius in milli-pixels |
| 18 | optional | int32 | shadow_spread | registered color ref |
| 19 | optional | int32 | inner_shadow_dx | opacity * 1000 (1000 = fully opaque) Inner shadow (maps to CSS box-shadow: inset) |
| 20 | optional | int32 | inner_shadow_dy | |
| 21 | optional | int32 | inner_shadow_blur | |
| 22 | optional | uint32 | inner_shadow_color_ref_id | |
| 23 | optional | int32 | inner_shadow_spread | |
| 24 | optional | int32 | glow_radius | opacity × 1000 Glow (maps to CSS filter: drop-shadow(0 0 Npx color)) |
| 25 | optional | uint32 | glow_color_ref_id | radius in milli-pixels |
| 26 | optional | int32 | soft_edge_radius | Soft edge (maps to CSS mask-image feathering) |
| 27 | optional | int32 | fill_opacity | radius in milli-pixels Fill opacity (maps to CSS opacity) |
| 28 | optional | string | image_fill_src | 0-1000 (1000 = fully opaque) Image fill (maps to CSS background-image on shape) |
| 29 | optional | bytes | image_fill_hash | image filename in img/ |
| 30 | optional | int32 | image_fill_mode | BLAKE3-128 hash (16 bytes) |
| 31 | optional | int32 | image_fill_rect_l | 0=stretch, 1=tile Stretch fillRect insets (0-100000, where 100000 = 100%) |
| 32 | optional | int32 | image_fill_rect_t | |
| 33 | optional | int32 | image_fill_rect_r | |
| 34 | optional | int32 | image_fill_rect_b | |
| 35 | optional | string | css_filter | CSS filter string (e.g. "grayscale(1)" or "brightness(0.50) contrast(1.20)") |
| 50 | optional | Media | media | Structured media descriptor. The C extractor populates intent + source + playback only. embed_url / provider / thumb_url are resolved by the service layer. |
| 51 | optional | int32 | rotation_cdeg | Geometric transform applied to the content item AFTER placement. SVG items (type=SVG) bake rotation into the SVG file — leave these unset. IMG / VIDEO / AUDIO items must use these fields so the renderer can apply the correct CSS transform: rotate(rotation_cdeg/100 deg) scaleX(-1) etc. rotation_cdeg: clockwise rotation in centi-degrees (1/100°). 0 = no rotation. Range: [0, 36000). Convert to CSS: rotation_cdeg / 100. From OOXML xfrm@rot (60000ths of a degree): rotation_cdeg = rot / 600. |
| 52 | optional | uint32 | flip | flip: bitmask — bit 0 (0x1) = flipH, bit 1 (0x2) = flipV. |
| 54 | optional | int32 | border_width_mpx | Picture / shape border from <p:spPr><a:ln>. Maps directly onto CSS: border: {border_width_mpx / 1000}px {border_dash} var(--ref-{border_color_ref_id}); border-radius: {border_radius_mpx / 1000}px; All unset → no border; renderer leaves the picture unwrapped. |
| 55 | optional | uint32 | border_color_ref_id | |
| 56 | optional | int32 | border_dash | LineDash string mapped to CSS border-style: 0 = unset (solid default), 1 = solid, 2 = dotted, 3 = dashed, 4..11 = the remaining DrawingML prstDash values (lgDash, dashDot, lgDashDot, lgDashDotDot, sysDash, sysDot, sysDashDot, sysDashDotDot). |
| 57 | optional | int32 | border_radius_mpx | Corner radius for rectangular pics with <a:prstGeom prst="roundRect">. Emitted when adj1 resolves to a concrete radius; unset = square corners. |
| 70 | optional | uint32 | rasterization_type | Integer source-family type for cheap editor routing. Absence/0 means an ordinary visual with no known typed source data; non-zero means the client can lazy-load the matching parser/editor and fetch the hash-addressed visual source data: blob-raster-sources/{hash}/t{rasterization_type}.pb 1=text, 2=chart, 3=smartart, 4=drawing, 5=media, 6=OLE / embedded object (eddocu.page.v3.OleSourceData), 7=ink raster source (eddocu.page.v3.InkSourceData). |
| 71 | optional | uint64 | source_op_id | Stable source operation identity for PDF-derived visual items. These are metadata-only join keys into extraction/debug sidecars; renderers must not use them for layout or route decisions. |
| 72 | optional | int32 | source_page_op_seq | |
| 73 | optional | uint32 | draw_op_index | |
| 74 | optional | ContentItemPlacement | placement | Additive placement metadata for v7 output. Renderers must continue to honor legacy b/src/hash fields until the explicit-placement capability is made mandatory. |
ContentItem. ContentType enum · 5 values
| # | name | notes |
|---|---|---|
| 0 | SVG | |
| 1 | IMG | |
| 3 | CHART | |
| 4 | VIDEO | |
| 5 | AUDIO | src=poster frame; media.source=playable blob |
FrameKind enum · 6 values
| # | name | notes |
|---|---|---|
| 0 | FRAME_KIND_UNSPECIFIED | |
| 1 | FRAME_KIND_GROUP | |
| 2 | FRAME_KIND_MATRIX | |
| 3 | FRAME_KIND_TABLE_REGION | |
| 4 | FRAME_KIND_EQUATION | |
| 5 | FRAME_KIND_GRAPHICS_TEXT |
Frame message · 11 fields
| # | label | type | name | notes |
|---|---|---|---|---|
| 1 | single | uint32 | id | |
| 2 | optional | int32 | original_frame_id | |
| 4 | optional | float | opacity | |
| 5 | optional | BlendMode | blend_mode | |
| 6 | repeated | int32 | clip_rect | Packed position for clip rect: [left, top, width, height, z] [packed = true] |
| 7 | optional | string | clip_path | |
| 8 | repeated | ContentItem | content | Asset-only frame content. Coordinates are structural: a child stored here is local to this frame. |
| 9 | optional | FrameKind | kind | Typed frame children. When present, child positions are local to this frame's clip_rect origin. This is the preferred representation for matrix/table/equation regions where text, paint, and links must be grouped without flattening meaningful text into an opaque SVG. |
| 10 | repeated | PageContent | children | |
| 11 | optional | string | semantic_text | |
| 12 | optional | uint32 | flags | Fallback flags bitmask: 0x01 = CHILDREN_ARE_FRAME_LOCAL 0x02 = APPROXIMATE_STRUCTURE 0x04 = SUPPRESSES_PAGE_LEVEL_SOURCE_TEXT |
ShapeKind enum · 7 values
| # | name | notes |
|---|---|---|
| 0 | SHAPE_KIND_UNSPECIFIED | |
| 1 | SHAPE_KIND_RECT | |
| 2 | SHAPE_KIND_ROUND_RECT | |
| 3 | SHAPE_KIND_ELLIPSE | |
| 4 | SHAPE_KIND_LINE | |
| 5 | SHAPE_KIND_PRESET | |
| 6 | SHAPE_KIND_CUSTOM_PATH |
Shape message · 16 fields
| # | label | type | name | notes |
|---|---|---|---|---|
| 1 | single | uint32 | id | |
| 2 | repeated | int32 | b | [packed = true] |
| 3 | single | ShapeKind | kind | |
| 4 | optional | uint32 | preset_id | |
| 5 | repeated | int32 | adj | [packed = true] |
| 10 | single | uint32 | fill_ref | |
| 11 | single | uint32 | stroke_ref | |
| 12 | single | uint32 | effect_ref | |
| 13 | single | int32 | stroke_width_mpx | |
| 14 | single | uint32 | stroke_dash | |
| 20 | single | int32 | rotation_cdeg | |
| 21 | single | uint32 | flip | |
| 30 | repeated | int32 | text_rect | [packed = true] |
| 31 | single | uint32 | flags | |
| 40 | optional | uint32 | drawing_source_id | |
| 41 | optional | string | alt_text |
HardChar message · 5 fields
| # | label | type | name | notes |
|---|---|---|---|---|
| 1 | single | uint32 | id | |
| 2 | optional | string | t | |
| 3 | repeated | int32 | b | Packed position: [left, top, width, height, z] [packed = true] |
| 5 | optional | HardCharCss | css | |
| 6 | optional | uint32 | class_id |
HardChar. HardCharCss message · 8 fields
| # | label | type | name | notes |
|---|---|---|---|---|
| 1 | optional | uint32 | font_ref_id | |
| 2 | optional | uint32 | color_ref_id | |
| 3 | optional | uint32 | font_size_ref_id | |
| 4 | optional | int32 | font_weight | |
| 5 | optional | uint32 | flags | Span Flags Bitmask: 0x01: italic 0x02: underline 0x04: strikethrough |
| 6 | optional | uint32 | font_size | |
| 7 | optional | ColorValue | color | |
| 8 | optional | TextShaping | text_shaping |
TableCell message · 45 fields
Per-side border styling for table cells.
Encoded in a bit-packed uint32:
bits 0..15 (16 bits): width in mpx (0..65535) — 1000 = 1px
bits 16..23 ( 8 bits): dash enum (0=unset/solid,1=solid,2=dot,
3=dash,4=lgDash,5=dashDot,6=lgDashDot,
7=lgDashDotDot)
bits 24..27 ( 4 bits): compound (0=single,1=dbl,2=thickThin,
3=thinThick,4=tri)
bits 28..31 ( 4 bits): cap (0=flat,1=round,2=square)
Use alongside the *_color_ref_id fields — color stays a separate ref
because it dedups across many cells.
Why packed: borders are set on every cell × 4 sides. Going from four
optional fields (width/dash/compound/cap) per side to one packed
uint32 saves ~12 bytes per styled cell per side, ~5KB for a
20-row × 10-col deck with full borders.
| # | label | type | name | notes |
|---|---|---|---|---|
| 1 | repeated | Paragraph | paragraphs | |
| 2 | optional | int32 | grid_span | Cell spanning |
| 3 | optional | bool | v_merge | horizontal span (default 1) |
| 4 | optional | int32 | row_span | true = vertical merge continuation (skip content) |
| 5 | optional | uint32 | bg_color_ref_id | vertical span on the originating cell (default 1) Cell fill (solid color shortcut; full fill lives in `fill_desc`). |
| 6 | optional | int32 | margin_left | Cell margins in milli-pixels |
| 7 | optional | int32 | margin_right | |
| 8 | optional | int32 | margin_top | |
| 9 | optional | int32 | margin_bottom | |
| 10 | optional | int32 | border_left_width | Cell borders [width_mpx, color_ref_id] per side. Packed border fields below take precedence when present. |
| 11 | optional | uint32 | border_left_color_ref_id | |
| 12 | optional | int32 | border_right_width | |
| 13 | optional | uint32 | border_right_color_ref_id | |
| 14 | optional | int32 | border_top_width | |
| 15 | optional | uint32 | border_top_color_ref_id | |
| 16 | optional | int32 | border_bottom_width | |
| 17 | optional | uint32 | border_bottom_color_ref_id | |
| 18 | optional | int32 | anchor | Text body properties |
| 19 | optional | int32 | vert | vertical align: 0=unset, 1=top, 2=center, 3=bottom |
| 20 | optional | int32 | anchor_ctr | text direction: 0=horz, 1=vert, 2=vert270 Horizontal anchor — a:tcPr @anchorCtr. Independent of `anchor`, which is vertical. 0=unset, 1=true (center inline content). |
| 21 | optional | uint32 | border_left_pack | Packed border styling per side — width + dash + compound + cap in one uint32. See comment on the message for bit layout. When nonzero, packed values take precedence over the per-side width fields. |
| 22 | optional | uint32 | border_right_pack | |
| 23 | optional | uint32 | border_top_pack | |
| 24 | optional | uint32 | border_bottom_pack | |
| 25 | optional | uint32 | border_diag_tl_br_pack | Diagonal borders — OOXML a:lnTlToBr and a:lnBlToTr. Width lives in the packed field (same layout as cardinal sides); color_ref_id is separate. 0 = no diagonal. |
| 26 | optional | uint32 | border_diag_tl_br_color_ref_id | |
| 27 | optional | uint32 | border_diag_bl_tr_pack | |
| 28 | optional | uint32 | border_diag_bl_tr_color_ref_id | |
| 29 | optional | string | fill_desc | Rich cell fill — gradient / pattern / image. When present, renderer prefers this over bg_color_ref_id (which becomes a solid fallback). Stored as a compact CSS-ish descriptor string so the renderer can map straight to `background:` without re-resolving proto structures. Schema: "linear:<angle_deg>:<stop0>:<stop1>:..." "radial:<cx>:<cy>:<stop0>:..." "pattern:<preset>:<fg_ref>:<bg_ref>" "image:<filename>:<mode>:<tile_sx>:<tile_sy>" where <stopN> = "<pos_pct>/<rgba_hex>/<opacity_pct>". |
| 30 | optional | uint32 | flags | Fallback flags bitmask for renderer/editor hints such as "skip borders on merged interior" or "has vertical text mode". 0x01 = HMERGE_CONTINUATION (cell occupies span of a gridSpan origin) — kept for editors that need to round-trip horizontal merges. |
| 31 | optional | int32 | row | === Container shape ===================================================== A cell is a real container node in the editor's node buffer. Anything that can appear on a page (textbox, image, svg, shape, nested frame, nested table) can appear inside a cell — each entry becomes a child node whose parentIndex points at this cell. Coordinates on the nested content are local to the cell's origin, same as Frame.children. Explicit (row, col) coordinates live on the cell when the enclosing Table uses the flat `cells` shape (see Table.cells). When the cell is inside `Table.rows[].cells`, these are ignored and position is inferred from array indices. |
| 32 | optional | int32 | col | |
| 33 | repeated | PageContent | content | |
| 35 | optional | string | border_left_fill_desc | Rich border fills for cardinal and diagonal borders. Uses the same compact descriptor syntax as fill_desc so renderers can paint gradient/pattern/ image border overlays without rehydrating DrawingML. |
| 36 | optional | string | border_right_fill_desc | |
| 37 | optional | string | border_top_fill_desc | |
| 38 | optional | string | border_bottom_fill_desc | |
| 39 | optional | string | border_diag_tl_br_fill_desc | |
| 40 | optional | string | border_diag_bl_tr_fill_desc | |
| 41 | optional | uint32 | border_left_join | OOXML line join metadata for table borders: 1=miter, 2=round, 3=bevel. |
| 42 | optional | uint32 | border_right_join | |
| 43 | optional | uint32 | border_top_join | |
| 44 | optional | uint32 | border_bottom_join | |
| 45 | optional | uint32 | border_diag_tl_br_join | |
| 46 | optional | uint32 | border_diag_bl_tr_join |
TableRow message · 2 fields
| # | label | type | name | notes |
|---|---|---|---|---|
| 1 | repeated | TableCell | cells | |
| 2 | optional | int32 | height |
Table message · 10 fields
row height in milli-pixels
| # | label | type | name | notes |
|---|---|---|---|---|
| 1 | single | uint32 | id | |
| 2 | repeated | int32 | b | Packed position: [left, top, width, height, z] [packed = true] |
| 3 | repeated | int32 | col_widths | Column widths in milli-pixels (one per column) [packed = true] |
| 4 | repeated | TableRow | rows | Nested row shape. When `cells` (field 9) is present it takes precedence and `rows` is ignored. |
| 5 | optional | uint32 | flags | Table property flags bitmask: 0x01: firstRow, 0x02: lastRow, 0x04: firstCol, 0x08: lastCol, 0x10: bandRow, 0x20: bandCol, 0x40: rtl |
| 6 | optional | string | style_id | Table style ID (GUID string from tableStyleId). Kept for round-tripping and editor display; renderer relies on pre-resolved per-cell styles that the extractor bakes in at parse time (corner > first/last > band > whole inheritance already applied). |
| 7 | optional | uint32 | table_fill_color_ref_id | Whole-table fill (a:tblPr > a:blipFill or a:gradFill or a:solidFill). Solid shortcut + rich descriptor, same schema as TableCell.fill_desc. |
| 8 | optional | string | table_fill_desc | |
| 9 | repeated | TableCell | cells | === Container shape (new, preferred) ================================== Flat cell list with explicit (row, col) on each cell. Eliminates the nested TableRow container and the v_merge ghost-cell signalling; spans are expressed directly via TableCell.grid_span / TableCell.row_span. When `cells` is non-empty the packer ignores `rows`. `row_heights` likewise takes precedence over per-row heights in `rows`. Editor writes produced by user edits MUST use this shape. |
| 10 | repeated | int32 | row_heights | [packed = true] |
PageContent message · 6 fields
| # | label | type | name | notes |
|---|---|---|---|---|
| 1 | single | Frame | frame oneof content_item | |
| 2 | single | TextBox | textbox oneof content_item | |
| 4 | single | ContentItem | item oneof content_item | |
| 5 | single | HardChar | hardchar oneof content_item | |
| 6 | single | Table | table oneof content_item | |
| 7 | single | Shape | shape oneof content_item |
PageInfo message · 7 fields
| # | label | type | name | notes |
|---|---|---|---|---|
| 1 | single | int32 | width | |
| 2 | single | int32 | height | |
| 3 | optional | ColorValue | background_color | |
| 4 | optional | uint32 | background_color_ref_id | |
| 5 | optional | uint32 | width_ref_id | |
| 6 | optional | uint32 | height_ref_id | |
| 7 | optional | bool | hidden |
AnimationPresetClass enum · 6 values
Hot-path animation summary attached directly to the page so renderers can
animate the right element without loading the cold AnimSourceData sidecar.
`target_content_item_id == 0` means the animation targets the page itself.
The detailed timing graph (sub-effects, command lists, advanced filters)
stays in `blob-raster-sources/{slide_hash}/t8.pb` (`AnimSourceData`).
| # | name | notes |
|---|---|---|
| 0 | ANIMATION_PRESET_CLASS_UNSPECIFIED | |
| 1 | ANIMATION_PRESET_CLASS_ENTRANCE | |
| 2 | ANIMATION_PRESET_CLASS_EMPHASIS | |
| 3 | ANIMATION_PRESET_CLASS_EXIT | |
| 4 | ANIMATION_PRESET_CLASS_MOTION_PATH | |
| 5 | ANIMATION_PRESET_CLASS_MEDIA |
AnimationTrigger enum · 4 values
| # | name | notes |
|---|---|---|
| 0 | ANIMATION_TRIGGER_UNSPECIFIED | |
| 1 | ANIMATION_TRIGGER_ON_CLICK | |
| 2 | ANIMATION_TRIGGER_WITH_PREVIOUS | |
| 3 | ANIMATION_TRIGGER_AFTER_PREVIOUS |
PageAnimation message · 6 fields
| # | label | type | name | notes |
|---|---|---|---|---|
| 1 | single | uint32 | target_content_item_id | |
| 2 | single | AnimationPresetClass | preset_class | |
| 3 | single | uint32 | preset_id | |
| 4 | single | uint32 | duration_ms | |
| 5 | single | uint32 | delay_ms | |
| 6 | single | AnimationTrigger | trigger |
PageTransition message · 4 fields
Hot-path slide transition summary. Cold sidecar carries the rest of the
timing graph as `blob-raster-sources/{slide_hash}/t9.pb`.
| # | label | type | name | notes |
|---|---|---|---|---|
| 1 | single | string | preset | |
| 2 | single | uint32 | duration_ms | e.g. "fade", "push", "wipe" |
| 3 | single | bool | advance_on_click | |
| 4 | single | uint32 | advance_after_ms |
Page message · 7 fields
| # | label | type | name | notes |
|---|---|---|---|---|
| 1 | optional | int32 | pageindex | |
| 2 | optional | PageInfo | page | |
| 3 | repeated | PageContent | content | |
| 4 | repeated | PageAnimation | animations | |
| 5 | optional | PageTransition | transition | |
| 10 | optional | uint32 | output_contract_version | Output contract markers. Bit 0x01 means ContentItem.placement is emitted for asset-backed items while legacy fields remain populated. |
| 11 | optional | uint32 | output_capability_flags |
§Raw schema
syntax = "proto3";
package eddocu.page.v3;
import "css_enums.proto";
import "css_extended.proto";
import "reftable.proto";
import "contentitem/media.proto";
enum InkSourceFormat {
INK_SOURCE_FORMAT_UNSPECIFIED = 0;
INK_SOURCE_FORMAT_PPTX_CONTENT_PART_INKML = 1;
INK_SOURCE_FORMAT_PPTX_P14_INLINE_INK = 2;
INK_SOURCE_FORMAT_CUSTOM_GEOMETRY_INK = 3;
INK_SOURCE_FORMAT_OPAQUE_BLOB = 4;
}
// Lazy source payload for raster-authoritative ink visuals. The hot page item
// remains a normal IMG; this sidecar exists only when ContentItem has
// rasterization_type=7 and the caller wants to inspect/edit the source ink.
message InkSourceData {
InkSourceFormat source_format = 1;
optional string raw_xml = 20;
optional bytes raw_blob = 21;
optional bytes source_sha256 = 22;
}
// Lazy source payload for OLE / object embeddings. Visible body remains an
// ordinary IMG / Picture in the hot page; this sidecar exists only when
// ContentItem has rasterization_type=6 and the caller wants the original
// embedded object bytes (e.g., embedded Excel/Word/Equation/PowerPoint blobs)
// for inspection, download, or in-place editing. Routed through
// blob-raster-sources/{hash}/t6.pb.
message OleSourceData {
// OOXML progId string (e.g., "Excel.Sheet.12", "Equation.3"). Empty when
// the producer did not declare one — `content_type` is the authoritative
// typed signal.
optional string prog_id = 1;
// IANA media type for the embedded blob, derived from progId or filename
// extension (e.g., "application/vnd.ms-excel",
// "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet").
optional string content_type = 2;
// Original filename of the embedded part as stored inside the package
// (e.g., "oleObject1.bin", "Microsoft_Excel_Worksheet.xlsx"). Useful for
// download / "open original" UX.
optional string source_filename = 3;
// The raw embedded blob. Always populated — OLE source payloads are
// typically modest in size and downstream consumers expect inline access.
optional bytes raw_blob = 21;
optional bytes source_sha256 = 22;
}
// ==============================================================================
// PACKED POSITION FORMAT (Variable Length)
// ==============================================================================
// All elements use a compact packed array for position and z-index.
// The array length indicates which values are present:
//
// STANDARD FORMAT (5 values): [left, top, width, height, z]
// Index 0: left - X coordinate in milli-units (1/1000th of a pixel)
// Index 1: top - Y coordinate in milli-units
// Index 2: width - Width in milli-units
// Index 3: height - Height in milli-units
// Index 4: z - Z-index for layering
//
// EXTENDED FORMAT (7 values): [left, top, width, height, z, right, bottom]
// Index 5: right - Explicit right edge (optional, for non-standard layouts)
// Index 6: bottom - Explicit bottom edge (optional, for non-standard layouts)
//
// Benefits:
// - 60% smaller wire format vs separate Rect message + z field
// - Single field for all positioning data
// - Packed encoding for maximum efficiency
// - Optional right/bottom adds negligible overhead (only when needed)
// - Array length indicates data availability (5 = standard, 7 = extended)
// ==============================================================================
enum BreakKind {
BREAK_KIND_NONE = 0;
// SOFT: hint-only line break. Renderer MAY honor it; safe to ignore for
// reflow. Currently unused by the PDF pipeline (which emits HARD instead).
BREAK_KIND_SOFT = 1;
// HARD: forced visual line break, equivalent to <br>. Used by the PDF
// pipeline to mark the boundary between visual rows that the textbox
// merger heuristically grouped into a single textbox/paragraph. The
// renderer MUST honor this so reflow does not silently re-wrap rows.
BREAK_KIND_HARD = 2;
}
enum AutofitMode {
AUTOFIT_UNSPECIFIED = 0;
AUTOFIT_NONE = 1;
AUTOFIT_SHRINK_TEXT = 2;
AUTOFIT_RESIZE_SHAPE = 3;
}
message TextRunCss {
optional uint32 font_ref_id = 1;
optional uint32 color_ref_id = 2;
optional uint32 font_size_ref_id = 3;
optional int32 font_weight = 4;
// Run flags bitmask:
// 0x01: Italic
// 0x02: Underline (presence; see underline_type for CSS style)
// 0x04: Strikethrough (presence; see strikethrough_type for CSS style)
// 0x08: Small caps
optional uint32 flags = 5;
optional uint32 line_height_ref_id = 6;
optional uint32 letter_spacing_ref_id = 7;
optional uint32 word_spacing_ref_id = 8;
optional uint32 background_color_ref_id = 9;
optional eddocu.reftable.v3.TextShaping text_shaping = 10;
optional eddocu.reftable.v3.ColorValue text_decoration_color = 11;
optional Position position = 12;
optional int32 top_offset = 13;
optional int32 bottom_offset = 14;
optional TextRunCssExtra extra = 15;
message Transform {
enum TransformType {
TRANSFORM_UNSPECIFIED = 0;
TRANSFORM_TRANSLATE_X = 1;
TRANSFORM_TRANSLATE_Y = 2;
TRANSFORM_TRANSLATE = 3;
TRANSFORM_SCALE = 4;
TRANSFORM_SCALE_X = 5;
TRANSFORM_SCALE_Y = 6;
TRANSFORM_ROTATE = 7;
TRANSFORM_SKEW_X = 8;
TRANSFORM_SKEW_Y = 9;
}
TransformType type = 1;
string value = 2;
}
repeated Transform transforms = 16;
optional string gradient_css = 17;
optional uint32 underline_type = 18;
optional uint32 strikethrough_type = 19;
optional int32 text_stroke_width = 20;
optional uint32 text_stroke_color_ref_id = 21;
optional string text_shadow_css = 22;
// Direct CSS value literals for common run-level text properties.
// Ref IDs above remain the preferred representation when the value comes
// from the ref table; these fields preserve inline CSS values without
// falling back to the generic extra.css declaration bag.
optional string font_family = 23;
optional string font_size = 24;
optional string line_height = 25;
optional string letter_spacing = 26;
optional string word_spacing = 27;
optional string color = 28;
optional string background_color = 29;
}
message TextRun {
reserved 14, 15, 17, 19 to 29;
optional string t = 1;
optional int32 flags = 3;
optional TextRunCss css = 6;
optional uint32 class_id = 7;
optional BreakKind break_before = 16;
optional int32 inline_gap_mpx = 30;
}
message ParagraphList {
enum Kind {
KIND_NONE = 0;
KIND_BULLET = 1;
KIND_NUMBER = 2;
}
Kind kind = 1;
optional string marker_text = 2;
optional uint32 marker_font_ref_id = 3;
optional uint32 marker_color_ref_id = 4;
optional uint32 marker_size_ref_id = 5;
optional string autonum_type = 6;
optional int32 start_at = 7;
optional string marker_image_src = 8;
optional bytes marker_image_hash = 9;
}
message ParagraphCss {
optional TextAlign text_align = 1;
optional int32 line_height = 2;
optional int32 margin_top = 3;
optional int32 margin_bottom = 4;
optional int32 padding_left = 5;
optional int32 padding_right = 6;
optional int32 text_indent = 7;
optional int32 letter_spacing = 8;
optional int32 word_spacing = 9;
optional bool white_space_nowrap = 10;
optional TextOverflow text_overflow = 11;
optional int32 max_width = 12;
optional ParagraphCssExtra extra = 13;
optional uint32 line_height_ref_id = 14;
optional uint32 letter_spacing_ref_id = 15;
optional uint32 word_spacing_ref_id = 16;
}
message Paragraph {
repeated TextRun runs = 1;
optional ParagraphCss css = 2;
optional uint32 class_id = 3;
optional int32 list_level = 4;
optional ParagraphList list = 5;
}
message TextBox {
reserved 60 to 69;
uint32 id = 1;
// Packed position: [left, top, width, height, z]
repeated int32 b = 3 [packed=true];
repeated Paragraph paragraphs = 6;
optional uint32 class_id = 7;
optional string t = 39; // Controlled by STORE_TEXT_TEXTBOX_LEVEL
optional float c = 40; // Controlled by STORE_CONFIDENCE
optional string alt_text = 41;
optional string text_warp = 42; // prstTxWarp preset name (e.g., "textWave1")
// Outer shadow (maps to CSS box-shadow or filter: drop-shadow)
optional int32 shadow_dx = 43;
optional int32 shadow_dy = 44;
optional int32 shadow_blur = 45;
optional uint32 shadow_color_ref_id = 46;
optional int32 shadow_spread = 47; // opacity * 1000 (1000 = fully opaque)
// Inner shadow (maps to CSS box-shadow: inset)
optional int32 inner_shadow_dx = 48;
optional int32 inner_shadow_dy = 49;
optional int32 inner_shadow_blur = 50;
optional uint32 inner_shadow_color_ref_id = 51;
optional int32 inner_shadow_spread = 52; // opacity × 1000
// Glow (maps to CSS filter: drop-shadow(0 0 Npx color))
optional int32 glow_radius = 53;
optional uint32 glow_color_ref_id = 54;
// Soft edge (maps to CSS mask-image feathering)
optional int32 soft_edge_radius = 55;
// Fill opacity (maps to CSS opacity)
optional int32 fill_opacity = 56; // 0-1000 (1000 = fully opaque)
optional AutofitMode autofit = 57;
// Geometric transform applied to the textbox AFTER placement. Mirrors
// ContentItem.rotation_cdeg / flip semantics. The packed `b` field carries
// the UN-ROTATED axis-aligned bounding box: the renderer rotates the box
// around its own center so `b` remains the "editable" rect users see.
//
// Used by the PDF pipeline to preserve upside-down / rotated text stamps
// (e.g. arXiv margin markers) without flattening them to axis-aligned
// horizontal text. Extractors for OOXML shape text should also populate
// these from xfrm@rot when the shape body carries text.
//
// rotation_cdeg: clockwise rotation in centi-degrees (1/100°).
// 0 = no rotation. Range: [0, 36000). Convert to CSS: rotation_cdeg / 100.
// From OOXML xfrm@rot (60000ths of a degree): rotation_cdeg = rot / 600.
optional int32 rotation_cdeg = 58;
// flip: bitmask — bit 0 (0x1) = flipH, bit 1 (0x2) = flipV.
// Rare for PDF (mirror-writing stamps); emitted by OOXML when xfrm has
// flipH/flipV on a text-bearing shape.
optional uint32 flip = 59;
}
enum ContentAssetKind {
CONTENT_ASSET_KIND_UNSPECIFIED = 0;
CONTENT_ASSET_KIND_SVG = 1;
CONTENT_ASSET_KIND_IMAGE = 2;
CONTENT_ASSET_KIND_CHART = 3;
CONTENT_ASSET_KIND_VIDEO = 4;
CONTENT_ASSET_KIND_AUDIO = 5;
}
enum ContentPlacementCoordSpace {
CONTENT_PLACEMENT_COORD_SPACE_UNSPECIFIED = 0;
CONTENT_PLACEMENT_COORD_SPACE_PAGE = 1;
CONTENT_PLACEMENT_COORD_SPACE_FRAME_LOCAL = 2;
}
message ContentItemAssetRef {
ContentAssetKind kind = 1;
// Compatibility join key for older producers that route frame content by
// numeric offsets such as image_index + 10000. New consumers should prefer
// kind + content_id/hash/src below.
optional uint32 legacy_numeric_ref = 2;
// Reusable visual asset/content identity. This is not a placement id.
optional uint32 content_id = 3;
optional bytes hash = 4;
optional string src = 5;
}
message ContentItemPlacement {
// Placement contract version. Version 1 is additive: legacy ContentItem.id,
// src/hash, and b remain populated and readable.
optional uint32 contract_version = 1;
// Capability flags:
// 0x01 = explicit placement id
// 0x02 = typed asset ref
// 0x04 = page-space bbox present
// 0x08 = frame-local bbox present
// 0x10 = rasterization type mirrored on placement
// 0x20 = transform present
optional uint32 capability_flags = 2;
optional uint32 placement_id = 3;
optional ContentItemAssetRef asset = 4;
optional ContentPlacementCoordSpace b_coord_space = 5;
// Packed [left, top, width, height, z] in page coordinates.
repeated int32 page_bbox = 6 [packed=true];
// Packed [left, top, width, height, z] in the owning frame's local space.
repeated int32 frame_local_bbox = 7 [packed=true];
// Packed [a, b, c, d, e, f] placement transform when captured.
repeated float transform = 8 [packed=true];
optional int32 z = 9;
optional uint32 owner_id = 10;
optional uint32 rasterization_type = 11;
}
message ContentItem {
reserved 7, 8, 60 to 67;
enum ContentType {
// 2 was DRAWING — reserved at v3 schema bump after audit confirmed no
// extractor path ever set it (2026-05-04). Value stays unused on the
// wire; clients can ignore it. Keep the slot so existing decoded
// payloads referencing the tag stay backwards-compatible if any
// historical data is rehydrated.
reserved 2;
reserved "DRAWING";
SVG = 0;
IMG = 1;
CHART = 3;
VIDEO = 4; // src=poster frame; media.source=playable blob
AUDIO = 5; // src=icon/thumb; media.source=playable blob
}
uint32 id = 1;
ContentType type = 2;
optional string src = 3;
// Packed position: [left, top, width, height, z]
repeated int32 b = 4 [packed=true];
optional bytes hash = 6;
// Image crop percentages (0-100000, where 100000 = 100%)
// Applied as inset crop from each edge
optional int32 crop_left = 9;
optional int32 crop_top = 10;
optional int32 crop_right = 11;
optional int32 crop_bottom = 12;
optional string alt_text = 13;
// Outer shadow (maps to CSS box-shadow or filter: drop-shadow)
optional int32 shadow_dx = 14; // X offset in milli-pixels
optional int32 shadow_dy = 15; // Y offset in milli-pixels
optional int32 shadow_blur = 16; // blur radius in milli-pixels
optional uint32 shadow_color_ref_id = 17; // registered color ref
optional int32 shadow_spread = 18; // opacity * 1000 (1000 = fully opaque)
// Inner shadow (maps to CSS box-shadow: inset)
optional int32 inner_shadow_dx = 19;
optional int32 inner_shadow_dy = 20;
optional int32 inner_shadow_blur = 21;
optional uint32 inner_shadow_color_ref_id = 22;
optional int32 inner_shadow_spread = 23; // opacity × 1000
// Glow (maps to CSS filter: drop-shadow(0 0 Npx color))
optional int32 glow_radius = 24; // radius in milli-pixels
optional uint32 glow_color_ref_id = 25;
// Soft edge (maps to CSS mask-image feathering)
optional int32 soft_edge_radius = 26; // radius in milli-pixels
// Fill opacity (maps to CSS opacity)
optional int32 fill_opacity = 27; // 0-1000 (1000 = fully opaque)
// Image fill (maps to CSS background-image on shape)
optional string image_fill_src = 28; // image filename in img/
optional bytes image_fill_hash = 29; // BLAKE3-128 hash (16 bytes)
optional int32 image_fill_mode = 30; // 0=stretch, 1=tile
// Stretch fillRect insets (0-100000, where 100000 = 100%)
optional int32 image_fill_rect_l = 31;
optional int32 image_fill_rect_t = 32;
optional int32 image_fill_rect_r = 33;
optional int32 image_fill_rect_b = 34;
// CSS filter string (e.g. "grayscale(1)" or "brightness(0.50) contrast(1.20)")
optional string css_filter = 35;
// Structured media descriptor.
// The C extractor populates intent + source + playback only.
// embed_url / provider / thumb_url are resolved by the service layer.
optional eddocu.media.v3.Media media = 50;
// Geometric transform applied to the content item AFTER placement.
// SVG items (type=SVG) bake rotation into the SVG file — leave these unset.
// IMG / VIDEO / AUDIO items must use these fields so the renderer can apply
// the correct CSS transform: rotate(rotation_cdeg/100 deg) scaleX(-1) etc.
//
// rotation_cdeg: clockwise rotation in centi-degrees (1/100°).
// 0 = no rotation. Range: [0, 36000). Convert to CSS: rotation_cdeg / 100.
// From OOXML xfrm@rot (60000ths of a degree): rotation_cdeg = rot / 600.
optional int32 rotation_cdeg = 51;
// flip: bitmask — bit 0 (0x1) = flipH, bit 1 (0x2) = flipV.
optional uint32 flip = 52;
// Picture / shape border from <p:spPr><a:ln>. Maps directly onto CSS:
// border: {border_width_mpx / 1000}px {border_dash} var(--ref-{border_color_ref_id});
// border-radius: {border_radius_mpx / 1000}px;
// All unset → no border; renderer leaves the picture unwrapped.
optional int32 border_width_mpx = 54;
optional uint32 border_color_ref_id = 55;
// LineDash string mapped to CSS border-style:
// 0 = unset (solid default), 1 = solid, 2 = dotted, 3 = dashed,
// 4..11 = the remaining DrawingML prstDash values (lgDash, dashDot,
// lgDashDot, lgDashDotDot, sysDash, sysDot, sysDashDot, sysDashDotDot).
optional int32 border_dash = 56;
// Corner radius for rectangular pics with <a:prstGeom prst="roundRect">.
// Emitted when adj1 resolves to a concrete radius; unset = square corners.
optional int32 border_radius_mpx = 57;
// Integer source-family type for cheap editor routing. Absence/0 means an
// ordinary visual with no known typed source data; non-zero means the client
// can lazy-load the matching parser/editor and fetch the hash-addressed
// visual source data:
// blob-raster-sources/{hash}/t{rasterization_type}.pb
// 1=text, 2=chart, 3=smartart, 4=drawing, 5=media,
// 6=OLE / embedded object (eddocu.page.v3.OleSourceData),
// 7=ink raster source (eddocu.page.v3.InkSourceData).
optional uint32 rasterization_type = 70;
// Stable source operation identity for PDF-derived visual items. These are
// metadata-only join keys into extraction/debug sidecars; renderers must not
// use them for layout or route decisions.
optional uint64 source_op_id = 71;
optional int32 source_page_op_seq = 72;
optional uint32 draw_op_index = 73;
// Additive placement metadata for v7 output. Renderers must continue to
// honor legacy b/src/hash fields until the explicit-placement capability is
// made mandatory.
optional ContentItemPlacement placement = 74;
}
enum FrameKind {
FRAME_KIND_UNSPECIFIED = 0;
FRAME_KIND_GROUP = 1;
FRAME_KIND_MATRIX = 2;
FRAME_KIND_TABLE_REGION = 3;
FRAME_KIND_EQUATION = 4;
FRAME_KIND_GRAPHICS_TEXT = 5;
}
message Frame {
uint32 id = 1;
optional int32 original_frame_id = 2;
optional float opacity = 4;
optional BlendMode blend_mode = 5;
// Packed position for clip rect: [left, top, width, height, z]
repeated int32 clip_rect = 6 [packed=true];
optional string clip_path = 7;
// Asset-only frame content. Coordinates are structural: a child stored here
// is local to this frame.
repeated ContentItem content = 8;
// Typed frame children. When present, child positions are local to this
// frame's clip_rect origin. This is the preferred representation for
// matrix/table/equation regions where text, paint, and links must be grouped
// without flattening meaningful text into an opaque SVG.
optional FrameKind kind = 9;
repeated PageContent children = 10;
optional string semantic_text = 11;
// Fallback flags bitmask:
// 0x01 = CHILDREN_ARE_FRAME_LOCAL
// 0x02 = APPROXIMATE_STRUCTURE
// 0x04 = SUPPRESSES_PAGE_LEVEL_SOURCE_TEXT
optional uint32 flags = 12;
}
enum ShapeKind {
SHAPE_KIND_UNSPECIFIED = 0;
SHAPE_KIND_RECT = 1;
SHAPE_KIND_ROUND_RECT = 2;
SHAPE_KIND_ELLIPSE = 3;
SHAPE_KIND_LINE = 4;
SHAPE_KIND_PRESET = 5;
SHAPE_KIND_CUSTOM_PATH = 6;
}
message Shape {
uint32 id = 1;
repeated int32 b = 2 [packed=true];
ShapeKind kind = 3;
optional uint32 preset_id = 4;
repeated int32 adj = 5 [packed=true];
uint32 fill_ref = 10;
uint32 stroke_ref = 11;
uint32 effect_ref = 12;
int32 stroke_width_mpx = 13;
uint32 stroke_dash = 14;
int32 rotation_cdeg = 20;
uint32 flip = 21;
repeated int32 text_rect = 30 [packed=true];
uint32 flags = 31;
optional uint32 drawing_source_id = 40;
optional string alt_text = 41;
}
message HardChar {
uint32 id = 1;
optional string t = 2;
// Packed position: [left, top, width, height, z]
repeated int32 b = 3 [packed=true];
message HardCharCss {
optional uint32 font_ref_id = 1;
optional uint32 color_ref_id = 2;
optional uint32 font_size_ref_id = 3;
optional int32 font_weight = 4;
// Span Flags Bitmask:
// 0x01: italic
// 0x02: underline
// 0x04: strikethrough
optional uint32 flags = 5;
optional uint32 font_size = 6;
optional eddocu.reftable.v3.ColorValue color = 7;
optional eddocu.reftable.v3.TextShaping text_shaping = 8;
}
optional HardCharCss css = 5;
optional uint32 class_id = 6;
}
// Per-side border styling for table cells.
// Encoded in a bit-packed uint32:
// bits 0..15 (16 bits): width in mpx (0..65535) — 1000 = 1px
// bits 16..23 ( 8 bits): dash enum (0=unset/solid,1=solid,2=dot,
// 3=dash,4=lgDash,5=dashDot,6=lgDashDot,
// 7=lgDashDotDot)
// bits 24..27 ( 4 bits): compound (0=single,1=dbl,2=thickThin,
// 3=thinThick,4=tri)
// bits 28..31 ( 4 bits): cap (0=flat,1=round,2=square)
// Use alongside the *_color_ref_id fields — color stays a separate ref
// because it dedups across many cells.
//
// Why packed: borders are set on every cell × 4 sides. Going from four
// optional fields (width/dash/compound/cap) per side to one packed
// uint32 saves ~12 bytes per styled cell per side, ~5KB for a
// 20-row × 10-col deck with full borders.
message TableCell {
repeated Paragraph paragraphs = 1;
// Cell spanning
optional int32 grid_span = 2; // horizontal span (default 1)
optional bool v_merge = 3; // true = vertical merge continuation (skip content)
optional int32 row_span = 4; // vertical span on the originating cell (default 1)
// Cell fill (solid color shortcut; full fill lives in `fill_desc`).
optional uint32 bg_color_ref_id = 5;
// Cell margins in milli-pixels
optional int32 margin_left = 6;
optional int32 margin_right = 7;
optional int32 margin_top = 8;
optional int32 margin_bottom = 9;
// Cell borders [width_mpx, color_ref_id] per side. Packed border fields
// below take precedence when present.
optional int32 border_left_width = 10;
optional uint32 border_left_color_ref_id = 11;
optional int32 border_right_width = 12;
optional uint32 border_right_color_ref_id = 13;
optional int32 border_top_width = 14;
optional uint32 border_top_color_ref_id = 15;
optional int32 border_bottom_width = 16;
optional uint32 border_bottom_color_ref_id = 17;
// Text body properties
optional int32 anchor = 18; // vertical align: 0=unset, 1=top, 2=center, 3=bottom
optional int32 vert = 19; // text direction: 0=horz, 1=vert, 2=vert270
// Horizontal anchor — a:tcPr @anchorCtr. Independent of `anchor`,
// which is vertical. 0=unset, 1=true (center inline content).
optional int32 anchor_ctr = 20;
// Packed border styling per side — width + dash + compound + cap in
// one uint32. See comment on the message for bit layout. When nonzero,
// packed values take precedence over the per-side width fields.
optional uint32 border_left_pack = 21;
optional uint32 border_right_pack = 22;
optional uint32 border_top_pack = 23;
optional uint32 border_bottom_pack = 24;
// Diagonal borders — OOXML a:lnTlToBr and a:lnBlToTr. Width lives in
// the packed field (same layout as cardinal sides); color_ref_id is
// separate. 0 = no diagonal.
optional uint32 border_diag_tl_br_pack = 25;
optional uint32 border_diag_tl_br_color_ref_id = 26;
optional uint32 border_diag_bl_tr_pack = 27;
optional uint32 border_diag_bl_tr_color_ref_id = 28;
// Rich cell fill — gradient / pattern / image. When present, renderer
// prefers this over bg_color_ref_id (which becomes a solid fallback).
// Stored as a compact CSS-ish descriptor string so the renderer can
// map straight to `background:` without re-resolving proto structures.
// Schema:
// "linear:<angle_deg>:<stop0>:<stop1>:..."
// "radial:<cx>:<cy>:<stop0>:..."
// "pattern:<preset>:<fg_ref>:<bg_ref>"
// "image:<filename>:<mode>:<tile_sx>:<tile_sy>"
// where <stopN> = "<pos_pct>/<rgba_hex>/<opacity_pct>".
optional string fill_desc = 29;
// Fallback flags bitmask for renderer/editor hints such as "skip borders on
// merged interior" or "has vertical text mode".
// 0x01 = HMERGE_CONTINUATION (cell occupies span of a gridSpan origin)
// — kept for editors that need to round-trip horizontal merges.
optional uint32 flags = 30;
// === Container shape =====================================================
//
// A cell is a real container node in the editor's node buffer. Anything
// that can appear on a page (textbox, image, svg, shape, nested frame,
// nested table) can appear inside a cell — each entry becomes a child
// node whose parentIndex points at this cell. Coordinates on the nested
// content are local to the cell's origin, same as Frame.children.
//
// Explicit (row, col) coordinates live on the cell when the enclosing
// Table uses the flat `cells` shape (see Table.cells). When the cell is
// inside `Table.rows[].cells`, these are ignored and position is inferred
// from array indices.
optional int32 row = 31;
optional int32 col = 32;
repeated PageContent content = 33;
// Rich border fills for cardinal and diagonal borders. Uses the same compact
// descriptor syntax as fill_desc so renderers can paint gradient/pattern/
// image border overlays without rehydrating DrawingML.
optional string border_left_fill_desc = 35;
optional string border_right_fill_desc = 36;
optional string border_top_fill_desc = 37;
optional string border_bottom_fill_desc = 38;
optional string border_diag_tl_br_fill_desc = 39;
optional string border_diag_bl_tr_fill_desc = 40;
// OOXML line join metadata for table borders:
// 1=miter, 2=round, 3=bevel.
optional uint32 border_left_join = 41;
optional uint32 border_right_join = 42;
optional uint32 border_top_join = 43;
optional uint32 border_bottom_join = 44;
optional uint32 border_diag_tl_br_join = 45;
optional uint32 border_diag_bl_tr_join = 46;
}
message TableRow {
repeated TableCell cells = 1;
optional int32 height = 2; // row height in milli-pixels
}
message Table {
uint32 id = 1;
// Packed position: [left, top, width, height, z]
repeated int32 b = 2 [packed=true];
// Column widths in milli-pixels (one per column)
repeated int32 col_widths = 3 [packed=true];
// Nested row shape. When `cells` (field 9) is present it takes precedence
// and `rows` is ignored.
repeated TableRow rows = 4;
// Table property flags bitmask:
// 0x01: firstRow, 0x02: lastRow, 0x04: firstCol,
// 0x08: lastCol, 0x10: bandRow, 0x20: bandCol, 0x40: rtl
optional uint32 flags = 5;
// Table style ID (GUID string from tableStyleId). Kept for round-tripping
// and editor display; renderer relies on pre-resolved per-cell styles
// that the extractor bakes in at parse time (corner > first/last >
// band > whole inheritance already applied).
optional string style_id = 6;
// Whole-table fill (a:tblPr > a:blipFill or a:gradFill or a:solidFill).
// Solid shortcut + rich descriptor, same schema as TableCell.fill_desc.
optional uint32 table_fill_color_ref_id = 7;
optional string table_fill_desc = 8;
// === Container shape (new, preferred) ==================================
//
// Flat cell list with explicit (row, col) on each cell. Eliminates the
// nested TableRow container and the v_merge ghost-cell signalling; spans
// are expressed directly via TableCell.grid_span / TableCell.row_span.
//
// When `cells` is non-empty the packer ignores `rows`. `row_heights`
// likewise takes precedence over per-row heights in `rows`.
//
// Editor writes produced by user edits MUST use this shape.
repeated TableCell cells = 9;
repeated int32 row_heights = 10 [packed=true];
}
message PageContent {
reserved 3;
oneof content_item {
Frame frame = 1;
TextBox textbox = 2;
ContentItem item = 4;
HardChar hardchar = 5;
Table table = 6;
Shape shape = 7;
}
}
message PageInfo {
int32 width = 1;
int32 height = 2;
optional eddocu.reftable.v3.ColorValue background_color = 3;
optional uint32 background_color_ref_id = 4;
optional uint32 width_ref_id = 5;
optional uint32 height_ref_id = 6;
optional bool hidden = 7;
}
// Hot-path animation summary attached directly to the page so renderers can
// animate the right element without loading the cold AnimSourceData sidecar.
// `target_content_item_id == 0` means the animation targets the page itself.
// The detailed timing graph (sub-effects, command lists, advanced filters)
// stays in `blob-raster-sources/{slide_hash}/t8.pb` (`AnimSourceData`).
enum AnimationPresetClass {
ANIMATION_PRESET_CLASS_UNSPECIFIED = 0;
ANIMATION_PRESET_CLASS_ENTRANCE = 1;
ANIMATION_PRESET_CLASS_EMPHASIS = 2;
ANIMATION_PRESET_CLASS_EXIT = 3;
ANIMATION_PRESET_CLASS_MOTION_PATH = 4;
ANIMATION_PRESET_CLASS_MEDIA = 5;
}
enum AnimationTrigger {
ANIMATION_TRIGGER_UNSPECIFIED = 0;
ANIMATION_TRIGGER_ON_CLICK = 1;
ANIMATION_TRIGGER_WITH_PREVIOUS = 2;
ANIMATION_TRIGGER_AFTER_PREVIOUS = 3;
}
message PageAnimation {
uint32 target_content_item_id = 1;
AnimationPresetClass preset_class = 2;
uint32 preset_id = 3;
uint32 duration_ms = 4;
uint32 delay_ms = 5;
AnimationTrigger trigger = 6;
}
// Hot-path slide transition summary. Cold sidecar carries the rest of the
// timing graph as `blob-raster-sources/{slide_hash}/t9.pb`.
message PageTransition {
string preset = 1; // e.g. "fade", "push", "wipe"
uint32 duration_ms = 2;
bool advance_on_click = 3;
uint32 advance_after_ms = 4;
}
message Page {
optional int32 pageindex = 1;
optional PageInfo page = 2;
repeated PageContent content = 3;
repeated PageAnimation animations = 4;
optional PageTransition transition = 5;
// Output contract markers. Bit 0x01 means ContentItem.placement is emitted
// for asset-backed items while legacy fields remain populated.
optional uint32 output_contract_version = 10;
optional uint32 output_capability_flags = 11;
}