/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;
}