rows.go 16.6 KB
Newer Older
xurime's avatar
xurime 已提交
1
// Copyright 2016 - 2020 The excelize Authors. All rights reserved. Use of
xurime's avatar
xurime 已提交
2 3 4 5 6 7
// this source code is governed by a BSD-style license that can be found in
// the LICENSE file.
//
// Package excelize providing a set of functions that allow you to write to
// and read from XLSX files. Support reads and writes XLSX file generated by
// Microsoft Excel™ 2007 and later. Support save file without losing original
8
// charts of XLSX. This library needs Go version 1.10 or later.
xurime's avatar
xurime 已提交
9

A
ahmad 已提交
10 11 12
package excelize

import (
13
	"bytes"
A
ahmad 已提交
14
	"encoding/xml"
xurime's avatar
xurime 已提交
15
	"errors"
L
Lunny Xiao 已提交
16
	"fmt"
17 18
	"io"
	"log"
19
	"math"
20
	"strconv"
A
ahmad 已提交
21 22
)

23 24
// GetRows return all the rows in a sheet by given worksheet name (case
// sensitive). For example:
25
//
26 27
//    rows, err := f.Rows("Sheet1")
//    if err != nil {
xurime's avatar
xurime 已提交
28
//        fmt.Println(err)
29 30 31 32 33
//        return
//    }
//    for rows.Next() {
//        row, err := rows.Columns()
//        if err != nil {
xurime's avatar
xurime 已提交
34
//            fmt.Println(err)
35
//        }
36
//        for _, colCell := range row {
xurime's avatar
xurime 已提交
37
//            fmt.Print(colCell, "\t")
38
//        }
xurime's avatar
xurime 已提交
39
//        fmt.Println()
40 41
//    }
//
42
func (f *File) GetRows(sheet string) ([][]string, error) {
H
Harris 已提交
43
	rows, err := f.Rows(sheet)
xurime's avatar
xurime 已提交
44 45 46
	if err != nil {
		return nil, err
	}
H
Harris 已提交
47 48 49
	results := make([][]string, 0, 64)
	for rows.Next() {
		if rows.Error() != nil {
50 51
			break
		}
H
Harris 已提交
52 53 54
		row, err := rows.Columns()
		if err != nil {
			break
55
		}
H
Harris 已提交
56
		results = append(results, row)
57
	}
H
Harris 已提交
58
	return results, nil
A
ahmad 已提交
59 60
}

L
Lunny Xiao 已提交
61 62
// Rows defines an iterator to a sheet
type Rows struct {
63 64 65 66 67 68
	err                        error
	curRow, totalRow, stashRow int
	sheet                      string
	rows                       []xlsxRow
	f                          *File
	decoder                    *xml.Decoder
L
Lunny Xiao 已提交
69 70 71 72
}

// Next will return true if find the next row element.
func (rows *Rows) Next() bool {
D
ducquangkstn 已提交
73
	rows.curRow++
74
	return rows.curRow <= rows.totalRow
L
Lunny Xiao 已提交
75 76 77 78 79 80 81 82
}

// Error will return the error when the find next row element
func (rows *Rows) Error() error {
	return rows.err
}

// Columns return the current row's column values
83
func (rows *Rows) Columns() ([]string, error) {
84 85 86 87 88 89
	var (
		err          error
		inElement    string
		row, cellCol int
		columns      []string
	)
90 91 92 93 94

	if rows.stashRow >= rows.curRow {
		return columns, err
	}

L
Lunny Xiao 已提交
95
	d := rows.f.sharedStringsReader()
96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111
	for {
		token, _ := rows.decoder.Token()
		if token == nil {
			break
		}
		switch startElement := token.(type) {
		case xml.StartElement:
			inElement = startElement.Name.Local
			if inElement == "row" {
				for _, attr := range startElement.Attr {
					if attr.Name.Local == "r" {
						row, err = strconv.Atoi(attr.Value)
						if err != nil {
							return columns, err
						}
						if row > rows.curRow {
112
							rows.stashRow = row - 1
113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136
							return columns, err
						}
					}
				}
			}
			if inElement == "c" {
				colCell := xlsxC{}
				_ = rows.decoder.DecodeElement(&colCell, &startElement)
				cellCol, _, err = CellNameToCoordinates(colCell.R)
				if err != nil {
					return columns, err
				}
				blank := cellCol - len(columns)
				for i := 1; i < blank; i++ {
					columns = append(columns, "")
				}
				val, _ := colCell.getValueFrom(rows.f, d)
				columns = append(columns, val)
			}
		case xml.EndElement:
			inElement = startElement.Name.Local
			if inElement == "row" {
				return columns, err
			}
137
		}
L
Lunny Xiao 已提交
138
	}
139
	return columns, err
L
Lunny Xiao 已提交
140 141 142 143 144 145 146 147
}

// ErrSheetNotExist defines an error of sheet is not exist
type ErrSheetNotExist struct {
	SheetName string
}

func (err ErrSheetNotExist) Error() string {
148
	return fmt.Sprintf("sheet %s is not exist", string(err.SheetName))
L
Lunny Xiao 已提交
149 150 151 152
}

// Rows return a rows iterator. For example:
//
xurime's avatar
xurime 已提交
153
//    rows, err := f.Rows("Sheet1")
154
//    if err != nil {
xurime's avatar
xurime 已提交
155
//        fmt.Println(err)
156 157
//        return
//    }
L
Lunny Xiao 已提交
158
//    for rows.Next() {
xurime's avatar
xurime 已提交
159
//        row, err := rows.Columns()
160
//        if err != nil {
xurime's avatar
xurime 已提交
161
//            fmt.Println(err)
162
//        }
163
//        for _, colCell := range row {
xurime's avatar
xurime 已提交
164
//            fmt.Print(colCell, "\t")
L
Lunny Xiao 已提交
165
//        }
xurime's avatar
xurime 已提交
166
//        fmt.Println()
L
Lunny Xiao 已提交
167 168 169 170 171 172 173
//    }
//
func (f *File) Rows(sheet string) (*Rows, error) {
	name, ok := f.sheetMap[trimSheetName(sheet)]
	if !ok {
		return nil, ErrSheetNotExist{sheet}
	}
174 175 176
	if f.Sheet[name] != nil {
		// flush data
		output, _ := xml.Marshal(f.Sheet[name])
177
		f.saveFileList(name, replaceRelationshipsNameSpaceBytes(output))
178 179 180 181 182 183 184
	}
	var (
		err       error
		inElement string
		row       int
		rows      Rows
	)
xurime's avatar
xurime 已提交
185
	decoder := f.xmlNewDecoder(bytes.NewReader(f.readXML(name)))
186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206
	for {
		token, _ := decoder.Token()
		if token == nil {
			break
		}
		switch startElement := token.(type) {
		case xml.StartElement:
			inElement = startElement.Name.Local
			if inElement == "row" {
				for _, attr := range startElement.Attr {
					if attr.Name.Local == "r" {
						row, err = strconv.Atoi(attr.Value)
						if err != nil {
							return &rows, err
						}
					}
				}
				rows.totalRow = row
			}
		default:
		}
L
Lunny Xiao 已提交
207
	}
208 209
	rows.f = f
	rows.sheet = name
xurime's avatar
xurime 已提交
210
	rows.decoder = f.xmlNewDecoder(bytes.NewReader(f.readXML(name)))
211
	return &rows, nil
L
Lunny Xiao 已提交
212 213
}

214 215
// SetRowHeight provides a function to set the height of a single row. For
// example, set the height of the first row in Sheet1:
N
Nikolas Silva 已提交
216
//
xurime's avatar
xurime 已提交
217
//    err := f.SetRowHeight("Sheet1", 1, 50)
N
Nikolas Silva 已提交
218
//
219
func (f *File) SetRowHeight(sheet string, row int, height float64) error {
220
	if row < 1 {
221
		return newInvalidRowNumberError(row)
222
	}
223

xurime's avatar
xurime 已提交
224 225 226 227
	xlsx, err := f.workSheetReader(sheet)
	if err != nil {
		return err
	}
228 229 230

	prepareSheetXML(xlsx, 0, row)

231 232 233
	rowIdx := row - 1
	xlsx.SheetData.Row[rowIdx].Ht = height
	xlsx.SheetData.Row[rowIdx].CustomHeight = true
234
	return nil
N
Nikolas Silva 已提交
235 236
}

xurime's avatar
xurime 已提交
237
// getRowHeight provides a function to get row height in pixels by given sheet
238 239
// name and row index.
func (f *File) getRowHeight(sheet string, row int) int {
xurime's avatar
xurime 已提交
240
	xlsx, _ := f.workSheetReader(sheet)
X
xxb-at-julichina 已提交
241 242
	for i := range xlsx.SheetData.Row {
		v := &xlsx.SheetData.Row[i]
243 244 245 246 247 248 249 250
		if v.R == row+1 && v.Ht != 0 {
			return int(convertRowHeightToPixels(v.Ht))
		}
	}
	// Optimisation for when the row heights haven't changed.
	return int(defaultRowHeightPixels)
}

xurime's avatar
xurime 已提交
251
// GetRowHeight provides a function to get row height by given worksheet name
252 253
// and row index. For example, get the height of the first row in Sheet1:
//
xurime's avatar
xurime 已提交
254
//    height, err := f.GetRowHeight("Sheet1", 1)
255
//
256
func (f *File) GetRowHeight(sheet string, row int) (float64, error) {
257
	if row < 1 {
258
		return defaultRowHeightPixels, newInvalidRowNumberError(row)
259 260
	}

xurime's avatar
xurime 已提交
261 262 263 264
	xlsx, err := f.workSheetReader(sheet)
	if err != nil {
		return defaultRowHeightPixels, err
	}
265
	if row > len(xlsx.SheetData.Row) {
266
		return defaultRowHeightPixels, nil // it will be better to use 0, but we take care with BC
267
	}
268
	for _, v := range xlsx.SheetData.Row {
269
		if v.R == row && v.Ht != 0 {
270
			return v.Ht, nil
271 272 273
		}
	}
	// Optimisation for when the row heights haven't changed.
274
	return defaultRowHeightPixels, nil
275 276
}

xurime's avatar
xurime 已提交
277
// sharedStringsReader provides a function to get the pointer to the structure
278 279
// after deserialization of xl/sharedStrings.xml.
func (f *File) sharedStringsReader() *xlsxSST {
280 281
	var err error

282 283
	if f.SharedStrings == nil {
		var sharedStrings xlsxSST
284
		ss := f.readXML("xl/sharedStrings.xml")
285
		if len(ss) == 0 {
286 287
			ss = f.readXML("xl/SharedStrings.xml")
		}
288 289 290 291
		if err = f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(ss))).
			Decode(&sharedStrings); err != nil && err != io.EOF {
			log.Printf("xml decode error: %s", err)
		}
292 293
		f.SharedStrings = &sharedStrings
	}
294

295
	return f.SharedStrings
A
ahmad 已提交
296 297
}

xurime's avatar
xurime 已提交
298 299 300
// getValueFrom return a value from a column/row cell, this function is
// inteded to be used with for range on rows an argument with the xlsx opened
// file.
301
func (xlsx *xlsxC) getValueFrom(f *File, d *xlsxSST) (string, error) {
302 303 304 305
	switch xlsx.T {
	case "s":
		xlsxSI := 0
		xlsxSI, _ = strconv.Atoi(xlsx.V)
306 307 308 309
		if len(d.SI) > xlsxSI {
			return f.formattedValue(xlsx.S, d.SI[xlsxSI].String()), nil
		}
		return f.formattedValue(xlsx.S, xlsx.V), nil
310
	case "str":
311
		return f.formattedValue(xlsx.S, xlsx.V), nil
312
	case "inlineStr":
xurime's avatar
xurime 已提交
313 314 315 316
		if xlsx.IS != nil {
			return f.formattedValue(xlsx.S, xlsx.IS.String()), nil
		}
		return f.formattedValue(xlsx.S, xlsx.V), nil
317
	default:
318
		return f.formattedValue(xlsx.S, xlsx.V), nil
319
	}
A
ahmad 已提交
320
}
321

322
// SetRowVisible provides a function to set visible of a single row by given
323
// worksheet name and Excel row number. For example, hide row 2 in Sheet1:
324
//
xurime's avatar
xurime 已提交
325
//    err := f.SetRowVisible("Sheet1", 2, false)
326
//
327
func (f *File) SetRowVisible(sheet string, row int, visible bool) error {
328
	if row < 1 {
329
		return newInvalidRowNumberError(row)
330
	}
331

xurime's avatar
xurime 已提交
332 333 334 335
	xlsx, err := f.workSheetReader(sheet)
	if err != nil {
		return err
	}
336 337
	prepareSheetXML(xlsx, 0, row)
	xlsx.SheetData.Row[row-1].Hidden = !visible
338
	return nil
339 340
}

341 342 343
// GetRowVisible provides a function to get visible of a single row by given
// worksheet name and Excel row number. For example, get visible state of row
// 2 in Sheet1:
344
//
xurime's avatar
xurime 已提交
345
//    visible, err := f.GetRowVisible("Sheet1", 2)
346
//
347
func (f *File) GetRowVisible(sheet string, row int) (bool, error) {
348
	if row < 1 {
349
		return false, newInvalidRowNumberError(row)
350 351
	}

xurime's avatar
xurime 已提交
352 353 354 355
	xlsx, err := f.workSheetReader(sheet)
	if err != nil {
		return false, err
	}
356
	if row > len(xlsx.SheetData.Row) {
357
		return false, nil
358
	}
359
	return !xlsx.SheetData.Row[row-1].Hidden, nil
360
}
361

362
// SetRowOutlineLevel provides a function to set outline level number of a
xurime's avatar
xurime 已提交
363 364
// single row by given worksheet name and Excel row number. The value of
// parameter 'level' is 1-7. For example, outline row 2 in Sheet1 to level 1:
365
//
xurime's avatar
xurime 已提交
366
//    err := f.SetRowOutlineLevel("Sheet1", 2, 1)
367
//
368
func (f *File) SetRowOutlineLevel(sheet string, row int, level uint8) error {
369
	if row < 1 {
370
		return newInvalidRowNumberError(row)
371
	}
xurime's avatar
xurime 已提交
372 373 374
	if level > 7 || level < 1 {
		return errors.New("invalid outline level")
	}
xurime's avatar
xurime 已提交
375 376 377 378
	xlsx, err := f.workSheetReader(sheet)
	if err != nil {
		return err
	}
379
	prepareSheetXML(xlsx, 0, row)
380
	xlsx.SheetData.Row[row-1].OutlineLevel = level
381
	return nil
382 383
}

xurime's avatar
xurime 已提交
384
// GetRowOutlineLevel provides a function to get outline level number of a
xurime's avatar
xurime 已提交
385 386
// single row by given worksheet name and Excel row number. For example, get
// outline number of row 2 in Sheet1:
387
//
xurime's avatar
xurime 已提交
388
//    level, err := f.GetRowOutlineLevel("Sheet1", 2)
389
//
390
func (f *File) GetRowOutlineLevel(sheet string, row int) (uint8, error) {
391
	if row < 1 {
392
		return 0, newInvalidRowNumberError(row)
393
	}
xurime's avatar
xurime 已提交
394 395 396 397
	xlsx, err := f.workSheetReader(sheet)
	if err != nil {
		return 0, err
	}
398
	if row > len(xlsx.SheetData.Row) {
399
		return 0, nil
400
	}
401
	return xlsx.SheetData.Row[row-1].OutlineLevel, nil
402 403
}

C
caozhiyi 已提交
404
// RemoveRow provides a function to remove single row by given worksheet name
405
// and Excel row number. For example, remove row 3 in Sheet1:
406
//
xurime's avatar
xurime 已提交
407
//    err := f.RemoveRow("Sheet1", 3)
408
//
409 410 411 412
// Use this method with caution, which will affect changes in references such
// as formulas, charts, and so on. If there is any referenced value of the
// worksheet, it will cause a file error when you open it. The excelize only
// partially updates these references currently.
413
func (f *File) RemoveRow(sheet string, row int) error {
414
	if row < 1 {
415
		return newInvalidRowNumberError(row)
416 417
	}

xurime's avatar
xurime 已提交
418 419 420 421
	xlsx, err := f.workSheetReader(sheet)
	if err != nil {
		return err
	}
422
	if row > len(xlsx.SheetData.Row) {
423
		return f.adjustHelper(sheet, rows, row, -1)
424
	}
425 426 427 428
	for rowIdx := range xlsx.SheetData.Row {
		if xlsx.SheetData.Row[rowIdx].R == row {
			xlsx.SheetData.Row = append(xlsx.SheetData.Row[:rowIdx],
				xlsx.SheetData.Row[rowIdx+1:]...)[:len(xlsx.SheetData.Row)-1]
429
			return f.adjustHelper(sheet, rows, row, -1)
430 431
		}
	}
432
	return nil
433 434
}

435 436 437
// InsertRow provides a function to insert a new row after given Excel row
// number starting from 1. For example, create a new row before row 3 in
// Sheet1:
438
//
xurime's avatar
xurime 已提交
439
//    err := f.InsertRow("Sheet1", 3)
440
//
441 442 443 444
// Use this method with caution, which will affect changes in references such
// as formulas, charts, and so on. If there is any referenced value of the
// worksheet, it will cause a file error when you open it. The excelize only
// partially updates these references currently.
445
func (f *File) InsertRow(sheet string, row int) error {
446
	if row < 1 {
447
		return newInvalidRowNumberError(row)
448
	}
449
	return f.adjustHelper(sheet, rows, row, 1)
450 451
}

452
// DuplicateRow inserts a copy of specified row (by its Excel row number) below
V
Veniamin Albaev 已提交
453
//
xurime's avatar
xurime 已提交
454
//    err := f.DuplicateRow("Sheet1", 2)
V
Veniamin Albaev 已提交
455
//
456 457 458 459
// Use this method with caution, which will affect changes in references such
// as formulas, charts, and so on. If there is any referenced value of the
// worksheet, it will cause a file error when you open it. The excelize only
// partially updates these references currently.
460 461
func (f *File) DuplicateRow(sheet string, row int) error {
	return f.DuplicateRowTo(sheet, row, row+1)
462 463
}

464 465
// DuplicateRowTo inserts a copy of specified row by it Excel number
// to specified row position moving down exists rows after target position
466
//
xurime's avatar
xurime 已提交
467
//    err := f.DuplicateRowTo("Sheet1", 2, 7)
468
//
469 470 471 472
// Use this method with caution, which will affect changes in references such
// as formulas, charts, and so on. If there is any referenced value of the
// worksheet, it will cause a file error when you open it. The excelize only
// partially updates these references currently.
473
func (f *File) DuplicateRowTo(sheet string, row, row2 int) error {
474
	if row < 1 {
475
		return newInvalidRowNumberError(row)
476
	}
477

xurime's avatar
xurime 已提交
478 479 480 481
	xlsx, err := f.workSheetReader(sheet)
	if err != nil {
		return err
	}
482
	if row > len(xlsx.SheetData.Row) || row2 < 1 || row == row2 {
483
		return nil
V
Veniamin Albaev 已提交
484 485
	}

486 487 488
	var ok bool
	var rowCopy xlsxRow

489
	for i, r := range xlsx.SheetData.Row {
V
Veniamin Albaev 已提交
490
		if r.R == row {
491
			rowCopy = xlsx.SheetData.Row[i]
492
			ok = true
V
Veniamin Albaev 已提交
493 494 495
			break
		}
	}
496
	if !ok {
497
		return nil
498
	}
V
Veniamin Albaev 已提交
499

500 501 502
	if err := f.adjustHelper(sheet, rows, row2, 1); err != nil {
		return err
	}
503 504

	idx2 := -1
505
	for i, r := range xlsx.SheetData.Row {
506 507 508 509 510
		if r.R == row2 {
			idx2 = i
			break
		}
	}
511
	if idx2 == -1 && len(xlsx.SheetData.Row) >= row2 {
512
		return nil
V
Veniamin Albaev 已提交
513
	}
514 515 516 517

	rowCopy.C = append(make([]xlsxC, 0, len(rowCopy.C)), rowCopy.C...)
	f.ajustSingleRowDimensions(&rowCopy, row2)

V
Veniamin Albaev 已提交
518
	if idx2 != -1 {
519
		xlsx.SheetData.Row[idx2] = rowCopy
V
Veniamin Albaev 已提交
520
	} else {
521
		xlsx.SheetData.Row = append(xlsx.SheetData.Row, rowCopy)
V
Veniamin Albaev 已提交
522
	}
523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556
	return f.duplicateMergeCells(sheet, xlsx, row, row2)
}

// duplicateMergeCells merge cells in the destination row if there are single
// row merged cells in the copied row.
func (f *File) duplicateMergeCells(sheet string, xlsx *xlsxWorksheet, row, row2 int) error {
	if xlsx.MergeCells == nil {
		return nil
	}
	if row > row2 {
		row++
	}
	for _, rng := range xlsx.MergeCells.Cells {
		coordinates, err := f.areaRefToCoordinates(rng.Ref)
		if err != nil {
			return err
		}
		if coordinates[1] < row2 && row2 < coordinates[3] {
			return nil
		}
	}
	for i := 0; i < len(xlsx.MergeCells.Cells); i++ {
		areaData := xlsx.MergeCells.Cells[i]
		coordinates, _ := f.areaRefToCoordinates(areaData.Ref)
		x1, y1, x2, y2 := coordinates[0], coordinates[1], coordinates[2], coordinates[3]
		if y1 == y2 && y1 == row {
			from, _ := CoordinatesToCellName(x1, row2)
			to, _ := CoordinatesToCellName(x2, row2)
			if err := f.MergeCell(sheet, from, to); err != nil {
				return err
			}
			i++
		}
	}
557
	return nil
V
Veniamin Albaev 已提交
558 559
}

xurime's avatar
xurime 已提交
560 561
// checkRow provides a function to check and fill each column element for all
// rows and make that is continuous in a worksheet of XML. For example:
562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583
//
//    <row r="15" spans="1:22" x14ac:dyDescent="0.2">
//        <c r="A15" s="2" />
//        <c r="B15" s="2" />
//        <c r="F15" s="1" />
//        <c r="G15" s="1" />
//    </row>
//
// in this case, we should to change it to
//
//    <row r="15" spans="1:22" x14ac:dyDescent="0.2">
//        <c r="A15" s="2" />
//        <c r="B15" s="2" />
//        <c r="C15" s="2" />
//        <c r="D15" s="2" />
//        <c r="E15" s="2" />
//        <c r="F15" s="1" />
//        <c r="G15" s="1" />
//    </row>
//
// Noteice: this method could be very slow for large spreadsheets (more than
// 3000 rows one sheet).
584
func checkRow(xlsx *xlsxWorksheet) error {
585 586
	for rowIdx := range xlsx.SheetData.Row {
		rowData := &xlsx.SheetData.Row[rowIdx]
587

588 589 590
		colCount := len(rowData.C)
		if colCount == 0 {
			continue
591
		}
592 593 594 595
		lastCol, _, err := CellNameToCoordinates(rowData.C[colCount-1].R)
		if err != nil {
			return err
		}
596 597 598 599 600 601 602 603

		if colCount < lastCol {
			oldList := rowData.C
			newlist := make([]xlsxC, 0, lastCol)

			rowData.C = xlsx.SheetData.Row[rowIdx].C[:0]

			for colIdx := 0; colIdx < lastCol; colIdx++ {
604 605 606 607 608
				cellName, err := CoordinatesToCellName(colIdx+1, rowIdx+1)
				if err != nil {
					return err
				}
				newlist = append(newlist, xlsxC{R: cellName})
609 610 611 612 613 614
			}

			rowData.C = newlist

			for colIdx := range oldList {
				colData := &oldList[colIdx]
615 616 617 618
				colNum, _, err := CellNameToCoordinates(colData.R)
				if err != nil {
					return err
				}
619
				xlsx.SheetData.Row[rowIdx].C[colNum-1] = *colData
620 621 622
			}
		}
	}
623
	return nil
624 625
}

xurime's avatar
xurime 已提交
626 627 628
// convertRowHeightToPixels provides a function to convert the height of a
// cell from user's units to pixels. If the height hasn't been set by the user
// we use the default value. If the row is hidden it has a value of zero.
629 630 631 632 633 634 635 636
func convertRowHeightToPixels(height float64) float64 {
	var pixels float64
	if height == 0 {
		return pixels
	}
	pixels = math.Ceil(4.0 / 3.0 * height)
	return pixels
}